继承的那些事

481 阅读4分钟

前言

最近在学习继承的一些东西,写这篇文章也是为了让自己的学习更加贯彻一下吧!
学习过程是通过 死磕36JS手写题这篇文章来学习的,所以有些代码可能应用了上面的,感兴趣的可以去看一下那篇文章!

原型链继承

先来看一下代码吧!

function Animal() {
    this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
    return this.colors
}
function Dog() {}
Dog.prototype =  new Animal()

let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors)  // ['black', 'white', 'brown']

我知道光看这代码可能有点蒙,所以根据自己的理解我画了一下里面的“恩爱情仇”,如有不足的地方希望大家帮我指出

20070879B301C7DAC1EBD13B0DA92B52.jpg 我就就着图给大家简单的解释一下吧!

  1. Animal的原型Animal.prototype的getColor属性等于function()函数,就相当于Animal的原型获得了返回colors的能力
  2. Animal的实例化对象等于Dog.prototype,相当于Dog的原型继承了Animal的属性
  3. Dog的实例化对象dog1在colors数组了新加入了一个颜色brown,所以Dog里面也有了brown属性
  4. Dog再一次实例化了一个对象dog2,所以dog2里面的colors数组有了三种颜色。
  5. 原型链继承存在的问题:

问题1:原型中包含的引用类型属性将被所有实例共享;
问题2:子类在实例化的时候不能给父类构造函数传参;


借用call

老规矩,先看代码!

  function Parent1(){
    this.name = 'parent1';
  }
  function Child1(){
    Parent1.call(this);
    this.type = 'child1'
  }
  console.log(new Child1); //parent1,child1

什么是call()?
call() 方法是预定义的 JavaScript 方法。

它可以用来调用所有者对象作为参数的方法。

通过 call(),您能够使用属于另一个对象的方法。

所以这就是为什么call可以实现继承的原因。
但是还有个问题:这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类原型对象中一旦存在方法那么子类无法继承

组合继承

组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过call继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

先看代码!

function Animal(name) {
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
    return this.name
}
function Dog(name, age) {
    Animal.call(this, name)
    this.age = age
}
Dog.prototype =  new Animal()
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2) 
// { name: "哈赤", colors: ["black", "white"], age: 1 }
  1. Dog函数里 Animal.call(this,name)继承Animal()函数的name属性
  2. Dog.prototype = new Animal()让Dog继承了Animal的colors属性以及getName方法
  3. Dog.prototype.constructor = Dog 让Dog.prototype的构造函数是Dog。
  4. 这样就实现了继承

寄生式组合继承

组合继承虽然解决了问题,已经算是比较完善了,但是它也产生了新的问题,就是调用了2次父类构造函数,第一次是new Animal(),第二次是在Animal.call()这里,所以我们可以再优化一下。 解决办法就是我们不让父构造函数给子类原型赋值,而是创建一个第三方,让第三方函数获取父类原型的副本

  function Parent5 () {
    this.name = 'parent5';
    this.play = [1, 2, 3];
  }
  function Child5() {
    Parent5.call(this);
    this.type = 'child5';
  }
  Child5.prototype = Object.create(Parent5.prototype);
  Child5.prototype.constructor = Child5;

这种方法可以说是最完美的啦(撒花),所有的问题都被完美解决了
在这里我们小小的解释一下
Child5.prototype = Object.create(Parent5.prototype);
这句代码的意思就是:基于Parent5.prototype创建一个新的原型(此原型会有Parent5原型的所有属性)赋值给Child5.prototype。
在这里,我们就成功的把两次调用父类构造函数变成了一次。

class 实现继承

先上代码!

class Animal {
    constructor(name) {
        this.name = name
    } 
    getName() {
        return this.name
    }
}
class Dog extends Animal {
    constructor(name, age) {
        super(name)
        this.age = age
    }
}

class继承其实和原型链继承没啥区别,但是它可比原型链要香一点(在我看来)!
香在哪里呢?

  1. 极大地简化了原型链代码。 看见了上面原型链继承有时候是不是有点绝望啊?我也是,但是没关系,我们有class,它大大的简化了原型链代码,是不是有点拨云见日的感觉啊,哈哈!
  2. class应用起来也是非常方便的,它在定义对象和方法的时候就可以用extends来实现继承。(香香)

总结

老师告诉我,学习必须要想苦行僧般的徒步旅行,每一步要走踏实,每一次都不能停下!加油,自己。