公所周知,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()
。