公所周知,JS在常规开发语言中。位于技术鄙视链顶端。说JS不好嘛,不是。Node
的出现。预示着JS大有统一前后端的趋势。(这只是小弟的一个拙见,勿喷)或者卑微的说一句,JS能在后端也可以展示一下拳脚了。
其中有一点很让其他OOP语言诟病的就是:JS基于Prototype的继承。对于一个接触过C++
、JAVA
,并在实际项目中使用过这些语言的卑微的我来说,第一次接触prototype
的时候,那是尼克杨脸上都是问号(估计只有懂点NBA的人才会知道这个梗吧)。
但是自从ES6颁布以来,局面有一些好转,ES6也有了class
/extends
等相关语法实现。JS在众多OOP语言里,瞬间站了起来,有没有。
其实,作为一个接触过其他OOP语言的前端开发者,很喜欢用class
来定义对象的结构,并用extends
来实现继承。 但是几乎所有关于ES6特性介绍的文档中,你肯定会看到
ES6 的
class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到
其实在开发中直接用class
定义对象结构,并用extends
来实现继承。很常见,很方便。
既然人家都说了,这是一个糖。但是作为一个合格的开发者。要有打破砂锅问到底的心思。所以,我用我拙劣的代码来将这层糖的成分细细分析一下。
下面一些方法,都是基于class
被脱糖之后的分析。关于class
是如何被脱糖的过程===>请先查看ES6-Class如何优雅的进行Prototype“糖化” 内容很精彩,但是不要忘记回来看这个。这个剧情也挺赤鸡的。
ES5基于prototype的继承
在进行继承的讲解的时候,还是需要明确一点。就是ES5的类 = 构造函数+prototpye
。所以基于这点就存在两个继承方向。
- 继承构造函数中的实例属性和方法
- 继承原型对象上的属性和方法
关于ES5的继承有很多实现方式:例如原型链继承(原型继承)、构造函数继承(实例属性)、组合式继承、原型式继承、寄生式继承、寄生组合式继承。 其中我们按组合式继承(原型链+构造函数继承)来简单说明一下实现原理。
原型链继承
继承原型对象上的属性和方法
将一个类型的实例,赋值给另外一个构造函数的原型
。
子类是继承父类的原型方式。
function SuperObj(){ this.objName = 'super'; } SuperObj.prototype.getName = function(){ return this.objName; } function SubObj(){ this.objName = 'sub'; } //将父类的实例,赋值给目标类的prototype SubObj.prototype = new SuperObj(); var instance = new SubObj(); //继承父类的原型方法 instance.getName() //super instance instanceof SubObj //true instance instanceof SuperOjb //true 复制代码
构造函数继承
继承构造函数中的实例属性和方法
首先说明一点,构造函数继承,只是对目标构造函数中属性的继承,而不是真正基于prototype
的继承。子类的实例如果用instanceof
来进行判断的话,其实返回的是false
子类继承父类的属性和方法
function SuperObj(){ this.nameArr = ['北宸','南蓁']; } //在子类的构造函数调用父类构造函数 function SubObj(){ SuperOjb.call(this) } var instance1 = new SubObj(); instance1.nameArr.push('你好'); //['北宸','南蓁','你好'] var instance2 = new SubObj(); instance2.nameArr //['北宸','南蓁'] instance1 instanceof SuperObj //false 复制代码
组合继承
组合式继承其实就是构造函数继承
+原型链继承
。也就是使用原型链实现对原型方法的继承,借用构造函数来实现对实例属性的继承。
由于ES5实现一个完整的类,就需要
构造函数
+prototype
。
function SuperObj(allname){ this.nameArr = ['北宸','南蓁']; this.allName =allname; } SuperObj.prototype.getAllName = function(){ return this.allName; } //构造函数继承,继承目标构造函数的属性和方法 function SubObj(allname,age){ SuperObj.call(this,allname); this.age = age; } //原型链继承 SubObj.prototype = new SuperObj('北宸'); var instance1 = new SubObj('instance1',1); var instance2 = new SubOjb('instance2',2); instance1 instanceof SuperObj //true instance2 instanceof SuperObj //true instance1.getAllName() //instance1 instance2.getAllName()//instance2 复制代码
ES6利用extends的"糖化"继承
最简单的继承
Talk is cheap,show you the code
class A { } class B extends A{ } 复制代码
这算是最简单的一个继承。或者从严格意义上,B
是对A
复制了一份。因为A没有任何的实例属性和方法,也没有原型属性。
但是我们可以看看脱糖之后的代码。
"use strict"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError( "this hasn't been initialised - super() hasn't been called" ); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _instanceof(left, right) { if ( right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance] ) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var A = function A() { _classCallCheck(this, A); }; var B = /*#__PURE__*/ (function(_A) { _inherits(B, _A); function B() { _classCallCheck(this, B); return _possibleConstructorReturn( this, _getPrototypeOf(B).apply(this, arguments) ); } return B; })(A); 复制代码
我们来分析一波。
var A = function A() { _classCallCheck(this, A); }; 复制代码
这个就不用在多说啥了。就是简单定义了一个空构造函数。
其实最关键的还是下面的代码: 首先映入眼帘的是一个IIFE,接收刚被定义的构造函数A
。
这里直接给大家把对应的注释给加上了。
(function(_A) { //继承原型对象上的属性和方法 _inherits(B, _A); function B() { _classCallCheck(this, B); //继承构造函数中的实例属性和方法 return _possibleConstructorReturn( this, _getPrototypeOf(B).apply(this, arguments) ); } return B; })(A); 复制代码
通过代码我们看到_inherits(B, _A)
用于继承原型对象上的属性和方法,而_possibleConstructorReturn
则是继承构造函数中的实例属性和方法。所以很符合我们上面讲的。
然后我们继续分析其中的原委:
继承原型对象上的属性和方法(_inherits)
Talk is cheap,show you the code:
//_inherits(B, _A) function _inherits(subClass, superClass) { //对superClass进行类型判断 if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } //子类的prototype继承父类的prototype subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); //子类是父类构建出的函数对象,需要指定对象的__proto__ if (superClass) _setPrototypeOf(subClass, superClass); } 复制代码
Note: 有一点需要注意的就是:
大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
继承构造函数中的实例属性和方法(_possibleConstructorReturn)
/* _possibleConstructorReturn( this,//指向子类构造函数 //_getPrototypeOf(B)用于获取指定对象的父类 _getPrototypeOf(B).apply(this, arguments) ); */ function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError( "this hasn't been initialised - super() hasn't been called" ); } return self; } 复制代码
在进行_possibleConstructorReturn
调用的时候,其实处理class
的constructor
的实例属性和方法的继承。当父类存在constructor
就需要_getPrototypeOf(B).apply(this, arguments)
将父类的属性和方法复制到新的构造函数中。实现继承。
Note:存在_getPrototypeOf(B).apply(this, arguments)
是父类存在constructor
。如果不存在,直接就是_possibleConstructorReturn(this)
要点汇总
其实ES6extends
实现继续还是基于ES5的组合继承(构造函数继承+原型链继承)
_inherits(B, _A)
实现原型链的继承- 在子类的构造函数中的
_possibleConstructorReturn
实现构造函数的继承
复杂的类
上面讲的那个例子就是一个空类A
被B
继承。其实就是B将A复制了一遍,这是利用的extends
的语法。来看基本的语法实现。
ES6示例
现在我们来构建一个比较常规的类:拥有实例方法的继承
Talk is cheap ,show you the code:
class A { static name = 'superClass' constructor(x,y){ this.x =x; this.y =y; } } class B extends A{ constructor(x,y,z){ super(x,y); this.y = y; } } 复制代码
ES5脱糖
"use strict"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError( "this hasn't been initialised - super() hasn't been called" ); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _instanceof(left, right) { if ( right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance] ) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var A = function A(x, y) { _classCallCheck(this, A); this.x = x; this.y = y; }; _defineProperty(A, "name", "superClass"); var B = /*#__PURE__*/ (function(_A) { _inherits(B, _A); function B(x, y, z) { var _this; _classCallCheck(this, B); _this = _possibleConstructorReturn( this, _getPrototypeOf(B).call(this, x, y) ); _this.y = y; return _this; } return B; })(A); 复制代码
看到代码之后,其实大致的实现流程和B
继承一个空类A
是一样的。都是_inherits()
实现原型继承,_possibleConstructorReturn()
实现构造函数继承。
但是这里多了一个内部_this
。
这里需要额外的提出:ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
也就是说,如果在子类的构造函数中想调用this
必须先调用super()
。