JavaScript 实现继承的方式

349 阅读2分钟

一、原型链继承

核心:将父类的实例作为子类的原型,主要是利用了 Dog.prototype = new Animal() ,这样Dog.prototype.__proto__ 就指向了 Animal.prototype ,从而将子类 Dog 的原型和父类Animal 的原型相关联

function Animal() {
    this.name = 'animal'
    this.arr = []
    this.eat = function() {
        console.log('animal eat')
    }
}
Animal.prototype.age = 18

function Dog() {
    this.break = function() {
        console.log('dog bark');
    }
}
// Dog.prototype.__proto__ = Animal.prototype
// Dog.prototype 指向 Animal的实例
Dog.prototype = new Animal();

// h1.__proto__ = Dog.prototype
let h1 = new Dog();
console.log(h1.arr) // []
h1.arr.push(1)
let h2 = new Dog();
console.log(h2.arr) // [1]

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到

缺点:

来自父类引用类型的实例属性是所有实例共享的,即属性没有私有化,原型上属性的改变会作用到所有的实例上

二、构造函数继承

核心:在调用子类构造函数时内部使用 call 或 apply 来调用父类的构造函数,从而达到了使用父类的构造函数来增强子类实例的效果,等于是复制父类的实例属性给子类(没有用到原型)

function Super(){  
    this.flag = true;  
}  
function Sub(){  
    Super.call(this)  // 如果父类可以需要接收参数,这里也可以直接传递  
}  
var obj = new Sub();  

特点:

  1. 实现了属性的私有化
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象) 缺点:
  4. 实例并不是父类的实例,只是子类的实例
  5. 只能继承父类的实例属性和方法,不能继承原型属性或方法
  6. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

三、组合继承

核心:结合了构造函数继承和原型链继承

function Super(){  
    this.flag = true;  
}  
Super.prototype.getFlag = function(){  
    return this.flag;     // 继承方法  
}  
function Sub(){
    this.subFlag = flase  
    Super.call(this)    // 继承属性  
}  
Sub.prototype = new Super();  
var obj = new Sub();  
// Sub.prototype = new Super(); 会导致Sub.prototype的constructor指向Super;
// 然而constructor的定义是要指向原型属性对应的构造函数的,Sub.prototype是Sub构造函数的原型,
// 所以应该添加一句纠正:Sub.prototype.constructor = Sub;
Sub.prototype.constructor = Sub;  // 修复构造函数指向
Super.prototype.getSubFlag = function(){  
    return this.flag;  
}