3

1.前言

本文完整代码指路我的GitHub,欢迎star。js中的继承方式有很多种,以下仅列出部分。

2.原型链继承

代码如下:

function Parent() {
    this.name = 'jchermy';
}

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

function Child() {

}

Child.prototype = new Parent();

var child1 = new Child();
console.log(child1.getName()); //jchermy

这样看来貌似可以完美完成继承,然后当属性换成引用类型时,就会出现问题了,如下:

function Parent() {
    this.names = ["aa", "bb"]; //引用类型值
}

function Child() {

}

Child.prototype = new Parent();

var child1 = new Child();
child1.names.push("cc");
console.log(child1.names); //["aa","bb","cc"]

var child2 = new Child();
console.log(child2.names); //["aa","bb","cc"]

child2.names.push("dd");
console.log(child1.names) //["aa", "bb", "cc", "dd"]
console.log(child2.names);//["aa", "bb", "cc", "dd"]

var p = new Parent();
console.log(p.names); //["aa", "bb"]

由此我们可以得出原型链继承的缺点:

  1. 引用类型的属性被所有实例共享
  2. 在创建Child实例时,不能向Parent传参

2.借用构造函数继承

function Parent() {
    this.names = ["aa", "bb"];
}

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

var child1 = new Child();
child1.names.push("cc");
console.log(child1.names);//["aa", "bb", "cc"]

var child2 = new Child();
console.log(child2.names);//["aa", "bb"]

child2.names.push("dd");
console.log(child1.names); //["aa", "bb", "cc"]
console.log(child2.names); //["aa", "bb", "dd"]

var p = new Parent();
p.names; //["aa", "bb"]

可以看出,借用构造函数继承避免了一下原型链继承的问题,主要体现在:

  1. 避免了引用类型的属性被所有实例共享
  2. 可以在Child中向Parent传参

然而,借用构造函数继承也有缺点。

function Parent(name) {
    this.name = "parent "+name;
}

function Child(name) {
    this.name = "child"+name;
    Parent.call(this, name);
}

var child1 = new Child('hemin');
console.log(chil1.name); //"parent hemin"

var child2 = new Child("aa");
console.log(child2.name); //"parent aa"

缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法

3.组合继承

组合继承融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式

function Parent(name) {
    this.name = name;
    this.colors = ["red", "blue"];
}

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

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

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child("aa", 18);
child1.colors.push("black");

child1.name; //"aa"
child1.age; //18
child1.colors; //["red", "blue","black"]

var child2 = new Child("bb", 20);
child2.name; //"bb"
child2.age; //20
child2.colors; //["red", "blue"]

然而组合继承也有一个缺点,就是会调用两次父构造函数。

如下:

Child.prototype = new Parent(); 
var child1 = new Child('aa', '18');

所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为['red', 'blue']。
图片描述

这个问题我们在下面再讨论。

4.原型式继承

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

var person = {
    name: 'jchermy',
    friends: ["aa", "bb"]
}

var person1 = createObj(person);
var person2 = createObj(person);

//注意:修改person1.name的值,person2.name的值并未发生改变,
//并不是因为person1和person2有独立的 name 值,而是因为person1.name = 'person1',给person1添加了 name 值,并非修改了原型上的 name 值。
person1.name = "xiaomi";
console.log(person2.name); //"jchermy"

person2.friends.push("cc");
console.log(person1.friends); //["aa", "bb", "cc"]

缺点:包含引用类型的属性值始终会共享相应的值,与原型链继承一样

5.寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式做增强对象,最后返回对象

 function createObj(o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log("hi");
      }
      return clone;
}

var person = {
    name: "jchermy",
    friends: ["aa", "bb"]
};

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = "xiaomi";
console.log(person1.name); //"xiaomi"
console.log(person2.name); //"jchermy"

person1.friends.push("xxx");
console.log(person1.friends); // ["aa", "bb", "xxx"]
console.log(person2.friends); // ["aa", "bb", "xxx"]

缺点:

  1. 跟借用构造函数模式一样,每次创建对象都会创建一遍方法
  2. 包含引用类型的属性值始终会共享相应的值

6.寄生组合式继承

还记得组合继承中提到的那些问题吗,那么我们该如何精益求精,避免这一次重复调用呢?

如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?可以这样实现:

function Parent(name) {
   this.name = name;
   this.colors = ["red", "blue", "green"];
}

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

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

//关键的三步
  var F = function(){};

  F.prototype = Parent.prototype;

  Child.prototype = new F();


  
  Child.prototype.constructor = Child;

  var child1 = new Child('xiaomi', 18);
  var child2 = new Child2('aa', 24);
  console.log(child1.name); //xiaomi
  console.log(child2.name); //aa

  child1.colors.push("black");
  child1.colors; //["red", "blue", "green", "black"]
  child2.colors; //["red", "blue", "green"];
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

7.结语

如果文章对你有帮助的话,欢迎点赞收藏!!有错误和不合理的地方欢迎大家指正!GitHub给个star就最好啦!=(//▽//)感谢大家~


Jchermy
857 声望132 粉丝

Keep learning