自從有了webpack以後,咱們這些jscoder彷佛獲得了史無前例的解放,箭頭函數,對象解構,let,const關鍵字,以及class、extends等等關鍵字使用得不亦樂乎,反正,webpack會幫咱們把這些es6代碼轉換成瀏覽器可以識別的es5代碼,那麼,咱們有多少人真正的看過,babel轉換以後的代碼呢?今天,我就來看一下,當咱們使用關鍵詞class的時候,babel到底作了什麼?webpack
我推薦打開網址:https://babeljs.io/repl,這裏咱們左邊寫es6代碼,立刻右邊就能轉譯出es5代碼,而後,我在左邊輸入了以下代碼:es6
class A { constructor(name) { this.name = name } getName() { return this.name } }
這是一個最簡單的類,一個屬性,一個方法。web
這時候,右邊框已經給我轉譯出了瀏覽器可識別的es5代碼了,格式化以後是這樣的:express
'use strict'; 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; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } var A = function () { function A(name) { _classCallCheck(this, A); this.name = name; } _createClass(A, [{ key: 'getName', value: function getName() { return this.name; } }]); return A; }();
好,如今來分析一下這段代碼。數組
// 自執行函數 var A = function () { function A(name) { // 這個函數的目的實際上是防止這個構造函數被當作普通函數執行 _classCallCheck(this, A); this.name = name; } // 對函數A執行_createClass方法,其實就是給A的原型上綁定方法 _createClass(A, [{ key: 'getName', //方法名 value: function getName() { //函數體 return this.name; } }]); return A; }();
這段代碼,變量A是一個自執行函數的返回值,該自執行函數的返回值其實就是咱們熟悉的構造函數,因此,es6裏面的類其實就是一個構造函數。瀏覽器
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
這個函數特別簡單,當執行函數A的時候,不容許this不是A的子類實例,好比直接這樣調用A(),可是在A的子類B中能夠這樣調用:A.apply(this, arguments)。
該函數的目的是防止構造函數被當作普通函數執行。babel
//該函數也是一個自執行的函數,其返回值是一個函數 var _createClass = function () { // 把props數組上每個對象,經過Object.defineProperty方法,都定義到目標對象target上去 function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { //這裏要確保props[i]是一個對象,而且有key和value兩個鍵 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) { // 若是傳入了原型屬性數組,就把屬性所有定義到Constructor的原型上去 if (protoProps) defineProperties(Constructor.prototype, protoProps); // 若是傳入了靜態屬性數組,就把屬性所有定義到Constructor對象自身上去 if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
其實_createClass函數作的事情,就是把幾個方法拷貝到構造函數A的原型上去。app
我在https://babeljs.io/repl 左側輸入框上加了下面這行代碼:函數
class B extends A {}
這時候,右側多出瞭如下幾行代碼:this
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError('this hasn\'t been initialised - super() hasn\'t been called'); } return call && (typeof call === 'object' || typeof call === 'function') ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var B = function (_A) { _inherits(B, _A); function B() { _classCallCheck(this, B); //這裏的重點是第二個參數:(B.__proto__ || Object.getPrototypeOf(B)).apply(this, arguments); //這裏實際上是將子類的實例對象,調用了父類的構造函數方法,這樣父類的屬性就均可以拷貝到子類上來 return _possibleConstructorReturn(this, (B.__proto__ || Object.getPrototypeOf(B)).apply(this, arguments)); } return B; }(A);
function _inherits(subClass, superClass) { //簡單校驗 if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } //把子類的原型指向父類的原型建立出來的對象(注意不是直接指向父類原型),而且修正constructor屬性爲子類本身 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // 這一步操做,實際上是想把superClass放到subClass下,至關於subClass.super = superClass,這樣後面的代碼中,subClass裏面能方便的引用到superClass函數 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError('this hasn\'t been initialised - super() hasn\'t been called'); } return call && (typeof call === 'object' || typeof call === 'function') ? call : self; }
若是call不是對象或者函數,即該調用:(B.__proto__ || Object.getPrototypeOf(B)).apply(this, arguments)的返回值既不是對象,也不是函數,那麼,就直接返回當前的self,而self其實就是子類B裏面的實例指針this。正常狀況,(B.__proto__ || Object.getPrototypeOf(B)).apply(this, arguments)的返回值就是一個對象,其實也就是對象。
好了,上面算是基本說清楚了使用es6語法定義類、繼承類,到底發生了什麼,若是錯誤,還請指正,謝謝!