综合技术

你真的理解JS的继承了吗?

微信扫一扫,分享到朋友圈

你真的理解JS的继承了吗?
0

噫吁嚱,js之难,难于上青天

在原型链和继承上花了不知道多少时间,当每次自以为已经吃透的时候,总是会出现各种难以理解的幺蛾子。也许就像kyle大佬说的那样,js的继承模式真的是‘蠢弟弟’设计吧。

请先看下图,如果各位觉得soeasy,那各位也许不需要往下看了。(图片忘了从那篇文章摘下来的,请多见谅)


阅读之前先约定,本文中称 __proto__内置原型 ,称 prototype原型对象 ,构造函数称为子类和父类

es5寄生组合继承

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}
SubType.prototype = Object.create(SuperType.prototype, {
  constructor: {
    value: SubType,
    enumerable: false,
    writable: true,
    configurable: true
  }
})
SubType.prototype.sayAge = function(){
  alert(this.age);
}; 
let instance = new SubType('gim', '17');
instance.sayName(); // 'gim'
instance.sayAge(); // '17'
复制代码
  1. 首先,这段代码声明了父类 SuperType 以及子类的原型对象方法 sayName
  2. 其次,声明了子类 SubType ,并在未来将要新创建的 SubType 实例环境上调用父类 SuperType.call ,以获取父类中的 namecolors 属性。
  3. 然后,用 Object.create() 方法把子类的原型对象 prototype 上的内置对象 __proto__ 指向了父类的原型对象,并把子类构造函数重新赋值为子类。
  4. 最后给子类的原型对象上添加方法 sayAge,然后初始化实例对象。

此时,代码中的原型链关系如下图所示:


图中有两种颜色的带箭头的线,红色的线是我们生成的实例的原型链,是我们之所以能调用到 instance.sayName()instance.sayAge() 的根本所在。

因此,子类的原型对象的 __proto__ 指向父类的原型对象

黑色的带箭头的线则是 es5 继承中产生的‘副作用’,使得所有的函数的 __proto__ 指向了 Function.prototype ,并最终指向 Object.prototype,从而使得我们声明的函数可以直接调用 toString (定义在Function.prototype上)、 hasOwnProperty (定义在Object.prototype上) 等方法,如: SubType.toString()、SubType.hasOwnProperty() 等。

子类也是函数,因此,在 es5 的实现中,子类的 __proto 指向的是 Function.prototype

es6的class … extends …

class SuperType {
  constructor(name) {
    this.name = name
    this.colors = ["red", "blue", "green"];
  }
  sayName() {
    alert(this.name)
  }
}
class SubType extends SuperType {
  constructor(name, age){
    super(name)
    this.age = age
  }
  sayAge() {
    alert(this.age)
  }
}
let instance = new SubType('gim', '17');
instance.sayName(); // 'gim'
instance.sayAge(); // '17'
复制代码

可以明显的发现这段代码比之前的更加简短和美观。es6 class 实现继承的核心在于使用关键字 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super 关键字,这段代码可以看成 SuperType.call(this, name)

虽然结果可能如你所料的实现了原型链继承,但是这里还是有个需要注意的点值得一说。


es6中的 class 继承存在两条继承链:

  1. 子类的 __proto__ 属性,表示构造函数的继承,总是指向父类。 如蓝线所示,子类 SubType__proto__ 指向父类 SuperType 。 相当于调用了 Object.setPrototypeOf(SubType, SuperType) ; 之所以注意到这点,是因为看 kyle 大佬的《你不知道的javascript 下》的时候,看到了 class MyArray extends Array{}var arr = MyArray.of(3) 这两行代码,很不理解为什么 MyArray 上面为什么能调到 of 方法。因为按照es5中继承的经验, MyArray.__proto__ 应该指向了 Function.prototype ,而后者并没有of方法。当时感觉世界观崩塌了,为什么我以前记得是错的,后来才发现原来 class 实现的继承是不同的。

  2. 子类 prototype 属性的 __proto__ 属性,表示方法的继承,总是指向父类的 prototype 属性。 这点倒和经典继承是一致的。 如红线所示,子类 SubTypeprototype 属性的 __proto__ 指向父类 SuperTypeprototype 属性。 相当于调用 Object.setPrototypeOf(SubType.prototype, SuperType.prototype) ;

kyle大佬提到的行为委托

let SuperType = {
  initSuper(name) {
	this.name = name
    this.color = [1,2,3]
  },
  sayName() {
	alert(this.name)
  }
}
let SubType = {
  initSub(age) {
	this.age = age
  },
  sayAge() {
	alert(this.age)
  }
}
Object.setPrototypeOf(SubType,SuperType)
SubType.initSub('17')
SubType.initSuper('gim')
SubType.sayAge() // 'gim'
SubType.sayName() // '17'
复制代码

小子愚钝,如果行为委托完全能够实现实现class继承的功能,而且更加简单和清晰,我们开发的过程中为什么不尝试用一下呢?

阅读原文...

微信扫一扫,分享到朋友圈

你真的理解JS的继承了吗?
0
稀土掘金

适用于iOS的Mozilla Firefox更新 全面改善隐私保护

上一篇

Android RxLife 一款轻量级别的RxJava生命周期管理库

下一篇

评论已经被关闭。

插入图片

热门分类

往期推荐

你真的理解JS的继承了吗?

长按储存图像,分享给朋友