雖然在 JavaScript 中對象無處不在,但這門語言並不使用經典的基於類的繼承方式,而是依賴原型,至少在 ES6 以前是這樣的。當時,假設咱們要定義一個能夠設置 id 與座標的類,咱們會這樣寫:git
// Shape 類 function Shape(id, x, y) { this.id = id; this.setLocation(x, y); } // 設置座標的原型方法 Shape.prototype.setLocation = function(x, y) { this.x = x; this.y = y; };
上面是類定義,下面是用於設置座標的原型方法。從 ECMAScript 2015 開始,語法糖 class
被引入,開發者能夠經過 class
關鍵字來定義類。咱們能夠直接定義類、在類中寫靜態方法或繼承類等。上例即可改寫爲:github
class Shape { constructor(id, x, y) { // 構造函數語法糖 this.id = id; this.setLocation(x, y); } setLocation(x, y) { // 原型方法 this.x = x; this.y = y; } }
一個更符合「傳統語言」的寫法。語法糖寫法的優點在於當類中充滿各種靜態方法與繼承關係時,class 這種對象模版寫法的簡潔性會更加突出,且不易出錯。但不能否認時至今日,咱們還須要爲某些用戶兼容咱們的 ES6+ 代碼,class 就是 TodoList 上的一項:npm
做爲當下最流行的 JavaScript 編譯器,Babel 替咱們轉譯 ECMAScript 語法,而咱們不用再擔憂如何進行向後兼容。數組
本地安裝 Babel 或者利用 Babel CLI 工具,看看咱們的 Shape 類會有哪些變化。惋惜的是,你會發現代碼體積由如今的219字節激增到2.1KB,即使算上代碼壓縮(未混淆)代碼也有1.1KB。轉譯後輸出的代碼長這樣:babel
"use strict";var _createClass=function(){function a(a,b){for(var c,d=0;d<b.length;d++)c=b[d],c.enumerable=c.enumerable||!1,c.configurable=!0,"value"in c&&(c.writable=!0),Object.defineProperty(a,c.key,c)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}();function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}var Shape=function(){function a(b,c,d){_classCallCheck(this,a),this.id=b,this.setLocation(c,d)}return _createClass(a,[{key:"setLocation",value:function c(a,b){this.x=a,this.y=b}}]),a}();
Babel 僅僅是把咱們定義的 Shape 還原成一個 ES5 函數與對應的原型方法麼?函數
好像沒那麼簡單,爲了摸清實際轉譯流程,咱們先將上述類定義代碼簡化爲一個只有14字節的空類:工具
class Shape {}
首先,當訪問器走到類聲明階段,須要補充嚴格模式:優化
"use strict"; class Shape {}
而進入變量聲明與標識符階段時則需補充 let 關鍵字並轉爲 var:ui
"use strict"; var Shape = class Shape {};
到這個時候代碼的變化都不太大。接下來是進入函數表達式階段,多出來幾行函數:this
"use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Shape = function Shape() { _classCallCheck(this, Shape); };
該階段不只替換了 class,還在類中調用了叫作 _classCallCheck
的方法。這是什麼呢?
這個函數的做用在於確保構造方法永遠不會做爲函數被調用,它會評估函數的上下文是否爲 Shape 對象的實例,以此肯定是否須要拋出異常。接下來,則輪到 babel-plugin-minify-simplify上場,這個插件作的事情在於經過簡化語句爲表達式、並使表達式儘量統一來精簡代碼。運行後的輸出是這樣的:
"use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function"); } var Shape = function Shape() { _classCallCheck(this, Shape); };
能夠看到 if 語句中因爲只有一行代碼,因而花括號被去掉。接下來上場的即是內置的 Block Hoist ,該插件經過遍歷參數排序而後替換,Babel 輸出結果爲:
"use strict"; function _classCallCheck(a, b) { if (!(a instanceof b)) throw new TypeError("Cannot call a class as a function"); } var Shape = function a() { _classCallCheck(this, a); };
最後一步,minify 一下,代碼體積由最初的14字節增爲338字節:
"use strict";function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}var Shape=function a(){_classCallCheck(this,a)};
這是一個什麼都沒幹的類聲明,但現實中任何類都會有本身的方法,而此時 Babel 一定會引入更多的插件來幫助它完成代碼的轉譯工做。直接在剛剛的空類中定義一個方法吧。
class Shape { render() { console.log("Hi"); } }
咱們用 Babel 轉譯一下,會發現代碼中包含以下這段:
var _createClass = function () { function a(a, b) { for (var c, d = 0; d < b.length; d++) c = b[d], c.enumerable = c.enumerable || !1, c.configurable = !0, "value" in c && (c.writable = !0), Object.defineProperty(a, c.key, c); } return function (b, c, d) { return c && a(b.prototype, c), d && a(b, d), b; }; }();
相似前面咱們遇到的 _classCallCheck
,這裏又多出一個 _createClass
,這是作什麼的呢?咱們稍微把代碼狀態往前挪一挪,來到 babel-plugin-minify-builtins 處理階段(該插件的做用在於縮減內置對象代碼體積,但咱們主要關注點在於這個階段的 _createClass
函數是基本可讀的),此時 _classCallCheck
長成這樣:
var _createClass = 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); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; } ();
能夠看出 _createClass
用於處理建立對象屬性,函數支持傳入構造函數與需定義的鍵值對屬性數組。函數判斷傳入的參數(普通方法/靜態方法)是否爲空對應到不一樣的處理流程上。而 defineProperties
方法作的事情即是遍歷傳入的屬性數組,而後分別調用 Object.defineProperty
以更新構造函數。而在 Shape 中,因爲咱們定義的不是靜態方法,咱們便這樣調用:
_createClass(Shape, [{ key: "render", value: function render() { console.log("Hi"); } }]);
T.J. Crowder 在 How does Babel.js create compile a class declaration into ES2015? 中談到 Babel 是如何將 class 轉化爲 ES5 兼容代碼時談到了幾點,大意爲:
constructor
會成爲構造方法數;Object.create(Base.prototype)
構造的對象,而不是 new Base()
;constructor
調用構造器基類是第一步操做;super
方法的寫法是 Base.prototype.baseMethod.call(this);
,這種操做不只繁瑣並且容易出錯;這些概述大體總結了類定義在兩個 ES 版本中的一些差別,其餘不少方面好比 extends
——繼承關鍵字,它的使用則會使 Babel 在轉譯結果加上 _inherits
與 _possibleConstructorReturn
兩個函數。篇幅所限,此處再也不展開詳述。
語法糖 class
給咱們帶來了不少寫法上的便利,但可能會使咱們在代碼體積上的優化努力「付諸東流」。
另外一方面,若是你是一名 React 應用開發者,你是否已經在想將代碼中的全部 class 寫法換爲 function 呢?那樣作的話,代碼體積無疑會減小不少,但你必定也知道 PureComponent 相比 Component 的好處。因此雖然 function 給你的代碼體積減負了,但他在哪裏又給你無形增長負擔了呢?
所以,真的不推薦開發者用 class
這種寫法麼,你以爲呢?