javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS实现类继承

JavaScript实现类继承的方法最全讲解

作者:鹧鸪yy

JavaScript 类继承通过extends和super关键字提供了一种直观的面向对象编程方式,基于原型链实现,这篇文章主要介绍了JavaScript实现类继承的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

先贴一张图:

1、原型链继承

原型链继承的核心思想是让子类的 prototype 指向父类的实例,这样子类就能访问父类的方法和属性。

缺点:

  1. 子类实例共享父类的引用属性:修改 child1.colors 之后,child2.colors 也会受到影响,因为 colors 是父类实例的属性,所有子类实例都共享同一个对象。
  2. 无法向父类构造函数传参:创建子类实例时,无法向 Parent 传递参数。

例子:

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

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

function Child() {}

Child.prototype = new Parent(); // 关键点:让子类的 prototype 指向父类实例

const child1 = new Child();
console.log(child1.getName()); // Parent
console.log(child1.colors); // ["red", "blue", "green"]

child1.colors.push("yellow");

const child2 = new Child();
console.log(child2.colors); // ["red", "blue", "green", "yellow"] (被修改了)

2、构造函数

通过在子类的构造函数中调用父类构造函数,并使用 call()apply() 绑定 this,从而避免原型链继承的问题。

缺点:

  1. 子类无法继承父类在原型链上的属性和方法。
  2. 每个实例都拷贝一份,占用内存大,尤其是方法过多的时候。(函数复用又无从谈起了,本来我们用 prototype 就是解决复用问题的)

优点:解决了通过原型链继承子类对于父类引用类型属性的修改,导致其他子类实列共享了修改的问题

例子:

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

function Child(name) {
  Parent.call(this, name); // 关键点:在子类构造函数中调用父类构造函数
}

const child1 = new Child("Child1");
const child2 = new Child("Child2");

child1.colors.push("yellow");

console.log(child1.colors); // ["red", "blue", "green", "yellow"]
console.log(child2.colors); // ["red", "blue", "green"] (不会被修改)

3、组合继承

组合继承(原型链继承+借用构造函数继承)由于这两种继承方式都存在各自的优缺点,从而将他们优点结合起来,通过原型继承父类原型上的属性和方法,通过构造函数的方法继承父类的属性

缺点:组合继承是js最常用的继承模式,组合继承最大的问题就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型时,另一次是在子类构造函数内部。

优点:就是可以把方法定义在原型上复用,每个实例又有自己的属性。组合继承弥补了原型链和盗用构造函数的不足,是js中使用最多的继承模式。

例子:

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

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

function Child(name, age) {
  Parent.call(this, name); // 调用父类构造函数,继承实例属性
  this.age = age;
}

Child.prototype = new Parent(); // 继承父类方法
Child.prototype.constructor = Child; // 修正 constructor 指向

const child1 = new Child("Child1", 18);
console.log(child1.getName()); // Child1
console.log(child1.colors); // ["red", "blue", "green"]

解决的问题:

  1. 避免了引用属性共享问题
  2. 支持传参
  3. 方法可以复用

存在的问题:

4、寄生组合继承

组合继承存在这一定的效率问题,它的父类构造函数始终会被调用俩次,一次在创建字类原型时调用,另一次在子类构造函数中调用。本质上子类只需要在执行时重写自己的原型就行了。寄生式组合继承可以算是引用类型继承的最佳模式。

寄生组合继承优化了组合继承中的 new Parent(),避免了父类构造函数的重复调用。

优点:

  1. 避免引用属性共享问题
  2. 支持传参
  3. 方法可以复用
  4. 避免了 new Parent() 造成的二次调用

其中拓展一下,Object.create(Parent.prototype) 的原理

Object.create() 会创建一个新对象,并将其 __proto__ 指向参数对象:

Child 的实例能通过原型链访问 Parent.prototype 的方法(如 getName)。

但不会在 Child.prototype 上生成多余的父类属性(如 colors)。

Child.prototype = Object.create(Parent.prototype);
// 相当于:
Child.prototype = {};
Child.prototype.__proto__ = Parent.prototype;

例子:

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

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

function Child(name, age) {
  Parent.call(this, name); // 继承属性
  this.age = age;
}
// 实现原型方法继承。只继承原型方法,不调用构造函数
Child.prototype = Object.create(Parent.prototype); // 继承方法,但不会执行 Parent 构造函数
Child.prototype.constructor = Child; // 修正 constructor 指向

const child1 = new Child("Child1", 18);
console.log(child1.getName()); // Child1
console.log(child1.colors); // ["red", "blue", "green"]

5、ES6中class+extends 的继承

ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

优点:

  1. 语法简洁,易读易写
  2. 继承逻辑清晰
  3. super()​​​​​​​ 关键字提供更清晰的继承机制

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

关键点

  • super(name) 等价于 Parent.call(this, name)(继承实例属性)。
  • extends 通过 Object.create() 实现原型方法的继承。

例子:

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

  getName() {
    return this.name;
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类构造函数,初始化子类的实例属性(相当于 Parent.call(this))
    this.age = age;
  }
}

const child1 = new Child("Child1", 18);
console.log(child1.getName()); // Child1
console.log(child1.colors); // ["red", "blue", "green"]

6、原型式继承(额外拓展一个)

原型式继承原型链继承构造函数继承 的折中方案,直接基于对象创建新对象。

例子:

// Object.create()底层实现原理 (ES6的新特性)
      Object.create =  function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
      };

      let person = {
        name:'萨哈',
        things:[]
      }


      let a = Object.create(person)
      a.name = '小红'
      a.things.push('笔')
      console.log('a',a)

      let b = Object.create(person,{
        //定义该原型对象里每个变量的属性
        name:{
          //该变量的值
          value:'小黄',
          //该变量是否可写
          writable: false,
          //该变量是否能遍历
          enumerable:true,
          //是否允许外部操作修改这个变量的其它属性值(value属性除外)
          configurable:true
        }
      })
      b.name = '我要修改'
      console.log('b',b)

总结 

到此这篇关于JavaScript实现类继承方法的文章就介绍到这了,更多相关JS实现类继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文