JS继承

113 阅读3分钟

原型链继承

将子类的原型设置为父类的实例

function SuperType() {
    this.color = ['red', 'blue', 'green'];
}
function SubType() {}

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

缺点:

  • 对继承过来的子类实例属性的修改也会反应到其他继承过来的子类实例上,实际上,共享了父类的属性
  • 子类型再实例化使不能给父类型的构造函数传参,我们无法再不影响所有对象实例的情况下把参数传进父类的构造函数,导致原型链基本不会被单独使用

构造函数继承

在子类构造函数中调用父类构造函数,因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象为上下文执行构造函数。

function SuperType() {
    this.colors = ['red', 'bule', 'green'];
}
function SubType() {
    // 继承 SuperType
    SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);  // "red,blue,green,black"

let instance2 = new SubType();
console.log(instance2.color);  // "red,blue,green"

优点:

  • 可以在子类构造函数中向父类构造函数传参。
function SuperType(name) {
    this.name = name;
}
function SubType() {
    // 传参
    SuperType.call(this, 'aaa');
    
    // 实例属性
    this.age = 26
}

let instance = new SubType();
console.log(instance.name); // 'aaa'

缺点

  • 必须在构造函数中定义方法,因此函数不能重用,此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。构造函数基本也不能单独使用。

组合继承

综合了原型链继承和构造函数继承,将两者的优点集中了起来。

  • 思路: 使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}

function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();

SubType.prototype.sayAge = function() {
    console.log(this.age);
}
let instance1 = new SuperType('aaa', 18);
instance1.colors.push('black');
console.log(instance1.colors); // "red, blue, green, black"
instance1.sayName(); // 'aaa'
instance1.sayAge();  // 18

let instance2 = new SuperType('bbb', 19);
console.log(instance2.colors); // 'red', 'blue', 'green'
instance2.sayName(); // 'bbb'
instance2.sayAge();  // 19
  • 组合继承弥补了原型链和盗用构造函数的不足,是javasript中使用最多的继承模式。而且组合继承也保留了instanceof操作符和isPrototyoeOf()方法识别合成对象的能力。

原型式继承(实例继承)

即使不自定义类型也可以通过原型实现对象之间的信息共享。

function object(o) {
    function F() {};
    f.prototype = o;
    return new F();
}
  • 这个object()函数会创建一个临时构造函数,将传入得到对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。本质上,object()是对传入的对象执行了一次浅复制。
let person = {
    name: 'bill',
    friends: ['aaa', 'bbb', 'ccc']
};

let anotherPerson = object(person);
anotherPerson.name = 'ddd';
anotherPerson.friends.push('ddd');

let anotherPerson1 = object(person);
anotherPerson1.name = 'eee';
anotherPerson1.friends.push('eee');
console.log(person.friends); // 'aaa', 'bbb', 'ccc', 'ddd', 'eee';
  • ES5增加了Object.create() 方法将原型式继承的概念规范化了。这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(可选)。在只有一个参数时,Object.create()和这里的object()方法效果相同。

寄生式继承

创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

function createAnother(original){

let clone = object(original); // 通过调用函数创建一个新对象

clone.sayHi = function() { // 以某种方式增强这个对象

console.log("hi");

};

return clone; // 返回这个对象

}

寄生式组合继承

寄生式组合继承也存在效率问题。最主要的问题是父类构造函数始终会被调用两次:

  1. 在创建子类原型时调用。
  2. 在子类构造函数中调用。
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

SuperType.prototype.sayName = function() {
    console.log(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() {
    console.log(this.age);
}