這篇文章主要講解面試某大廠遇到的一個問題 - ES6中的class語法的實現?前端
ECMAScript 6 實現了class,class是一個語法糖,使得js的編碼更清晰、更人性化、風格更接近面向對象的感受;也使 IDE 、編譯期類型檢查器、代碼風格檢查器等工具更方便地檢測代碼語法,作靜態分析。一樣的,這給沒有類就缺點什麼的軟件開發工程師一個更低的門檻去接觸js。面試
JavaScript語言的傳統方法是經過構造函數定義並生成新對象,這種寫法和傳統的面嚮對象語言差別較大。因此,ES6引入了Class這個概念做爲對象的模板。數組
效果:ES6建立一個class會默認添加constructor
方法,並在new調用時自動調用該方法。bash
ES5:babel
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.toString = function () {
return '(' + this.name + ',' + this.age + ')';
}
var p = new Person('Mia', 18);
console.log(p);// Person { name: 'Mia', age: 18 }
複製代碼
ES6:函數
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
toString() {
return '(' + this.name + ',' + this.age + ')';
}
}
var p = new Person('Mia', 18);
console.log(p);// Person { name: 'Mia', age: 18 }
複製代碼
ES6的class
中constructor
是構造方法,對應的是ES5中的構造函數Person
,this
關鍵字則表明實例對象。工具
裏面的class
類能夠看作是構造函數的另外一種寫法,由typeof Person === 'function'
爲true
;Person === Person.prototype.constructor
爲true
能夠得出,類的數據類型就是函數,類自己指向構造函數。也能夠說class的底層依然是function構造函數。ui
類的公共方法都定義在類的prototype屬性上。可使用
Object.assign
一次向類添加多個方法。this
特別的:class的內部定義的方法都是不可枚舉的(non-enumerable),這一點與ES5的行爲不一致。編碼
ES5:
Object.keys(Person.prototype); // ['toString']
複製代碼
ES6:
Object.keys(Person.prototype); // Person {}
複製代碼
不可枚舉的代碼實現會在後面將ES6代碼用Babel轉碼以後解析。
效果:class類必須使用new調用,不然會報錯。
ES5:
Person()// undefined
複製代碼
ES6:
Person() // TypeError: Class constructor Person cannot be invoked without 'new'
複製代碼
效果:實例的屬性是顯式定義在this對象上,不然都是定義在原型上。類的全部實例共享一個原型對象,與ES5行爲一致。
ES5:
function Person() {
this.grade = {
count: 0
};
}
複製代碼
ES6:
class Person {
constructor() {
this.grade = {
count: 0
};
}
}
複製代碼
此外還要關注新提案,Babel已經支持實例屬性和靜態屬性新的寫法。
類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。若是在一個方法前,加上static
關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。
注意:若是靜態方法包含
this
關鍵字,指的是類。
ES5:
function Person() { }
Person.toSay = function () {
return 'I love JavaScript.';
};
Person.toSay(); // I love JavaScript.
複製代碼
ES6:
class Person {
static toSay() {
return 'I love JavaScript.';
}
}
Person.toSay(); // I love JavaScript.
複製代碼
ES6提供 get
和 set
關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲,和ES5行爲一致。
ES5:
function Person(name) {}
Person.prototype = {
get name() {
return 'mia';
},
set name(newName) {
console.log('new name:' + newName);
}
}
複製代碼
ES6:
class Person {
get name() {
return 'mia';
}
set name(newName) {
console.log('new name:' + newName);
}
}
複製代碼
其餘class特性:
下文主要用babel轉碼器分別對class中幾個主要的方法進行轉碼,分析ES5的實現方式。 將下面的代碼使用babel轉碼器轉換成ES5代碼,按照代碼結構和功能分塊進行講解。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
toString() {
return '(' + this.name + ',' + this.age + ')';
}
}
var p = new Person('Mia', 18);
複製代碼
"use strict";//class默認開啓嚴格模式
複製代碼
JS開發者在變量名或函數名前綴加下劃線,通常表示私有。
前綴加下劃線表示私有僅僅是一個約定俗成的習慣,澄清意圖,並無作其餘處理。因爲ECMAScript草案中並無定義私有變量的方法,因此在此限定之下仍能夠在函數外或做用域外訪問該函數或變量。
_instanceof和_classCallCheck的做用
檢查聲明的class類是否經過new的方式調用,不然會報錯。
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");
}
}
複製代碼
_createClass和_defineProperties的做用
_createClass函數有三個參數,Constructor是傳入構造函數Person,protoProps 是要添加到原型上的函數數組,staticProps 是要添加到構造函數自己的函數,即靜態方法。這裏的第二個和第三個參數是能夠缺省的,會在_createClass 函數體內判斷。
_createClass 函數的做用是收集公有函數和靜態方法,將方法添加到構造函數或構造函數的原型中,並返回構造函數。
defineProperties 是將方法添加到構造函數或構造函數的原型中的主要邏輯,遍歷函數數組,分別聲明其描述符。若enumerable
沒有被定義爲true
,則默認爲fals
,設置 configurable
爲true
。以上兩個布爾值是爲了限制 Object.keys()
之類的方法被遍歷到。若是存在 value
,就爲 descriptor
添加 value
和 writable
屬性,若是不存在,就直接使用 get
和 set
屬性。
最後,使用 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 Person =
/*#__PURE__*/
function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [{
key: "toString",
value: function toString() {
return '(' + this.name + ',' + this.age + ')';
}
}]);
return Person;
}();
var p = new Person('Mia', 18);
複製代碼
解析:
instance instanceof Constructor
爲false
,拋出異常。讀到這相信你們對class的實現有了更深的理解。筆者也是一邊在忙畢業設計,一邊整理了這道阿里前端面試題的解析。但願你們有所收穫。
評論區歡迎對ES6 class實現原理相關問題進行討論。另外,class中的extend
也是頗有趣的實現,在下一篇文章會對class實現繼承進行解析。