一、原型对象链和 Object.prototype
JS 内建的继承方法被称为原型对象链(对象继承其原型对象,而原型对象继承它的原型对象,以此类推),又可称为对象继承。
当访问一个对象的属性时,JS引擎会执行一个搜索过程,如果在对象实例上发现该属性(自有属性),该属性值就会被使用,否则会搜索 [[Prototype]],如果仍然没有发现,则搜索该原型对象上的 [[Prototype]] 直到继承链末端。
上几章博客中用到的多个方法都是定义在 Object.prototype 上,因此可以被其他对象继承:
- hasOwnProperty() 检查是否存在一个给定名字的自有属性;
- propertyIsEnumerable() 检查一个自有属性是否可枚举;
- isPrototypeOf() 检查一个对象是否是另一个对象的原型对象
- valueOf() 返回一个对象的值表达
- toString() 返回一个对象的字符串表达
最好不要修改 Object.prototype !!!(在 Object.prototype 上添加的方法和属性会出现在所有对象上!并且以 Object.prototype.property = anything 方式添加的属性是可枚举的,即会出现在每一个对象的 for-in 循环中)。
二、对象继承
对象继承是最简单的继承类型。唯一需要做的就是制定哪个对象时新对象的 [[Prototype]] 。
对象字面形式会隐式制定 Object.prototype 为其 [[Prototype]] ,也可以用 Object.create() 方法显式制定。
Object.create() 方法接受两个参数,1.需要被设置为新对象的 [[Prototype]] 的对象(可以为 null,即没有原型对象链),第二个可选参数是一个属性描述对象,格式和 Object.defineProperties() 中使用的一样。
var book = {
title: "This is a book"
};
// 等同于下面代码
var book = Object.create(Object.Prototype, {
title: {
value: "This is a book",
configurable: true,
enumerable: true,
writable: true
}
})
三、构造函数继承
构造函数也是对象。当新建一个函数的时候,该函数的 prototype 属性被自动设置为一个新的继承自 Object.prototype 的泛用对象,该对象有一个自有属性 constructor。事实上,JavaScript 引擎做了下面的事情:
// 你定义一个函数
funcction Person(){ //do something }
// JS 在引擎中:
Person.prototype = Object.create(Object.prototype, {
constructor: {
value: Perosn, //指向自身
configurable: true,
enumerable: true,
writable: true
}
})
四、构造函数窃取
构造函数窃取的关键是 call() 和 apply() 方法。只需要在子类的构造函数中用 call() 或者 apply() 调用父类的构造函数,并将新创建的对象传进去即可。实际上,就是用自己的对象窃取父类的构造函数。
function Rectangle(length, width) {
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function() {
returen this.length * this.width;
}
function Square(size) {
//从 Rectangle 继承
Rectangle.call(this, size, size);
// 添加新的属性或覆盖原有属性
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square, //指向自身
configurable: true,
enumerable: true,
writable: true
}
})
Square.prototype.getArea = function() { //覆盖原有方法};
Square.prototype.toString = function() { //添加新方法};
这种分两步走的过程在需要完成自定义类型之间的继承时比较有用。
一般来说,需要修改 prototype 来继承方法并用构造函数窃取来设置属性。由于这种做法模仿了那些基于类的语言的类继承,通常被称作伪类继承。
五、访问父类方法
可以通过直接访问父类原型对象的方式访问父类的方法,当这么做时,必须以 call() 或 apply() 执行父类方法并传入一个子类的对象。