在一些書籍中花費了很多的篇幅進行講述,新的語法中也出現了相關的關鍵字,實現的方式中也涉及到 JavaScript 中很重要的知識點。git
注意:JavaScript 中並無相似 Java 中的類和繼承,如下用「類」和「繼承」是爲了方便描述。es6
上一篇 JavaScript 新舊替換三:參數轉換。github
實現繼承功能的方式有多種,JavaScript 中經常使用的繼承模式是組合繼承,這裏以此爲例。express
function Fruit(name) { this.name = name; } Fruit.prototype.showName = function() { console.info("Fruit Name:", this.name); }; function Apple(name, color) { Fruit.call(this, name); this.color = color; } Apple.prototype = new Fruit(); // 矯正語義指向,並非必需 Apple.prototype.constructor = Apple; Apple.prototype.showColor = function() { console.info("Apple Color:", this.color); }; var apple = new Apple("apple", "green"); console.info("apple:", apple); apple.showName(); apple.showColor();
在組合繼承中,主要的思路是:babel
Fruit.call(this, name)
綁定子類的 this
,達到繼承父類屬性效果。prototype
屬性,子類的實例會沿着原型鏈查找,達到了繼承父類方法的效果。用新的語法實現上面的繼承:app
class Fruit { constructor(name) { this.name = name; } showName() { console.info("Fruit Name:", this.name); } } class Apple extends Fruit { constructor(name, color) { super(name); this.color = color; } showColor() { console.info("Apple Color:", this.color); } } let apple = new Apple("apple", "green"); console.info("apple:", apple); apple.showName(); apple.showColor();
在書寫形式上有很大的變化,但實際上也是經過原型鏈實現,經過 Babel 轉譯爲 ES5 看下是怎樣的實現思路。函數
首先說明一下 Babel 中轉譯有兩種模式:normal 和 loose。ui
選擇 normal 模式的轉譯更加合適,先來看下 Fruit
類轉譯後的實現:this
"use strict"; /** * Symbol.hasInstance 屬性,指向一個內部方法。 * 當其它對象使用 instanceof 運算符,判斷是否爲該對象的實例時,會調用這個方法。 * 好比,foo instanceof Foo 在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)。 */ 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) { // 判斷 instance 是否爲 Constructor 的實例 if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * * Object.defineProperty 直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,並返回這個對象。 * */ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); // 靜態方法直接放在構造函數上 if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Fruit = /*#__PURE__*/ (function() { function Fruit(name) { _classCallCheck(this, Fruit); this.name = name; } _createClass(Fruit, [ { key: "showName", value: function showName() { console.info("Fruit Name:", this.name); } } ]); return Fruit; })();
在上面轉譯的代碼中,處理的主要思路有:prototype
_classCallCheck
方法判斷調用的方式,防止 Fruit()
這樣直接調用。_createClass
方法在 prototype
上添加公用方法,在 Fruit
上添加靜態方法。這種方式跟組合使用構造函數模式和原型模式建立對象很類似,不過表達的語義不太同樣。
再來看下繼承轉譯後的代碼:
"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); } /** * * Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。 * @param {*} superClass */ 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 _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Fruit = /*#__PURE__*/ (function() { function Fruit(name) { _classCallCheck(this, Fruit); this.name = name; } _createClass(Fruit, [ { key: "showName", value: function showName() { console.info("Fruit Name:", this.name); } } ]); return Fruit; })(); var Apple = /*#__PURE__*/ (function(_Fruit) { _inherits(Apple, _Fruit); function Apple(name, color) { var _this; _classCallCheck(this, Apple); // _getPrototypeOf(Apple).call(this, name) 調用的實際是父類的函數,注意沒有使用 new ,返回的是默認的 undefined _this = _possibleConstructorReturn( this, _getPrototypeOf(Apple).call(this, name) ); _this.color = color; return _this; } _createClass(Apple, [ { key: "showColor", value: function showColor() { console.info("Apple Color:", this.color); } } ]); return Apple; })(Fruit); let apple = new Apple("apple", "green"); console.info("apple:", apple); apple.showName(); apple.showColor();
在上面轉譯的代碼中,處理的主要思路是:
_inherits
方法基於父類的 prototype
建立了一個新的對象,賦給了子類的 prototype
。還將子類的 __proto__
指向了父類,爲的是繼承父類的屬性。prototype
上定義子類本身的方法。this
的值。ES2015 引入了類的概念,經過 class
關鍵字能夠定義類。類有下面一些特色:
new
調用,不然會報錯。static
關鍵字。this
指向類的實例。constructor
是構造方法,經過 new
命令生成對象實例時,自動調用該方法。一個類必須有 constructor
方法,若是沒有顯式定義,一個空的 constructor
方法會被默認添加。
class A {} let obj = new A(); console.info(obj.constructor === A.prototype.constructor);
class
繼承經過 extends
實現。繼承時,類中構造函數必需要執行 super
方法,不然建立實例的時候會報錯。
class A {} class B extends A { constructor() { } } let obj = new B(); // VM55958:3 Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
子類中若是沒有顯式的寫出構造方法,會默認的添加。
class A {} class B extends A {} // 等同於 class B extends A { constructor(...args) { super(...args) } }
super
關鍵字能夠當作函數或對象使用。使用 super
的時候,必須顯式指定是做爲函數、仍是做爲對象使用,不然會報錯。