我们所熟知的面向对象言语如 C++、Java 都有类的的观点,类是实例的范例模板,比方Student
示意门生这类范例,而不示意任何详细的某个门生,而实例就是依据这个范例建立的一个详细的对象,比方zhangsan
、lisi
,由类天生对象表现了笼统模板到详细化的历程,这叫做基于类的面向对象体式格局,而 JavaScript 没有类的观点,是基于原型的面向对象体式格局(虽然 Es6 增加了 class
,本质是对原型体式格局的封装)。总结起来就是以下两点:
- 在基于类的面向对象体式格局中,对象(object)依托类(class)来发生。
- 在基于原型的面向对象体式格局中,对象(object)则是依托组织函数(constructor)和原型(prototype)组织出来的。
面向对象言语的第一个特征毫无疑问是封装,在 JS 中,封装的历程就是把一些属性和要领放到对象中“包裹”起来,那末我们要怎样去封装属性和要领,或者说怎样去建立对象呢(后文统一说建立对象)?下面用逐步推进的体式格局论述:
对象字面量 --> 工场情势 --> 组织函数 --> 原型情势 --> 组织函数+原型情势
对象字面量
JS中建立对象最原始的体式格局有两种:
- 对象字面量
var person = {
name: "leon",
age: "20",
greeting: function() {
alert('Hi!');
}
}
- 为
Object
实例增加属性要领
var person = new Object();
person.name = "leon";
person.age = "20";
person.greeting = function() {
alert('Hi!');
};
- 长处:代码简朴
- 瑕玷: 建立多个对象会发生大批的代码,编写贫苦,且并没有实例与原型的观点。
- 处理办法:工场情势。
工场情势
工场情势是编程范畴一种广为人知的设想情势,它笼统了建立详细对象的历程。JS 中建立一个函数,把建立新对象、增加对象属性、返回对象的历程放到这个函数中,用户只需挪用函数来天生对象而无需关注对象建立细节,这叫工场情势:
function createPerson(name, age) {
var person = new Object();
person.name = name;
person.age = age;
person.greeting = function() {
alert('Hi!');
};
return person;
}
var person1 = createPerson("leon", "20");
- 长处:工场情势处理了对象字面量建立对象代码反复题目,建立相似对象能够运用统一API。
- 瑕玷:由于是挪用函建立对象,没法辨认对象的范例。
- 处理办法:组织函数
组织函数
JS 中组织函数与其他函数的唯一区分,就在于挪用它的体式格局差别。任何函数,只需经由过程new
操作符来挪用,那它就能够作为组织函数。来看下面的例子:
function Person(name, age) {
this.name = name;
this.age = age;
this.greeting = function() {
alert('Hi!');
};
// return this;
}
var person1 = new Person("leon", "20");
var person2 = new Person("jack", "21");
经由过程组织函数new
一个实例阅历了四步:
- 建立一个新对象;
- 将组织函数内的
this
绑定到新对象上; - 为新对象增加属性和要领;
- 返回新对象(JS 引擎会默许增加
return this;
)。
而经由过程组织函数建立的对象都有一个constructor
属性,它是一个指向组织函数自身的指针,因而就能够检测对象的范例啦。:
alert(person1.constructor === Person) //true
alert(person1 instanceof Person) // true
然则依然存在题目:
alert(person1.greeting == person2.greeting) //false
统一个组织函数中定义了greeting()
,而差别实例上的同名函数倒是不相等的,意味着这两个同名函数的内存空间不一致,也就是组织函数中的要领要在每一个实例上从新建立一次。这明显是不划算的。
- 长处:处理了相似对象建立题目,且能够检测对象范例。
- 瑕玷:组织函数要领要在每一个实例上新建一次。
- 处理办法:原型情势。
原型情势
终究讲到了原型情势,JS 中每一个组织函数都有一个prototype
属性,这个属性是一个指针,指向原型对象,而这个原型对象包含了这个组织函数一切实例同享的属性和要领。而实例对象中有一个proto
属性,它指向原型对象,也就是组织函数.prototype == 原型对象 == 对象._proto_
,那末对象就能够获取到原型对象中的属性和要领啦。同时,一切对象中都有一个constructor
属性,原型对象的constructor
指向其对应的组织函数。
运用原型,就意味着我们能够把愿望实例同享的属性和要领放到原型对象中去,而不是放在组织函数中,如许每一次经由过程组织函数new
一个实例,原型对象中定义的要领都不会从新建立一次。来看下面的例子:
function Person() {
}
Person.prototype.name = "leon";
Person.prototype.age = "20";
Person.prototype.greeting = function() {
alert('Hi!');
};
var person1 = new Person();
var person2 = new Person();
alert(person1.name); //"leon"
alert(person2.name); //"leon"
alert(person1.greeting == person2.greeting); //true
- 长处:与纯真运用组织函数不一样,原型对象中的要领不会在实例中从新建立一次,勤俭内存。
- 瑕玷:运用空组织函数,实例 person1 和 person2 的
name
都一样了,我们明显不愿望一切实例属性要领都一样,它们照样要有本身独占的属性要领。而且假如原型中对象中有援用范例值,实例中获得的都是该值的援用,意味着一个实例修改了这个值,其他实例中的值都邑响应转变。 - 处理办法:组织函数+原型情势组合运用。
别的 JS 中还定义了一些与原型相干的属性,这里排列一下:
-
Object.getPrototypeOf()
,获得实例的原型对象。
Object.getPrototypeOf(person1);
-
isPrototypeOf()
,推断是不是是一个实例的原型对象。
Person.prototype.isPrototypeOf(person1);
-
hasOwnProperty()
,检测一个属性是不是存在于实例中
person1.hasOwnProperty("name");
-
in
,推断一个属性是不是存在于实例和原型中。
"name" in person1;
组织函数+原型情势
末了一种体式格局就是组合运用组织函数和原型情势,组织函数用于定义实例属性,而同享属性和要领定义在原型对象中。如许每一个实例都有本身独占的属性,同时又有对同享要领的援用,节约内存。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person,
nationality: "China",
greeting: function() {
alert(this.name);
}
}
var person1 = new Person("leon", "20");
var person2 = new Person("jack", "21");
alert(person1.greeting == person2.greeting) //true
上面代码顶用对象字面量的情势重写了原型对象,如许相当于建立了一个新的对象,那末它的constructor
属性就会指向Object
,这里为了让它继承指向组织函数,显现的写上了constructor: Person
这类组织函数与原型情势混成的情势,是现在在 JS 中运用最为普遍的一种建立对象的要领。