学习链接

JavaScript常见的六种继承方式

JavaScript深入之继承的多种方式和优缺点

继承方式

1.原型链继承

关键

  1. Child.prototype = new Parent();

完整

  1. function Parent() {
  2. this.name = 'kevin';
  3. }
  4. Parent.prototype.getName = function () {
  5. console.log(this.name);
  6. }
  7. function Child() {}
  8. Child.prototype = new Parent();
  9. const child1 = new Child();
  10. child1.getName(); // kevin

问题

  • 引用类型的属性被所有实例共享(this.arr = [1,2]
  • 创建 Child 实例时,无法向 Parent 传参

补充

  1. Child.constructor === Function
  2. Child.prototype.constructor === Parent
  3. child1.constructor === Parent

2.盗用构造函数继承

关键

  1. Parent.call(this);

完整

  1. function Parent() {
  2. this.names = ['kevin', 'daisy'];
  3. this.getName = function() {
  4. console.log(this.names);
  5. }
  6. }
  7. function Child() {
  8. Parent.call(this);
  9. }
  10. const child1 = new Child();
  11. child1.names.push('yayu');
  12. child1.getName(); // ["kevin", "daisy", "yayu"]
  13. const child2 = new Child();
  14. child2.getName(); // ["kevin", "daisy"]

优点

  • 避免了引用类型的属性被所有实例共享

  • 可以在 Child 中向 Parent 传参

缺点

  • 子类不能访问父类原型上定义的方法

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

3.原型链 + 盗用构造函数的组合继承

关键

  1. Child.prototype = new Parent();
  2. Parent.call(this);

完整

  1. function Parent() {
  2. this.names = ['kevin', 'daisy'];
  3. }
  4. Parent.prototype.getName = function() {
  5. console.log(this.names);
  6. }
  7. function Child() {
  8. Parent.call(this);
  9. }
  10. Child.prototype = new Parent();
  11. const child1 = new Child();
  12. child1.names.push('yayu');
  13. child1.getName(); // ["kevin", "daisy", "yayu"]
  14. const child2 = new Child();
  15. child2.getName(); // ["kevin", "daisy"]

优点

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

缺点

  • 调用了两次父类构造函数,生成了两份实例

4.原型式继承

关键

  1. function createObj(o) {
  2. function F() {}
  3. F.prototype = o;
  4. return new F();
  5. }
  6. // 或
  7. Object.create(o);

完整

  1. const person = {
  2. name: 'kevin',
  3. friends: ['daisy', 'kelly']
  4. }
  5. const person1 = createObj(person);
  6. const person2 = createObj(person);
  7. person1.name = 'person1'; // 定义在自身 覆盖了原型同名属性
  8. console.log(person2.name); // kevin
  9. person1.friends.push('taylor');
  10. console.log(person2.friends); // ["daisy", "kelly", "taylor"]

优点

  • 原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合

缺点

  • 包含引用类型的属性始终都会共享相应的值,这点跟原型链继承一样

5.寄生式继承

关键

  1. function createObj(o) {
  2. const clone = Object.create(o); // 共享了属性
  3. clone.sayName = function() {
  4. console.log(this.name);
  5. }
  6. return clone;
  7. }

完整

  1. const person = {
  2. name: "Nicholas",
  3. friends: ["Shelby", "Court", "Van"]
  4. };
  5. const anotherPerson = createObj(person);
  6. anotherPerson.sayName(); // "Nicholas"

优点

  • 适合主要关注对象,而不在乎类型和构造函数的场景

缺点

  • 跟借用构造函数模式一样,每次创建对象都会创建一遍方法

6.寄生组合式继承

关键

  1. function createObj(o) {
  2. function F() {}
  3. F.prototype = o;
  4. return new F();
  5. }
  6. // 或
  7. Object.create(o);
  1. function inheritPrototype(child, parent) {
  2. const prototype = createObj(parent.prototype);
  3. prototype.constructor = child;
  4. child.prototype = prototype;
  5. }

完整

  1. function Parent() {
  2. this.names = ['kevin', 'daisy'];
  3. // console.log('调用了 Parent 构造函数');
  4. }
  5. Parent.prototype.getName = function() {
  6. console.log(this.names);
  7. }
  8. function Child() {
  9. Parent.call(this);
  10. }
  11. // Child.prototype = new Parent(); // 这里调用了 Parent 构造函数
  12. // 对比
  13. // const F = function () {};
  14. // F.prototype = Parent.prototype; // 这里没调用 Parent 构造函数
  15. // Child.prototype = new F();
  16. inheritPrototype(Child, Parent);
  17. const child1 = new Child();
  18. child1.names.push('yayu');
  19. child1.getName(); // ["kevin", "daisy", "yayu"]
  20. const child2 = new Child();
  21. child2.getName(); // ["kevin", "daisy"]

优点

这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。

与此同时,原型链还能保持不变;因此,还能够正常使用 instanceofisPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

Class 继承

  1. class Person {
  2. //调用类的构造方法
  3. constructor(name, age) {
  4. this.name = name;
  5. this.age = age;
  6. }
  7. //定义一般的方法
  8. showName() {
  9. console.log("调用父类的方法")
  10. console.log(this.name, this.age);
  11. }
  12. }
  13. let p1 = new Person('kobe', 39);
  14. console.log(p1);
  15. //定义一个子类
  16. class Student extends Person {
  17. constructor(name, age, salary) {
  18. super(name, age); //通过super调用父类的构造方法
  19. this.salary = salary;
  20. }
  21. showName() { //在子类自身定义方法
  22. console.log("调用子类的方法");
  23. console.log(this.name, this.age, this.salary);
  24. }
  25. }
  26. let s1 = new Student('wade', 38, 1000000000);
  27. console.log(s1);
  28. s1.showName();

补充

阮一峰JS教程:多重继承

JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。

  1. Object.assign ( target, ...sources )

将所有可枚举属性的值从一个或多个源对象分配到目标对象,返回目标对象

  1. function M1() {
  2. this.hello = 'hello';
  3. }
  4. function M2() {
  5. this.world = 'world';
  6. }
  7. function S() {
  8. M1.call(this);
  9. M2.call(this);
  10. }
  11. // 继承 M1
  12. S.prototype = Object.create(M1.prototype);
  13. // 继承链上加入 M2
  14. Object.assign(S.prototype, M2.prototype);
  15. // 指定构造函数
  16. S.prototype.constructor = S;
  17. var s = new S();
  18. s.hello // 'hello'
  19. s.world // 'world'

上面代码中,子类 S 同时继承了父类 M1M2。这种模式又称为 Mixin(混入)。

注意:M2 是在继承链上,而非原型链