第六章 面向对象的程序设计
1. 数据属性
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性。默认值为true。
[[Enumerable]]:表示能否通过for-in循环返回属性。默认值为true。
[[Writable]]:表示能否修改属性的值,默认为true。
[[Value]]:包含这个属性的数据值。默认为undefined。
要修改属性默认的特性,必须使用ECMAScript5的Object.defineProperty( )方法。接收三个参数:属性所在对象,属性名字和一个描述符对象。其中,描述符对象的属性必须是:configurable、enumerable、writable和value。
可以多次调用Object.defineProperty()方法需改同一属性,但在把configurable特性设置为false之后就会有限制了。
2. 访问器属性
访问器属性不包含数据值,包含一对getter和setter函数。
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性。默认值为true。
[[Enumerable]]:表示能否通过for-in循环返回属性。默认值为true。
[[Get]]:在读取属性时调用的函数,默认值为undefined。
[[Set]]:在写入属性时调用的函数,默认值为undefined。
定义多个属性,ECMAScript5定义了Object.defineProperties()方法。
3. 使用ECMAScript5的Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。此方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象。
4. 创建对象
方法 |
细节 |
工厂模式 |
用函数来封装以特定接口创建对象的细节。 |
优点:可以无数次调用函数。 |
|
缺点:但没有解决对象识别的问题。 |
|
构造函数模式 |
没有显示地创建对象; 直接将属性和方法赋给了this对象; 没有return语句。 构造函数始终都以一个大写字母开头,而非构造函数应该以一个小写字母开头。 要创建构造函数的新实例,必须使用new操作符。 |
优点:构造函数模式胜过工厂模式的地方在于:可以将它的实例标识为一种特定的类型。 |
|
缺点:每个方法都要在每个实例上重新创建一遍。 |
|
原型模式 |
每个函数都有一个prototype(原型)属性,此属性是一个指针,指向一个对象,此对象的用途是包含可以由特定类型的所有实例共享的属性和方法。 当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,但不会修改那个属性。使用delete操作符则可以完全删除实例属性,能够重新访问原型中的属性。 hasOwnProperty( )方法可以检测一个属性是存在于实例中,还是存在于原型中。只有存在于对象实例中,才会返回true。 单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。只要in操作符返回true,而hasOwnProperty( )返回false,就可以确定属性时原型中的属性。 hasPrototypeProperty( )方法,当原型属性存在时,返回true,当原型属性被实例重写时,返回false。 在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。 重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,他们引用的仍然是最初的原型。 |
优点:可以让所有对象实例共享它所包含的属性和方法,即不必再构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。 |
|
缺点:由共享本质,对于包含引用类型值的属性而言问题突出。 |
|
组合使用构造函数模式和原型模式 |
创建自定义的最常见方式,也是用来定义引用类型的默认形式。 实例属性在构造函数中定义,所有实例共享的属性constructor和方法都在原型中定义。 |
优点:集构造函数与原型模式之所长。 |
|
动态原型模式 |
使用if语句检查初始化之后应该存在的任何属性或方法。采用该模式创建的对象可以用instanceof操作符确定它的类型。 |
寄生构造函数模式 |
除了使用new操作符并把使用的包装函数叫做构造函数之外,此模式与工厂模式是一样的。构造函数在不返回值的情况下,默认会返回新对象实例,而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数返回的值。 返回的对象与构造函数或者构造函数的原型属性直接没有关系。 不能依赖instanceof操作符来确定对象类型。 可以使用其他模式,尽量不要使用该模式 |
稳妥构造函数模式 |
稳妥对象:没有公共属性,而且其方法也不引用this的对象。 与寄生构造模式的两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。 |
5. OO语言一般支持两种继承方式:接口集成和实现继承。接口集成只继承方法签名,而实现继承则集继承实际的方法。ECMAScript只支持实现继承,且主要依赖原型链来实现。
6. 原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
functionSuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue= function( ){
returnthis.property;
}
functionSubType(){
this.subproperty = false;
}
SubType.prototype= new SuperType();
SubType.prototype.getSubValue= function(){
return this.subproperty;
}
instance.getSubValue() //false
instance.getSuperValue() //true
7. 所有引用类型默认都集成了Object,而这个继承也是通过原型链实现的。
8. 确定原型和实例的两种方法:一是使用instanceof操作符,用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true,第二种方式是使用isPrototypeOf( )方法,只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
9. 给原型添加方法的代码一定要放在替换原型的语句之后,另外,在通过原型链实现继承时,不能使用对象字面量创建原型方法。
10. 继承
方法 |
实现 |
原型链 |
利用原型让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。 |
原型链的两大问题,一是来自包含引用类型值的原型,另一个是在创建子类型的实例时,不能向超类型的构造函数中传递参数。 |
|
借用构造函数 |
使用apply()和call( )方法在新创建的对象上执行构造函数。 |
优点:相对于原型链而言,可以在子类型构造函数中向超类型构造函数传递参数 |
|
缺点:方法都在构造函数中定义,因此函数复用就无从谈起。 |
|
组合集成 |
将原型链和借用构造函数的技术一起,取长处的方式。原理是使用原型链实现对原型属性和方法的集成,而通过借用构造函数来实现对实例属性的继承。 |
优点:避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。而且,instanceof和isPrototypeOf( )也能够用于识别基于组合继承创建的对象。 |
|
缺点:无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。 |
|
原型式继承 |
此方法没有严格意义上的构造函数,借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。 ECMAScript通过新增Object.create( )方法规范化了原型式继承。 |
缺点:包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。 |
|
寄生式继承 |
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的处理之后一样返回对象。 |
缺点:使用寄生式集成来为对象添加函数,会由于不能做到函数复用而降低效率 |
|
寄生组合式继承 |
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路是:不必为了指定子类型的原型而调用超烈性的构造函数,我们所需要的是超类型原型的一个副本而已。即使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。 |
优点:效率高,值调用一次超类型原型的构造函数,原型链还能保持不变,能正常使用instanceof和isPrototypeOf( )。是最理想的继承范式。 |