深入javaScript继承

93 阅读3分钟

一 :原型链继承

    this.name = 'parent1';
    this.play = [1, 2, 3]
  }
  function Child1() {
    this.type = 'child1';
  }
  Child1.prototype = new Parent1();
  console.log(new Child1());
​
​
var s1 = new Child1()
var s2 = new Child1()
s1.play.push(4)
console.log(s1.play, s2.play) // [1,2,3,4] [1,2,3,4]
​
​
但是有一个弊端就是,原型继承的对象只是一个引用,那么就是每个实例都可以修改,这就是使用原型链继承方式的一个缺点。
因为我们期望的是s2 = [1,2,3]
​
关于编程想法,为什么Child1.prototype = new Parent1()不放入Child1构造函数里面呢?
因为当我们new关键字作用的时候,通过 obj.__proto__ = Object.create(constructor.protype),这相当于拷贝对象,
那么下面执行这个constructor改变的原型已经意义不大了,因为指向的位置变了。
​

二 :借用构造函数继承( 借助 call/apply)

   this.name = 'parent2';
 }
​
 Parent2.prototype.getName = function () {
   return this.name;
 }
​
 function Child2(){
   Parent2.call(this);
   this.type = 'child2'
 }
​
 let child = new Child2();
 console.log(child);  // 没问题
 console.log(child.getName());  // 会报错
​

除了 Child2 的属性 type 之外,也继承了 Parent2 的属性 name。这样写的时候子类虽然能够拿到父类的属性值,解决了第一种继承方式的弊端,但问题是,只能继承父类的实例属性和方法,不能继承原型属性或者方法。 ​ 上面的两种继承方式各有优缺点,那么结合二者的优点,于是就产生了下面这种组合继承的方式。

三 :组合式继承

function Parent3 (age) {
  this.name = 'parent3';
  this.play = [1, 2, 3];
  this.age = age
}
​
Parent3.prototype.getName = function () {
  return this.name;
}
function Child3() {
  // 第二次调用 Parent3()
  Parent3.call(this,30);
  this.type = 'child3';
}
​
// 第一次调用 Parent3()
Child3.prototype = new Parent3(30);
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);  // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
​
虽然这种方式弥补了上述两种方式的一些缺陷,但有些问题仍然存在:

1.  子类仍旧无法传递动态参数给父类!
1.  父类的构造函数被调用了两次。

四 : 原型式继承

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。 这个方案很像原型链继承

function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数

另外,ES5中存在Object.create()的方法,能够代替上面的object方法。

五 : 寄生式继承

核心:在原型式继承的基础上,增强对象,返回构造函数

使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。

虽然其优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法。那么我们看一下代码是怎么实现

 let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };
​
  function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
      return this.friends;
    };
    return clone;
  }
​
  let person5 = clone(parent5);
​
  console.log(person5.getName());
  console.log(person5.getFriends());

六 : 寄生组合式继承

  function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
  }
   Parent6.prototype.getName = function () {
    return this.name;
  }
  function Child6() {
    Parent6.call(this);
    this.friends = 'child6';
  }
​
  function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
  }
​
  clone(Parent6, Child6);
  Child6.prototype.getFriends = function () {
    return this.friends;
  }
​
  let person6 = new Child6();
  console.log(person6);
  console.log(person6.getName());
  console.log(person6.getFriends());
​
// 它可以解决组合继承 父类被调用两次和在不同层级属性重复的问题。
  1. 子类继承了父类的属性和方法,同时,属性没有被创建在原型链上,因此多个子类不会共享同一个属性。
  2. 子类可以传递动态参数给父类!
  3. 父类的构造函数只执行了一次!