继承

原型链继承

function Parent () {
    this.name = 'Jack'
}
Parent.prototype.getName = function () {
    return this.name
}

function Child () {
    this.age = 20
}
Child.prototype = new Parent()
Child.prototype.getAge = function () {
    return this.age
}

var person = new Child()
console.log(person.getName()) // Jack

分析:看这句代码 Child.prototype = new Parent(),Child 的原型被换成了 Parent 的实例,目前 Child 的新原型不仅具有作为一个 Parent 的实例所拥有的全部属性和方法,而且内部还有一个指针,指向了 Parent 的原型。 调用 person.getName() 这个会经历三个步骤:1、搜索实例;2、搜索 Child.prototype;3、搜索 Parent.prototype,最后一步才找到结果。通过实现原型链,Child 的实例成功在 Parent 定义中找到了需要的方法。

问题:1、引用类型值的原型属性会被所有实例共享;2、在创建子类型的实例时,不能向父类型构造函数传递参数;

第一个问题举例如下:

function Parent () {
    this.fruit = ['apple', 'pear']
}
function Child () {}
Child.prototype = new Parent()

var one = new Child()
one.fruit.push('banner')
console.log(one.fruit) // apple, pear, banner

var two = new Child()
console.log(two.fruit) // apple, pear, banner

因此实践中很少单独使用原型链。

借用构造函数

function Parent (name) {
    this.fruit = ['apple', 'pear']
    this.name = name
}

function Child (name) {
    // 继承 Parent,同时还传递了参数
    Parent.call(this, name)

    // 实例属性
    this.age = 20
}

var person0 = new Child('Jack')
person0.fruit.push('banner')
console.log(person0.fruit) // ["apple", "pear", "banner"]
console.log(person0.name) // Jack

var person1 = new Child()
console.log(person1.fruit) // ["apple", "pear"]

优点:

  1. 子类型构造函数可以向父类型构造函数传递参数
  2. 引用类型值不会被实例共享

缺点:

  1. 每个方法都要在每个实例上重新创建一遍
  2. 方法都在构造函数中定义,因此函数复用无从谈起

因此借用构造函数也是很少单独使用。

组合继承

function Parent (name) {
    this.fruit = ['apple', 'pear']
    this.name = name
}
Parent.prototype.sayName = function () {
    console.log(this.name)
}

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

// 继承方法
Child.prototype = new Parent()
Child.prototype.constructor = Child // 修复子类实例 constructor 指向,不然会指向 Parent
Child.prototype.sayAge = function () {
    console.log(this.age)
}

var person1 = new Child('Jack', 20)
person1.fruit.push('banner')
console.log(person1.fruit) // ["apple", "pear", "banner"]
person1.sayName() // Jack
person1.sayAge() // 20

var person2 = new Child('Mike', 18)
person2.fruit.push('watermelon')
console.log(person2.fruit) // ["apple", "pear", "watermelon"]
person2.sayName() // Mike
person2.sayAge() // 18

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

原型式继承

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

var obj = {
    name: 'color',
    colors: ['black', 'blue']
}

var obj1 = objectCreate(obj)
obj1.name = 'fruit'
obj1.colors.push('red')

var obj2 = objectCreate(obj)
obj2.name = 'vehicle'
obj2.colors.push('yellow')

console.log(obj.name) // color
console.log(obj.colors) // ['black', 'blue', 'red', 'yellow']
console.log(obj1.name) // fruit
console.log(obj2.name) // vehicle

当 ES5 的 Object.create() 只传入第一个参数时,上面的 objectCreate() 方法就是它的实现原理。

缺点是包含引用类型值的属性始终都会共享相应的值。

寄生式继承

这种继承模式和原型式继承紧密联系。即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

// 原型式继承
function objectCreate (o) {
    function F () {}
    F.prototype = o
    return new F()
}

// 寄生式继承
function obj (orgObj) {
    var clone = objectCreate(orgObj)
    // var clone = Object.create(orgObj) ES5 api
    clone.sayHi = function () {
        console.log('Hi')
    }
    return clone
}

缺点:每次创建对象都要创建一遍方法,降低函数的复用效率。

寄生组合式继承

// 关键函数
function inheritPrototype (child, parent) {
    var prototype = objectCreate(parent.prototype)
    prototype.constructor = child
    child.prototype = prototype
}

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

function Parent (name) {
    this.name = name
    this.colors = ['black', 'blue']
}

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

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

inheritPrototype(Child, Parent)

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

var demo = new Child('Mike', 22)
demo.sayName() // Mike
demo.sayAge() // 22

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

结语

这篇总结主要参考《JavaScript高级程序设计》中的继承章节。