创建对象
1 工厂方式
1 | function createPerson(name, age, job){ |
工厂模式虽然解决了创建 多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)
2构造方法
1 | function Person(name, age, job){ |
构造的对象属于同一类型1
2
3
4
5
6
7alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
其实有这么一个问题:不同实例上的同名函数是不相等的
即person1.sayName == person2.sayName为false
3原型方式
1 | function Person(){ } |
所有对象可以通过引用Person.prototype属性,从而实现共享属性和方法
原型对象,对象属性和原型属性
无论什么时候,只要创建了一个新函数(如上文Person),就会根据一组特定的规则为该函数创建一个原型对象prototype,在默认情况下,所有原型对象都会自动获得一个 constructor,该构造方法指向 prototype 属性所在函数(即Person.prototype.constructor=Person)。通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(__proto__,注,ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但 Firefox、Safari 和 Chrome 在每个对象上都支持属性 proto ;而在其他实现中,这个属性对脚本则是完全不可见的),指向构造函数的原型对象(即person1.__proto__=Person.prototype)。
在无法访问[[Prototype]]的情形下(QQs尚未接触到不支持__proto__的环境),可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系,有Person.prototype.isPrototypeOf(person1)返回true,另ES5提供Object.getPrototypeOf,有Object.getPrototypeOf(person1) == Person.prototype为true。
代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先 从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到, 则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这 个属性,则返回该属性的值。也就是说,在我们调用 person1.sayName()的时候,会先后执行两次搜 索。首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。”然后,它继续搜索,再 问:“person1 的原型有 sayName 属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函数。
可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们 在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。而delete对象实例的这个属性,会重新暴露原型对象的属性1
2
3
4
5
6Person.prototype.name = "Nicholas";
var person1 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
delete person1.name;
alert(person1.name);//"Nicholas"——来自原型
使用hasOwnProperty()方法可以检测一个属性是存在于实例中(true),还是存在于原型中(false)。
检测属性(无论实例属性或是原型属性)是否存在可以用in,即”name” in person1返回true。
从in说到for in,遍历所有能够通过对象访问的、可枚举的(enumerated)属性,其中 既包括存在于实例中的属性,也包括存在于原型中的属性。
“可枚举”相当于”可以出现在对象属性的遍历中”,在数组上使用for in遍历,不仅会包含所有数值索引,还会包含所有可枚举属性,。最好只在对象上应用 for in 循环,如果要遍历数组就使用传统的 for 循环来遍历数值索引
Object.propertyIsEnumerable()方法可以检查给定属性名是否直接存在于实例中且enumerable = true
不可枚举属性,即将 [[Enumerable]]标记为 false 的属性,而屏蔽不可枚举属性的实例属性也会遍历到。
Object.keys()返回所有可枚举属性的字符串数组。
Object.getOwnPropertyNames()返回所有属性的数组,无论是否可以枚举
更多创建对象姿势
动态原型模式
1 | function Person(name, age, job){ //属性 |
原型对象方法sayName在第一次调用构造方法Person时执行。
寄生构造方法模式
1 | function Person(name, age, job){ |
注意这种方式不能依赖 instanceof 操作符来确定对象类型
稳妥构造函数模式
1 | function Person(name, age, job){ //创建要返回的对象 |
对象实例只能调用闭包内函数以返回name,而无法直接访问任何属性
原型链和继承
原型链
假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。1
2
3
4
5
6
7
8
9
10
11
12
13function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){ }
SubType.prototype = new SuperType();//继承了 SuperType
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true
SubType 继承了 SuperType。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于基类型的实例中的所有属性和方法,现在也存在于衍生类原型对象中了。
注意,所有引用类型的对象都继承了Object,这个继承也是通过原型链实现的。
两个问题缺点:父类包含的引用类型属性,随实例共享到子类的所有实例,这可能并不是我们期望的;子类原型引用的是父类实例,不能向超类型的构造函数中传递参数(在ES6中可以在子类constructor中通过超类型super调用父类方法)。
子类原型对象的constructor指向父类构造方法
借用构造方法constructor stealing
1 | function SuperType(){ |
传参1
2
3
4
5
6
7
8
9
10function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this, "Nicholas");//调用SuperType构造方法
this.age = 29; //实例属性
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age);//29
无法避免构造函数模式存在的问题——方法都在构造函数中定 义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。
组合构造
1 | function SuperType(name){ |
在注@这一行,将原型对象的构造方法指向到SubType,因为之前该对象根据继承关系图示,作为SuperType实例,其[[prototype]]指向SuperType.prototype,故而SubType.prototype.constructor原先为SuperType方法。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。
原型式继承
浅复制方法1
2
3
4
5function object(o){
function F(){}
F.prototype = o;
return new F();
}
关于ES5 Object.create();
操作符instanceOf1
2
3
4
5
6
7
8
9
10
11function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}