JavaScript 系列之继承(一)

236 阅读3分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

一、原型链继承

利用原型链作为实现继承的主要方法,主要思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

function SuperType() {
  this.property = true;
}
SuperType.prototype.getSuperValue = function () {
  return this.property;
};

function SubType() {
  this.subproperty = false;
}

// 继承SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function () {
  return this.subproperty;
};
var instance = new SubType();

console.log(instance.getSuperValue()); // true

SubType 继承了 SuperType,继承是通过 SubType.prototype = new SuperType(); 实现的。实现的本质是重写原型对象,换成一个新类型(super)的实例。这样,原来存在于 SuperType 的实例中的属性和方法,也存在与 SubType.prototype 中了。然后给 SubType.prototype 添加一个方法,这样就继承了 SuperType 的属性和方法的基础上又添加了一个方法。

WechatIMG25037.jpeg

image.png

上面没有使用 SubType 默认提供的原型,而是给它换了一个新原型;这个新原型就是 SuperType 的实例。新原型内部还有一个指向 SuperType 的原型的指针。结果变成了 instance 指向 SubType 的原型,SubType 的原型又指向 SuperType 的原型。getSuperValue() 方法仍然还在 SuperType.prototype 中,property 则位于 SubType.prototype。这是因为 property 是一个实例属性,而 getSuperValue() 则是一个原型方法。既然 SubType.prototype 现在是 SuperType 的实例,那么 property 自然位于该实例(SubType.prototype)中。

注意:instance.constructor 现在指向的是 SuperType。这是因为原来的 SubType.prototype 中的 constructor 被重写了的缘故。还有当以读取模式访问一个属性时,首先在实例中搜索。如果没有找到。则会继续搜索实例的原型。通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。

1. 默认的原型:Object.prototype

image.png

SubType 继承了 SuperType,而 SuperType 继承了 Object。当调用 instance.toString() 时,实际上调用的是保存在 Object.prototype 中的那个方法。

2. 确定原型和实例的关系:

console.log(instance instanceof Object); //true
console.log(instance instanceof SuperType); //true
console.log(instance instanceof SubType); //true
console.log(Object.prototype.isPrototypeOf(instance)); //true
console.log(SuperType.prototype.isPrototypeOf(instance)); //true
console.log(SubType.prototype.isPrototypeOf(instance)); //true

3. 谨慎地定义方法:

function SuperType() {
  this.property = true;
}
SuperType.prototype.getSuperValue = function () {
  return this.property;
};

function SubType() {
  this.subproperty = false;
}
SuperType.prototype = new SuperType();
// 给原型添加方法的代码一定要放在替换原型的语句之后
SubType.prototype.getSubValue = function () {
  return this.subproperty;
};

// 覆盖超类中的方法
SubType.prototype.getSuperValue = function () {
  return false;
};
var instance = new SubType();
console.log(instance.getSuperValue()); // false

在通过原型链实现继承时,不能使用对象字面量创建原型方法,这样会重写原型链。

function SuperType() {
  this.property = true;
}
SuperType.prototype.getSuperValue = function () {
  return this.property;
};
function SubType() {
  this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();

// 使用字面量添加新方法,导致上一行代码无效
SubType.prototype = {
  getSubValue: function () {
    return this.subproperty;
  },
  someOtherMethod: function () {
    return false;
  }
};

var instance = new SubType();
console.log(instance.getSuperValue()); // error

现在的原型包含一个 Object 的实例,而非 SuperType 的实例,SubTypeSuperType 之间已经没有关系了。

4. 原型链的问题

因此很少单独使用原型链实现继承

问题一:包含引用类型的原型属性会被所有实例共享

function SuperType() {
  this.colors = ["red", "blue", "green"];
}
function SubType() {}
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red", "blue", "green", "black"

var instance2 = new SubType();
console.log(instance2.colors); // "red", "blue", "green", "black"

SuperType 构造函数中定义了一个 colors 属性,该属性包含一个数组,SuperType 的每个实例都会有各自包含自己数组的 colors 属性。

SubType 通过原型链继承了 SuperType 之后,SubType.prototype 就变成了 SuperType 的一个实例,所以它也拥有了一个它自己的 colors 属性。

但是,SubType 的所有实例都会共享这一个 colors 属性(因为该属性是是引用类型的,占用同一块内存)。

问题二:创建子类型时,不能向超类型的构造函数中传递参数