梳理一下 JS 的几种实现继承的方式

1. 原型链继承

我们从上一章知道,在 new 运算符操作的时候,会将实例原型(Child.prototype)指向构造函数的原型(Parent.prototype),这样 child1 就可以访问到构造函数原型中的属性,实现了继承。

缺点:

  1. 多个实例对引用类型的操作会被篡改。这个理解一下原型链就明白了,就不费篇幅写例子了。
  2. 在创建 Child 的实例时,不能向Parent传参。 ```javascript function Parent () { this.name = ‘name’; }

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

function Child () {}

Child.prototype = new Parent();

const child1 = new Child();

console.log(child1.getName()) // name

  1. 我们可以打印出来验证一下:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1105483/1626369740772-4145d492-c787-464e-9274-e65580370df0.png#clientId=uf1a2c660-eaaf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=505&id=u16ed148c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=505&originWidth=493&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33689&status=done&style=none&taskId=u89d2fdb6-b945-482a-8cc2-2f7f3c0c516&title=&width=493)
  2. <a name="IvuEn"></a>
  3. # 2. 借用构造函数
  4. 使用父类的构造函数来实现继承,等同于复制父类的实例给子类(不使用原型)。但在创建子类实例时,会调用 Perent 构造函数,所以 Child 的每个实例都会将 Parent 中的属性复制一份
  5. 优点:
  6. 1. 避免了引用类型的属性被所有实例共享
  7. 2. 可以在 Child 中向 Parent 传参
  8. 缺点:
  9. 1. 方法都在构造函数中定义,每次创建实例都会创建一遍方法,影响性能
  10. 2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  11. ```javascript
  12. function Parent (name) {
  13. this.name = name;
  14. }
  15. function Child (name) {
  16. // 继承于 Parent
  17. Parent.call(this, name);
  18. }
  19. var child1 = new Child("kevin");
  20. console.log(child1.name); // kevin
  21. var child2 = new Child("yayu");
  22. console.log(child2.name); // yayu

3. 组合继承

原型链继承和经典继承双剑合璧,用原型链实现对原型属性和方法的继承,用借用构造函数的方法来实现实例属性的继承。

优点:

  1. 融合原型链继承和构造函数的优点。

缺点:

  1. 在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。 ```javascript function Parent (name) { this.name = name; this.colors = [‘red’, ‘blue’, ‘green’]; }

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

function Child (name, age) { // 继承属性,第二次调用 Parent Parent.call(this, name); this.age = age; }

// 继承方法,构建原型链,第一次调用 Parent Child.prototype = new Parent(); // 重写 Child.prototype 的 constructor 属性,指向自己的构造函数 Child Child.prototype.constructor = Child;

var child1 = new Child(‘kevin’, ‘18’); child1.colors.push(‘black’); console.log(child1.name); // kevin console.log(child1.age); // 18 console.log(child1.colors); // [“red”, “blue”, “green”, “black”]

var child2 = new Child(‘daisy’, ‘20’); console.log(child2.name); // daisy console.log(child2.age); // 20 console.log(child2.colors); // [“red”, “blue”, “green”]

  1. 同样,我们可以把 Child 的实例打印出来看看:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1105483/1626395543569-838b6b16-a814-4afa-a108-c4bf222ef055.png#clientId=u1a8eaaff-804f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=236&id=ua90f1e17&margin=%5Bobject%20Object%5D&name=image.png&originHeight=236&originWidth=712&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27121&status=done&style=none&taskId=u1f87f2ac-1d19-4891-997c-8d0458beb96&title=&width=712)
  2. <a name="wBomx"></a>
  3. # 4. 原型式继承
  4. 利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
  5. 缺点:
  6. 1. 包含引用类型的属性值始终都会共享相应的值,跟原型链继承一样。
  7. 2. 无法传递参数
  8. ```javascript
  9. function createObj(o) {
  10. function F(){}
  11. F.prototype = o;
  12. return new F();
  13. }

Object.create() 对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。

  1. var person = {
  2. name: "Nicholas",
  3. friends: ["Shelby", "Court", "Van"]
  4. };
  5. var anotherPerson = Object.create(person);
  6. anotherPerson.name = "Greg";
  7. anotherPerson.friends.push("Rob");
  8. var yetAnotherPerson = object(person);
  9. yetAnotherPerson.name = "Linda";
  10. yetAnotherPerson.friends.push("Barbie");
  11. console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"

5. 寄生式继承

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

缺点(同原型式继承)

  1. function createObj (o) {
  2. var clone = Object.create(o);
  3. // 函数的主要作用是为构造函数新增属性和方法,以增强函数
  4. clone.sayName = function () {
  5. console.log('hi');
  6. }
  7. return clone;
  8. }
  9. var person = {
  10. name: "Nicholas",
  11. friends: ["Shelby", "Court", "Van"]
  12. };
  13. var p1 = createObj(person);
  14. p1.sayHi(); // "hi"

6. 寄生组合式继承

结合借用构造函数传递参数和寄生模式实现继承,这种方式的高效率体现它只调用了一次 SuperType 构造函数,并且因此避免了在 SuperType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

  1. function inheritPrototype(subType, superType){
  2. // 创建对象,创建父类原型的一个副本
  3. var prototype = Object.create(superType.prototype);
  4. // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  5. prototype.constructor = subType;
  6. // 指定对象,将新创建的对象赋值给子类的原型
  7. subType.prototype = prototype;
  8. }
  9. // 父类初始化实例属性和原型属性
  10. function SuperType(name){
  11. this.name = name;
  12. this.colors = ["red", "blue", "green"];
  13. }
  14. SuperType.prototype.sayName = function(){
  15. console.log(this.name);
  16. };
  17. // 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
  18. function SubType(name, age){
  19. SuperType.call(this, name);
  20. this.age = age;
  21. }
  22. // 将父类原型指向子类
  23. inheritPrototype(SubType, SuperType);
  24. // 新增子类原型属性
  25. SubType.prototype.sayAge = function(){
  26. console.log(this.age);
  27. }
  28. var instance1 = new SubType("xyc", 23);
  29. var instance2 = new SubType("lxy", 23);
  30. instance1.colors.push("2"); // ["red", "blue", "green", "2"]
  31. instance1.colors.push("3"); // ["red", "blue", "green", "3"]