第六章 面向对象的程序设计
理解对象
-
属性类型
- 数据属性,包含一个数据值的位置,四个特性
- Configurable:能否删除并重新定义属性、修改属性的特性,默认为 true
- Enumerable:能否通过 for-in 循环返回属性,默认为 true
- Writable:能否修改属性的值,默认为 true
- Value:该属性的数据值,默认为 undefined
- 修改特性值:
在调用 Object.defineProperty()方法时,如果不指定,configurable、enumerable、writable 特性的默认值都是 false;除了 writable,其他属性设为 false 后不可变回Object.defineProperty(object, propertyName, { writable: false, value: "name" });
- 访问器属性,包含 getter()、setter(),四个特性
- Configurable:能否删除并重新定义属性、修改属性的特性,默认为 true
- Enumerable:能否通过 for-in 循环返回属性,默认为 true
- Get:读取属性时调用的函数,默认为 undefined
- Set:写入属性时调用的函数,默认为 undefined
访问器属性不能直接定义,必须使用 Object.defineProperty() 定义
- 数据属性,包含一个数据值的位置,四个特性
-
定义多个属性:Object.defineProperties(object, properties)
-
读取属性的特性:Object.getOwnPropertyDescriptor(object, propertyName)
创建对象
-
工厂模式:用函数来封装以特定接口创建对象的细节
-
构造函数模式
- 构造函数自定义
- 没有显式地创建对象
- 直接将属性和方法赋值给 this 对象
- 没有 return 语句
- 需要使用 new 操作符调用
- 将构造函数当作函数:任何函数只要通过 new 来调用,都可以作为构造函数
- 缺点:每个方法都要在每个实例上重新创建一遍(原型模式可以解决)
- 构造函数自定义
-
原型模式
- 理解原型对象
- prototype(原型)属性,指向函数的原型对象。原型对象会自动获得一个 constructor (构造函数)属性,其他方法继承自 Object。
- 对象的 prototype 指向原型对象,原型对象的 constructor 指向对象
- 判断实例是否由某对象创建
Person.prototype.isPrototypeOf(person1)
Object.getPrototypeOf(person1) == Person.prototype
// ES5 新增
- 当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,可同 delete 来恢复与同名原型属性的连接。可用 hasOwnProperty() 方法来判断实例中是否含有某属性,以此判断访问的是实例属性还是原型属性
- 原型与 in 操作符
"name" in person
// 当存在实例属性或原型属性时,都返回 true- for-in 可访问所有 Enumerable 为 true 的属性
- Object.keys(object) 枚举所有 Enumerable 为 true 的属性
- Object.getOwnPropertyNames(object) 枚举所有属性(包含 constructor 属性)
- 可以直接用对象字面量的方式对 prototype 进行赋值
若不特定 constructor 的值,则该属性会指向 Object;若特定值,则该属性会变成可枚举的 - 实例中的指针仅指向原型,而不指向构造函数
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,它们引用的仍然是最初的原型 - 所有原生的引用类型,也是采用原型模式创建的
- 缺点:对于引用类型的属性,所有实例只能共享同一个值
- 理解原型对象
-
组合使用构造函数模式和原型模式(使用最广泛!!)
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
-
动态原型模式
- 当方法不存在时,才添加到原型中
-
寄生构造函数模式(当可以使用其他模式时,不推荐使用)
- 基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象
- 当需要创建一个特殊的数组,又不能直接修改 Array 的构造函数时,可以使用这种模式
- 缺点:返回的对象与原型对象之间没有关系,不能依赖 instanceof 操作符来确定对象类型
-
稳妥构造函数模式
- 稳妥对象:没有公共属性(只有私有变量和方法),其方法也不引用 this
- 不使用 new 调用构造函数
- 适合在一些安全环境、防止数据被改动时使用
继承
由于函数没有签名,无法实现接口继承
,只支持实现继承
-
原型链(很少单独使用)
- 利用原型让一个引用类型继承另一个引用类型的属性和方法,本质是重写原型对象:
SubType.prototype = new SuperType()
- 所有引用类型默认继承 Object,也是通过原型链继承的
- 只要是原型链中出现过的原型,用 instanceof、isPrototypeOf 都会返回 true
- 定义方法的语句一定要放在替换原型后
- 在通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做就会重写原型链
- 缺点:
- 对于引用类型的属性,所有实例只能共享同一个值(即原型模式的缺点)
- 在创建子类型的实例时,不能向超类型的构造函数中传递参数
- 利用原型让一个引用类型继承另一个引用类型的属性和方法,本质是重写原型对象:
-
借用构造函数(很少单独使用)
- 在子类型构造函数的内部调用超类型构造函数
function SubType(){ SuperType.call(this); // apply()、bind() 也可以 }
- 优点:可以向超类型的构造函数中传递参数
- 缺点:无法函数复用(即构造函数模式的缺点)
-
组合继承(最常用!!)
- 结合原型链和借用构造函数
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name, age){ //继承属性 SuperType.call(this, name); // 第二次调用 SuperType() this.age = age; } //继承方法 SubType.prototype = new SuperType(); // 第一次调用 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); };
- 缺点:会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部
-
原型式继承
- 子类型构造函数中传入一个已有的对象,并创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例
- Object.create() 规范了这种继承方式:
var SubType = Object.create(SuperType);
- 本质上是对传入对象的浅拷贝,缺点与原型模式相同
-
寄生式继承
- 创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象
- 缺点:不能做到函数复用
-
寄生组合式继承
- 使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 } inheritPrototype(SubType, SuperType); //替换 SubType.prototype = new SuperType()
- 只调用了一次超类型构造函数,被认为是寄生组合式继承是引用类型最理想的继承范式