【面试宝典】JavaScript继承

173 阅读3分钟

更像是笔记,本文介绍多种继承方式的优缺点,推荐《JavaScript高级程序设计》

原型链继承

// 父类构造函数
function Parent () {
  this.name = 'parent';
}

Parent.prototype.sayName = function () {
  console.log(this.name);
};

function Child () {}
// 原型链继承
Child.prototype = new Parent();
const pan = new Child();
pan.sayName(); // 'parent'

原型链继承主要有两个问题:

  • 原型链属性被所有实例共享,如果是引用类型属性,一个实例修改该属性后,所有实例上获取到的都是修改后的属性
  • 创建子类Child对应的实例时,无法向Parent传参
// 父类构造函数
function Parent () {
  this.name = 'parent';
  this.wealth = ['car', 'money'];
}

Parent.prototype.sayName = function () {
  console.log(this.name);
};

function Child () {}
// 原型链继承
Child.prototype = new Parent();

const pan = new Child();
console.log(pan.wealth); // ['car', 'money']
// 修改原型链属性
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']

const duan = new Child();
console.log(duan.wealth); // ['car', 'money', 'house']

借用构造函数

// 父类构造函数
function Parent (name) {
  this.name = name;
  this.wealth = ['car', 'money'];
}

function Child (name) {
  Parent.call(this, name);
}

const pan = new Child('panpan');
console.log(pan.name); // 'panpan'
console.log(pan.wealth); // ['car', 'money']
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']

const duan = new Child('duanduan');
console.log(duan.name); // 'duanduan'
console.log(duan.wealth); // ['car', 'money']

借用构造函数实现继承优点如下:

  • 可以在创建子类实例时向Parent传参
  • 避免了引用类型属性被所有子类实例共享 缺点:
  • 方法都在构造函数中定义,每次创建实例都会创建一遍方法

组合继承

原型链继承和构造函数继承的组合版

// 父类构造函数
function Parent (name) {
  this.name = name;
  this.wealth = ['car', 'money'];
}

Parent.prototype.sayName = function () {
  console.log(this.name);
};

function Child (name) {
  Parent.call(this, name);
}
Child.prototype = new Parent();
// 修正constructor
Child.prototype.constructor = Child;

const pan = new Child('panpan');
console.log(pan.name); // 'panpan'
console.log(pan.wealth); // ['car', 'money']
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']

const duan = new Child('duanduan');
console.log(duan.name); // 'duanduan'
console.log(duan.wealth); // ['car', 'money']

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。

原型式继承

function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

实际就是Object.create()的模拟实现

const person = {
  name: 'panent',
  wealth: ['car', 'money']
};

function createObj (o) {
  function F () {}
  F.prototype = o;
  return new F();
}

const pan = createObj(person);
console.log(pan.name); // 'panent'
pan.name = 'pan';
console.log(pan.name); // 'pan'
console.log(pan.wealth); // ['car', 'money']
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']

const duan = createObj(person);
console.log(duan.name); // 'panent'
console.log(duan.wealth); // ['car', 'money', 'house']

缺点:

  • 引用类型的值还是会被所有实例共享

寄生式继承

function createObj (o) {
    var clone = object.create(o);
    clone.sayName = function () {
        console.log('hello');
    }
    return clone;
}

缺点:

  • 跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

寄生组合式继承

组合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。下面来看一下寄生组合式继承

function Person(name){
    this.category = 'human';
    this.legNum = 2;
    this.name = name;
}

Person.prototype.sayHello = function(){
    console.log('Hi,i am ' + this.name);
}

// 定义集成方法
function inherit(subType,superType){
    //在new inheritFn 的时候将构造函数指向子类
    function inheritFn(){this.constructor = subType}
    inheritFn.prototype = superType.prototype;
    //将子类的原型指向父类原型的一个副本
    subType.prototype = new inheritFn();
}

//定义子类构造函数Pan
function Pan(name,age){
    Person.call(this,name);  //借用构造函数
    this.age = age;
}

//将子类Pan的原型指向父类Person原型的一个副本
//注意:要执行该动作后才能在Pan的prototype上定义方法,否则没用
inherit(Pan,Person); 

Pan.prototype.sayAge = function(){
    console.log(this.age);
}

//定义子类构造函数Duan
function Duan(name,hairColor){
    Person.call(this,name);
    this.hairColor = hairColor;
}

inherit(Duan,Person);

Duan.prototype.showHairColor = function(){
    console.log(this.hairColor);
}

//Pan的实例
var pan = new Pan('panfengshan',27);
console.log(pan.name); //panfengshan
console.log(pan.age); //27
console.log(pan.category); //human
console.log(pan.legNum); //2

pan.sayHello(); //Hi,i am panfengshan
pan.sayAge(); //27

//Duan的实例
var duan = new Duan('duanyanan','black');
console.log(duan.name); //duanyanan
console.log(duan.hairColor); //black
console.log(pan.category); //human
console.log(pan.legNum); //2

duan.sayHello(); //Hi,i am duanyanan
duan.showHairColor(); //black

寄生组合式继承的高效体现在它只调用了一次Person构造函数,并且因此避免了在Pan.prototype上面创建不必要的、多余属性。与此同时,原型链还能保持不变;因此,还能正常使用instanceofisPropertyOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。