面试官:你知道有哪些对象继承的方式?

37 阅读4分钟

面试官:你知道有哪些对象继承的方式?

这里列举几种常用的对象继承的方式

1. 原型链继承

function Father(name) {  
    this.name = name;  
}  
  
Father.prototype.sayHello = function() {  
    console.log('Hello, I am ' + this.name);  
};  
  
function Son() {}  
Son.prototype = new Father('John'); // 继承自Father,并设置name属性  
  
var son = new Son();  
son.sayHello(); // 输出: Hello, I am John
  • 这种方式的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个类型的指针,而另一个类型又有一个指向自己原型的指针。这样另一层关系就被加入到了其中,形成了原型链。
  • 缺点是,原型链继承多个实例的引用属性指向相同,改变一个会影响另一个实例的属性;不能传递参数;继承单一。

2. 借用构造函数(经典继承)

function Father(name) {  
    this.name = name;  
    this.colors = ['red', 'blue', 'green'];  
}  
  
function Son(name, age) {  
    Father.call(this, name); // 调用Father构造函数,绑定Son的this  
    this.age = age;  
}  
  
var son = new Son('John', 10);  
console.log(son.name); // 输出: John  
console.log(son.age); // 输出: 10
  • 在子类型构造函数的内部调用超类型构造函数。通过使用call()apply()方法,可以在新创建的对象上执行构造函数。
  • 优点是,可以在子类型构造函数中向超类型构造函数传递参数。
  • 缺点是,方法都在构造函数中定义,因此函数复用就无从谈起。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

3. 组合继承(伪经典继承)

function Father(name) {  
    this.name = name;  
    this.colors = ['red', 'blue', 'green'];  
}  
  
Father.prototype.sayHello = function() {  
    console.log('Hello, I am ' + this.name);  
};  
  
function Son(name, age) {  
    Father.call(this, name); // 继承属性  
    this.age = age;  
}  
  
Son.prototype = new Father(); // 继承方法,但这里不需要传递name参数  
Son.prototype.constructor = Son; // 修复constructor  
  
var son = new Son('John', 10);  
son.sayHello(); // 输出: Hello, I am John
  • 有时称为伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

4. 原型式继承

function object(o) {  
    function F() {}  
    F.prototype = o;  
    return new F();  
}  
  
var person = {  
    name: 'John',  
    friends: ['Shelby', 'Court']  
};  
  
var anotherPerson = object(person);  
anotherPerson.name = 'Matt';  
anotherPerson.friends.push('Van');  
  
console.log(anotherPerson.friends); // 输出: ['Shelby', 'Court', 'Van']  
console.log(person.friends); // 输出: ['Shelby', 'Court', 'Van'] (注意这里也会改变)
  • 借助原型可以基于已有的对象创建新对象。同时还不必因此创建自定义的类型。在ES5中,引入了Object.create()方法规范了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。

5. 寄生式继承

function createAnother(original) {  
    var clone = object(original); // 调用上面的object函数  
    clone.sayHi = function() {  
        console.log('Hi from ' + clone.name);  
    };  
    return clone;  
}  
  
var person = {  
    name: 'John',  
    friends: ['Shelby', 'Court']  
};  
  
var anotherPerson = createAnother(person);  
anotherPerson.sayHi(); // 输出: Hi from John
  • 寄生式继承的思路与寄生构造函数和工厂模式类似,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像工厂一样返回对象。

6. 寄生组合式继承

function inheritPrototype(subType, superType) {  
    var prototype = Object.create(superType.prototype); // 创建对象,以新对象作为子类型的原型  
    prototype.constructor = subType; // 增强对象,为新的原型添加constructor属性  
    subType.prototype = prototype; // 指定对象,将新创建的对象(即子类型的原型)赋值给子类型的prototype属性  
}  
  
function Father(name) {  
    this.name = name;  
    this.colors = ['red', 'blue', 'green'];  
}  
  
Father.prototype.sayHello = function() {  
    console.log('Hello, I am ' + this.name);  
};  
  
function Son(name, age) {  
    Father.call(this, name); // 继承属性  
    this.age = age;  
}  
  
inheritPrototype(Son, Father); // 继承方法  
  
var son = new Son('John', 10);  
son.sayHello(); // 输出: Hello, I am John
  • 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。