公所周知,JS在常規開發語言中。位於技術鄙視鏈頂端。說JS很差嘛,不是。Node
的出現。預示着JS大有統一先後端的趨勢。(這只是小弟的一個拙見,勿噴)或者卑微的說一句,JS能在後端也能夠展現一下拳腳了。javascript
其中有一點很讓其餘OOP語言詬病的就是:JS基於Prototype的繼承。對於一個接觸過C++
、JAVA
,並在實際項目中使用過這些語言的卑微的我來講,第一次接觸prototype
的時候,那是尼克楊臉上都是問號(估計只有懂點NBA的人才會知道這個梗吧)。前端
可是自從ES6頒佈以來,局面有一些好轉,ES6也有了class
/extends
等相關語法實現。JS在衆多OOP語言裏,瞬間站了起來,有沒有。java
其實,做爲一個接觸過其餘OOP語言的前端開發者,很喜歡用class
來定義對象的結構,並用extends
來實現繼承。 可是幾乎全部關於ES6特性介紹的文檔中,你確定會看到express
ES6 的
class
能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到後端
其實在開發中直接用class
定義對象結構,並用extends
來實現繼承。很常見,很方便。瀏覽器
既然人家都說了,這是一個糖。可是做爲一個合格的開發者。要有打破砂鍋問到底的心思。因此,我用我拙劣的代碼來將這層糖的成分細細分析一下。app
下面一些方法,都是基於class
被脫糖以後的分析。關於class
是如何被脫糖的過程===>請先查看ES6-Class如何優雅的進行Prototype「糖化」 內容很精彩,可是不要忘記回來看這個。這個劇情也挺赤雞的。函數
在進行繼承的講解的時候,仍是須要明確一點。就是ES5的類 = 構造函數+prototpye
。因此基於這點就存在兩個繼承方向。post
關於ES5的繼承有不少實現方式:例如原型鏈繼承(原型繼承)、構造函數繼承(實例屬性)、組合式繼承、原型式繼承、寄生式繼承、寄生組合式繼承。 其中咱們按組合式繼承(原型鏈+構造函數繼承)來簡單說明一下實現原理。ui
繼承原型對象上的屬性和方法
將一個類型的實例,賦值給另外一個構造函數的原型
。
子類是繼承父類的原型方式。
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
複製代碼
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
則是繼承構造函數中的實例屬性和方法。因此很符合咱們上面講的。
而後咱們繼續分析其中的原委:
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( 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
的語法。來看基本的語法實現。
如今咱們來構建一個比較常規的類:擁有實例方法的繼承
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;
}
}
複製代碼
"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()
。