| 
 图解JS中的六种继承:原型链、盗用构造函数、组合继承、原型式继承、寄生继承、组合寄生继承继承继承有两种:接口继承(Interface Inheritance)和实现继承(Implementation Inheritance)。接口继承需要有方法签名,JS由于没有方法签名,因此接下来我们讲解的六种继承都是属于实现继承。 继承本质上就是一个对象复用另一个对象的属性和方法,注意,属性和方法的复用效果是不同的,从这点出发去理解继承,也就能理解为什么会产生六种继承。 原型链 Prototype Chaning首先说一下原型链: function Animal(){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.say = function(index){
        console.log(`I am ${this.names[index]}`)
    }
}
var oneAnimal = new Animal()
oneAnimal.say(1) 
 
 每一个方法都有原型,我们可以在原型链挂载属性或者方法,原型在方法和通过new方法产生的实例之间分享: function Animal(){
}
Animal.prototype.names = ['animal', 'zoom', 'beast', 'creature']
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}
var oneAnimal = new Animal()
oneAnimal.say(1) 
 
 原型链最大的好处是实例间可以复用: var anotherAnimal = new Animal()
anotherAnimal.say(2)
 
 接着说一下原型链是怎么继承的: function Animal(){
}
Animal.prototype.names = ['animal', 'zoom', 'beast', 'creature']
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}
function Cat(){
    this.whiskers = 8
}
Cat.prototype = new Animal()
var kitten = new Cat()
kitten.say(2) 
 
 原型链有两个大问题,一个是引用值在实例之间互用: var kitten = new Cat()
var oldCat = new Cat()
kitten.names.push('kitten')
oldCat.say(4) 
 第二个问题是,在创建实例的时候,不能传参给父类构造器。 盗用构造器 Constructor Stealing为了解决引用值的问题和无法传参给父类的情况,开发者开始使用称为盗用构造器的方法,也成为对象伪装或者经典继承(masquerading or classic inheritance)。 function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}
function Cat(myName){
    Animal.apply(this, [myName])
}
Cat.prototype = new Animal()
var kitten = new Cat('kitten')
kitten.say(4) 
 
 盗用构造器是一种很有趣的比喻,它是说,把父类的构造器当成自己的构造器来使用,最重要的目的是做一份属性的副本,也就是拷贝引用值,这样就不用担心实例之间互相修改引用值。同时,在引用了父类构造器的时候传参,也解决了创建实例不能传参给父类的问题。 var kitten = new Cat('kitten')
var oldCat = new Cat('oldCat')
kitten.say(4) 
oldCat.say(4) 
 组合继承 Combination Inheritance盗用构造器中,当我们继承父类之后,如果要添加方法(or属性)或者重写方法的话,我们自然而然会想到: function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}
function Cat(myName, age){
    Animal.apply(this, [myName])
    
    this.age = age                                                 
    
}
Cat.prototype = new Animal()
Cat.prototype.sayAge = function(){                                  
    console.log(`My age = ${this.age}`)                             
}                                                                   
var kitten = new Cat('kitten', 3)
kitten.sayAge()
 没错,这个就是组合继承,也称为伪经典继承(pseudoclassical inheritance),基本思想是盗用构造器+添加属性+原型链附着方法。 
 组合继承比盗用构造器更加合理的解决了引用值、传参、添加属性/方法的问题,但实际上,组合继承还不够完美,它还需要两次new Animal()父类对象。 原型式继承 Prototypal Inheritance在之前的例子中,我们需要先自定义一个Animal父类,然后再定义Cat子类去继承它,原型式继承的作用是跳过第一步,使用一个现成的类直接继承。 function create(fatherInstance){
    function Son(){}
    Son.prototype = fatherInstance
    return new Son()
}
 
 这个就是原型式继承,Object.create()就是使用这种方式: 
  
 In 2006, Douglas Crockford wrote an article, “Prototypal Inheritance in JavaScript”. 寄生继承 Parasitic Inheritancefunction parasiticCreate(fatherInstance){
    var sonInstance = create(fatherInstance)
    sonInstance.sayHi = function(){
        console.log(`Hi`)
    }
    return sonInstance
}
 
 寄生继承核心实现是完成继承+给实例添加方法。 寄生组合继承 Parasitic Combination Inheritance基础寄生组合继承: function parasiticCombination(Father, Son){
    var middle = create(Father.prototype)
    middle.contructor = Son
    Son.prototype = middle
}
 寄生组合继承:盗用构造器继承属性+寄生继承来创建一个新对象(作为子类对象的新原型) function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}
function Cat(myName, age){
    Animal.apply(this, [myName])
    this.age = age
}
parasiticCombination(Animal, Cat)
Cat.prototype.sayAge = function(){
    console.log(`My age is ${this.age}`)
}
var kitten = new Cat('kitten', 3)
var oldCat = new Cat('oldCat', 9)
kitten.say(4)
oldCat.say(4)
 
 对比一下组合继承,寄生组合继承中不再去new一个Animal实例作为Cat的原型,而是通过寄生继承产生一个新对象替代,因此少了一次调用父类构造器,减少了不必要的属性。 寄生组合继承是目前ES6类的实现方法,它被认为是性能最好的继承方案。 实际实现: function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}
function Cat(myName, age){
    Animal.apply(this, [myName])
    this.age = age
}
var middle = Object.create(Animal.prototype)
Cat.prototype = middle
Cat.prototype.sayAge = function(){
    console.log(`My age is ${this.age}`)
}
var kitten = new Cat('kitten', 3)
var oldCat = new Cat('oldCat', 9)
kitten.say(4)
oldCat.say(4)
 转换后: class Animal{
    constructor(myName){
        this.names = ['animal', 'zoom', 'beast', 'creature']
        this.names.push(myName)
    }
    say(index){
        console.log(`I am ${this.names[index]}`)
    }
}
class Cat extends Animal{
    constructor(myName, age){
        super(myName) 
        this.age = age
    }
    sayAge(){
        console.log(`My age is ${this.age}`)
    }
}
var kitten = new Cat('kitten', 3)
var oldCat = new Cat('oldCat', 9)
kitten.say(4)
kitten.sayAge()
oldCat.say(4)
oldCat.sayAge()
 所以为什么说ES6继承是原型链的语法糖,因为背后封装了好几道复杂的工艺。 总结
 最后最后,期待大家能够关注个人公粽号:YopenTech主要引进国外前沿技术文章、博客,学习国外先进技术,以此师夷长技以制夷,争做一个高质量技术公众号,而不是广告混杂、哗众取宠、猎奇标题、质量低下。
 |