目錄html
1.序言react
ECMAScript 2015(ES6) 中引入的 JavaScript 類實質上是 JavaScript 現有的基於原型的繼承的語法糖。類語法(class)不會爲JavaScript引入新的面向對象的繼承模型。git
2.class 是一個特殊的函數es6
ES6 的 class 主要提供了更多方便的語法去建立老式的構造器函數。咱們能夠經過 typeof 獲得其類型:github
class People { constructor(name) { this.name = name; } } console.log(typeof People) // function
那 class 聲明的類究竟是一個什麼樣的函數呢?咱們能夠經過在線工具 ES6 to ES5 來分析 class 背後真正的實現。express
3.class 的工做原理瀏覽器
下面經過多組代碼對比,來解析 class 聲明的類將轉化成什麼樣的函數。babel
第一組:用 class 聲明一個空類app
ES6的語法:函數
class People {}
這裏提出兩個問題:
1.class 聲明的類與函數聲明不同,不會提高(即便用必須在聲明以後),這是爲何?
console.log(People) // ReferenceError class People {}
在瀏覽器中運行報錯,以下圖:
2.不能直接像函數調用同樣調用類People()
,必須經過 new 調用類,如 new People()
,這又是爲何?
class People {} People() // TypeError
在瀏覽器中運行報錯,以下圖:
轉化爲ES5:
"use strict"; function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } // 判斷 Constructor.prototype 是否出如今 instance 實例對象的原型鏈上 function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var People = function People() { // 檢查是否經過 new 調用 _classCallCheck(this, People); };
針對上面提到的兩個問題,咱們均可以用轉化後的 ES5 代碼來解答:
對於問題1,咱們能夠看到 class 聲明的類轉化爲的是一個函數表達式,而且用變量 People 保存函數表達式的值,而函數表達式只能在代碼執行階段建立並且不存在於變量對象中,因此若是在 class 聲明類以前使用,就至關於在給變量 People 賦值以前使用,此時使用是沒有意義的,由於其值爲 undefined,直接使用反而會報錯。因此 ES6 就規定了在類聲明以前訪問類會拋出 ReferenceError 錯誤(類沒有定義)。
對於問題2,咱們能夠看到 People 函數表達式中,執行了 _classCallCheck 函數,其做用就是保證 People 函數必須經過 new 調用。若是直接調用 People(),因爲是嚴格模式下執行,此時的 this 爲 undefined,調用 _instanceof 函數檢查繼承關係其返回值必然爲 false,因此必然會拋出 TypeError 錯誤。
補充:類聲明和類表達式的主體都執行在嚴格模式下。好比,構造函數,靜態方法,原型方法,getter和setter都在嚴格模式下執行。
第二組:給類添加公共字段和私有字段
ES6的語法:
class People { #id = 1 // 私有字段,約定以單個的`#`字符爲開頭 name = 'Tom' // 公共字段 }
轉化爲ES5:
... // 將類的公共字段映射爲實例對象的屬性 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 People = function People() { _classCallCheck(this, People); // 初始化私有字段 _id.set(this, { writable: true, value: 1 }); // 將類的公共字段映射爲實例對象的屬性 _defineProperty(this, "name", 'Tom'); }; // 轉化後的私有字段(會自動檢查命名衝突) var _id = new WeakMap();
對比轉化先後的代碼能夠看出:
對於私有字段,在使用 class 聲明私有字段時,約定是以字符 '#' 爲開頭,轉化後則將標識符中的 '#' 替換爲 '_',而且單獨用一個 WeakMap 類型的變量來替代類的私有字段,聲明在函數表達式後面(也會自動檢查命名衝突),這樣就保證了類的實例對象沒法直接經過屬性訪問到私有字段(私有字段根本就沒有在實例對象的屬性中)。
對於公共字段,則是經過 _defineProperty 函數將類的公共字段映射爲實例對象的屬性,若是是首次設置,還會經過 Object.defineProperty 函數來進行初始化,設置屬性的可枚舉性(enumerable)、可配置性(configurable)、可寫性(writable)
第三組:給類添加構造函數與實例屬性
ES6的語法:
class People { #id = 1 // 私有字段,約定以單個的`#`字符爲開頭 name = 'Tom' // 公共字段 constructor(id, name, age) { this.#id = id this.name = name this.age = age // 實例屬性 age } }
轉化爲ES5:
... // 設置(修改)類的私有字段 function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; } var People = function People(id, name, age) { _classCallCheck(this, People); _id.set(this, { writable: true, value: 1 }); _defineProperty(this, "name", 'Tom'); // constructor 從這開始執行 _classPrivateFieldSet(this, _id, id); this.name = name; this.age = age; }; var _id = new WeakMap();
對比轉化先後的代碼能夠看出:
類的構造函數(constructor)裏面的代碼的執行時機是在字段定義(字段映射爲實例對象的屬性)以後。而對私有字段的賦值(修改)是專門經過 _classPrivateFieldSet 函數來實現的。
第四組:給類添加原型方法和靜態方法
ES6的語法:
class People { #id = 1 name = 'Tom' constructor(id, name, age) { this.#id = id this.name = name this.age = age } // 原型方法 getName() { return this.name } // 靜態方法 static sayHello() { console.log('hello') } }
轉化爲ES5:
... // 設置對象的屬性 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); } } // 將類的方法映射到構造函數的原型(Constructor.prototype)的屬性上 // 將類的靜態方法映射到構造函數(Constructor)的屬性上 function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var People = function () { function People(id, name, age) { // ... } // 設置類的方法和靜態方法 _createClass(People, [{ key: "getName", value: function getName() { return this.name; } }], [{ key: "sayHello", value: function sayHello() { console.log('hello'); } }]); return People; }(); var _id = new WeakMap();
對比一下第三組和第四組轉化後的代碼,能夠明顯發現:
類的字段經過 _defineProperty 函數映射到實例對象(this)的屬性上。
類的方法則經過 _createClass 函數映射到構造函數的原型(Constructor.prototype)的屬性上,
類的靜態方也經過 _createClass 函數映射到構造函數(Constructor)的屬性上。
第五組:類的繼承
ES6的語法:
// 父類(superClass) class People {} // 子類(subClass)繼承父類 class Man extends People {}
轉化爲ES5:
... var People = function People() { _classCallCheck(this, People); }; var Man = function (_People) { // Man 繼承 _People _inherits(Man, _People); // 獲取 Man 的父類的構造函數 var _super = _createSuper(Man); function Man() { _classCallCheck(this, Man); // 實現了父類構造函數的調用, 子類的 this 繼承父類的 this 上的屬性 return _super.apply(this, arguments); } return Man; }(People);
在 _inherits 函數中,實現了原型鏈和靜態屬性的繼承:
// 實現繼承關係 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } // Object.create(proto, propertiesObject) 方法 // 建立一個新對象,使用 proto 來提供新建立的對象的__proto__ // 將 propertiesObject 的屬性添加到新建立對象的不可枚舉(默認)屬性(即其自身定義的屬性,而不是其原型鏈上的枚舉屬性) subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } // 設置對象 o 的原型(即 __proto__ 屬性)爲 p function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
1.經過 Object.create
函數調用可知:
(1)subClass.prototype.__proto__ === superClass.prototype
,至關於實現了原型鏈的繼承
(2)subClass.prototype.constructor === subClass
,代表 subClass 構造函數的顯示原型對象(prototype)的 constructor 屬性指向原構造函數
2.經過調用 _setPrototypeOf(subClass, superClass)
可知:
(1)subClass.__proto__ === superClass
,至關於實現了靜態屬性的繼承
在 Man 構造函數中,經過調用其父類的構造函數(_super),實現了子類的 this 繼承父類的 this 上的屬性:
// 得到父類的構造函數 function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } // 判斷 call 的類型,返回合適的 Constructor function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } // 斷言 selft 是否初始化 function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } // 判斷是否可否使用 Reflect function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } // 獲取 o 對象的原型(__proto__) function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
從上述可知 class 繼承的實現主要包含三部分:
4.class 繼承的原型鏈關係
實例代碼:
class People { constructor(name) { this.name = name } } class Man extends People { constructor(name, sex) { super(name) this.sex = sex } } var man = new Man('Tom', 'M')
根據上面分析所知道的類(class)的繼承的實現原理,並結合 深刻理解JS中的對象(一):原型、原型鏈和構造函數 中所提到的構造函數的原型鏈關係,可得示例代碼的完整原型鏈關係以下圖:
5.參考