module1-03-JS6种继承方式

JS6种继承方式

继承的意义

  • 继承是面向对象的,使用这个方式可以让我们更好的复用代码,缩短开发周期,提升开发效率

思考题

  • ① JS的继承方式到底有多少种实现方式呢?

  • ② ES5的 extends 关键字是用哪种继承方式实现的呢?

一、原型链继承

  • 原型链继承是比较常见的继承方式之一

  • 让一段代码来感受一下

(1)案例感受

function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1.prototype = new Parent1();
console.log(new Child1());
  • 用代码测试一下

var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play);
  • 结果

(2)优缺点分析

  • 实现简单,方便快捷

  • 虽然父类的方法和属性都能够访问,但其实有一个潜在的问题,比如上面那样,只改变了一个实例,而另一个实例也跟着改变。

    • 因为内存空间是共享的

    • 当一个发生变化的时候另一个也跟着发生变化

二、构造函数继承(call)

(1)案例感受

function Parent1(){
this.name = 'parent1';
}

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

function Child1(){
Parent1.call(this);
this.type = 'child1'
}

let child = new Child1();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错
  • 结果

(2)优缺点分析

  • 子类可以拿到父类实例的属性和方法

  • 但是子类拿不到父类原型的属性和方法

三、组合继承

  • 结合了原型继承和构造函数继承的优缺点

(1)案例感受

function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}

Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次调用 Parent3()
Parent3.call(this);
this.type = 'child3';
}

// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
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'
  • 结果

(2)优缺点分析

  • 很好解决了上述两个方法的问题

  • 但是会调用两次父类,造成多的性能开销

四、原型式继承

  • 上面说的更多的是构造函数(类)的继承,那么围绕对象的继承又有哪些呢

  • 说起原型式继承不得不提到 ES5 的 Object.create 方法

    • 接受两个参数

      • ① 用作新对象的原型对象

      • ② 为新对象定义额外属性的对象

(1)案例感受

let parent4 = {
   name: "parent4",
   friends: ["p1", "p2", "p3"],
   getName: function() {
     return this.name;
  }
};

let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");

let person5 = Object.create(parent4);
person5.friends.push("lucy");

console.log(person4.name);
console.log(person4.name === person4.getName());
console.log(person5.name);
console.log(person4.friends);
console.log(person5.friends);
  • 结果

(2)优缺点

  • 可以实现对象的浅拷贝

  • 但是对于多个对象使用该方法的话,会出现共享问题

五、寄生式继承

  • 在上一个方法的基础上进行优化的一个继承方法

  • 原理:

    • 使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力在进行增强,添加一些方法

(1)案例感受

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());
  • 结果

(2)优缺点

  • 相比原型式继承,可以添加方法,从而使对象在继承的过程中又添加了一个方法

  • 缺点跟原型式继承一样,也只能实现浅拷贝

六、寄生组合式继承

  • 在前面这几种继承方式的优缺点基础上进行改造,这是所有继承方式里面相对最优的继承方式

(1)案例感受

  function clone (parent, child) {
   // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
   child.prototype = Object.create(parent.prototype);
   child.prototype.constructor = child;
}

 function Parent6() {
   this.name = 'parent6';
   this.play = [1, 2, 3];
}
  Parent6.prototype.getName = function () {
   return this.name;
}
 function Child6() {
   Parent6.call(this);
   this.friends = 'child5';
}

 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());
  • 结果

(2)优缺点

  • 很好的结合以上的优点,较少了构造函数的调用次数,减少了性能开销

  • es6的语法糖extends

(3)总结脑图

七、ES6的extends关键字实现逻辑

  • 使用关键词很容易就实现JS的继承,但是巷深入了解extends语法糖怎么实现,就要深入研究extends的底层逻辑

(1)es6中extends使用方法

class Person {
 constructor(name) {
   this.name = name
}
 // 原型方法
 // 即 Person.prototype.getName = function() { }
 // 下面可以简写为 getName() {...}
 getName = function () {
   console.log('Person:', this.name)
}
}
class Gamer extends Person {
 constructor(name, age) {
   // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
   super(name)
   this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法
  • 在babel编译成es5的代码

function _possibleConstructorReturn (self, call) { 
		// ...
		return call && (typeof call === 'object' || typeof call === 'function') ? call : self; 
}
function _inherits (subClass, superClass) { 
    // 这里可以看到
	subClass.prototype = Object.create(superClass && superClass.prototype, { 
		constructor: { 
			value: subClass, 
			enumerable: false, 
			writable: true, 
			configurable: true 
		} 
	}); 
	if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

var Parent = function Parent () {
	// 验证是否是 Parent 构造出来的 this
	_classCallCheck(this, Parent);
};
var Child = (function (_Parent) {
	_inherits(Child, _Parent);
	function Child () {
		_classCallCheck(this, Child);
		return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
	return Child;
}(Parent));
原文地址:https://www.cnblogs.com/lezaizhu/p/14455026.html