JavaScript 之 实现继承的几种方式

文章目录

    • 前言:
    • 导图
    • 一、ES5及其之前的继承办法
      • 1、原型链方式继承
      • 2、借用构造函数方式继承
      • 3、组合式继承
      • 4、寄生组合式继承
    • 二、ES6的继承方式
      • class方式

前言:

本文总结了JS常见的实现继承的方式,例子与一些标注都是自己的思考,欢迎各位交流,结构上主要参考了这个文章1

导图

JavaScript 之 实现继承的几种方式_第1张图片

一、ES5及其之前的继承办法

1、原型链方式继承

先来看看原型链继承方式吧,代码如下:

 function Person() {
  this.nation = ["china"];
} // 内部属性

Person.prototype.say = function () {
  let res = this.nation;
  res = `I have been ${res}`;
  return res;
}; // 原型对象上的属性方法

function Chinese() {}
Chinese.prototype = new Person(); // 这里通过prototype继承

p1 = new Chinese();
p1.nation.push("japan");
p2 = new Chinese();
p2.nation = "korea"; // 这里是相当于新申请了一个空间,并赋值给p2.nation
p3 = new Chinese();
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2); 
console.log("p3", p3); // 注意这里p3 与 p1 公用了nation

// 所以这种方式存在的两种问题
// 问题1:原型中包含的引用类型属性将被所有实例共享;
// 问题2:子类在实例化的时候不能给父类构造函数传参;

我们来看一下运行结果,如下图所示
JavaScript 之 实现继承的几种方式_第2张图片
从上图中我们可以看到,p1在调用Person原型对象(prototype)上的say方法时,对于原型上的引用类型nation是大家共享的,所以p1也会说,自己去过america。而p2因为赋值操作于词法分析阶段,在p2的身上追加了一个nation属性,在js运行时,在自身有所需属性时,不会去原型链上查找了,所以此处没有公用原型链上的nation

2、借用构造函数方式继承

function Person(name) {
  this.name = name;
  this.nation = ["china"];
  this.say = function () {
    let res = this.nation;
    res = `I have been ${res}`;
    return res;
  };
}

function Chinese(name) {
  Person.call(this, name); // 对this,进行绑定
}

Chinese.prototype = new Person(); // 这里继承

p1 = new Chinese("Li Hua");
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei");
p2.nation = "korea";
p3 = new Chinese("Lao Wang");
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);
// 这种方式虽然解决了原型方式中的不可传参的问题,与引用类型共享的问题,
// 但是同时也引入了新的问题,即每次创建实例的时候都会重新创建一遍对象方法

运行结果如下图所示:
JavaScript 之 实现继承的几种方式_第3张图片
我们可以看到,这种方式解决了原型方式的两个不足,他可以传递参数,而且对于原型上引用类型属性也不是公用的。但是我们同时也能清楚的看到,每个实例对象都重复的创建了say这个方法,这可以说是一种新的不足。

3、组合式继承

代码如下:

function Person(name) {
  this.name = name;
  this.nation = ["china"];
}
// 公用方法还是用原型对象的方式
Person.prototype.say = function () { 
  let res = this.nation;
  res = `I have been ${res}`;
  return res;
};

function Chinese(name, age) {
  Person.call(this, name); // 私有属性利用构造函数,下面指定constructor为自身
  this.age = age;
}

Chinese.prototype = new Person(); // 继承name nation 与 say方法
Chinese.prototype.constructor = Chinese; // 构造器用Chinese

p1 = new Chinese("Li Hua", 16);
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei", 16);
p2.nation = "korea";
p3 = new Chinese("Lao Wang", 35);
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);

运行结果如下图所示:
JavaScript 之 实现继承的几种方式_第4张图片
如上图所示:组合继承结合了原型链方式借用构造函数方式,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

4、寄生组合式继承

// 组合继承已经相对完善了,但还是存在问题,它的问题就是调用了 2 次父类构造函数
// 第一次是 在Person.call(this,name) 第二次 是在new Person()的时候
// 改进如下,主要是第20行,不在用new操作符了
function Person(name) {
  this.name = name;
  this.nation = ["china"];
}

Person.prototype.say = function () {
  let res = this.nation;
  res = `I have been ${res}`;
  return res;
};

function Chinese(name, age) {
  Person.call(this, name);
  this.age = age;
}

Chinese.prototype = Object.create(Person.prototype); // 继承name nation 与 say方法,但是不再使用new来调用。
Chinese.prototype.constructor = Chinese; // 构造器用Chinese

p1 = new Chinese("Li Hua", 16);
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei", 16);
p2.nation = "korea";
p3 = new Chinese("Lao Wang", 35);
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);

运行结果如下图所示:
JavaScript 之 实现继承的几种方式_第5张图片

二、ES6的继承方式

class方式

class Person {
  constructor(name) {
    this.name = name;
    this.nation = ["china"];
  }
  say() {
    let res = this.nation;
    res = `I have been ${res}`;
    return res;
  }
}

class Chinese extends Person {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
}

p1 = new Chinese("Li Hua", 16);
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei", 16);
p2.nation = "korea";
p3 = new Chinese("Lao Wang", 35);
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);

运行结果如下图所示:
JavaScript 之 实现继承的几种方式_第6张图片
在class这里我们发现,利用class实现和ES5寄生组合式的实现表现基本一致,但是更加清晰。通过一步步的实现与对比,我们更好的理解了原型链等相关基础知识。如果这篇文章对您有帮助,欢迎您在评论区与我交流,不足之处也请指正。谢谢!


  1. https://mp.weixin.qq.com/s/mG3MTIQFcKvUU2WBByIugg ↩︎

你可能感兴趣的:(JavaScript,javascript,prototype,js)