您好,登錄后才能下訂單哦!
序言
這篇文章主要講解面試中的一個問題 - ES6中的class語法的實現?
ECMAScript 6 實現了class,class是一個語法糖,使得js的編碼更清晰、更人性化、風格更接近面向對象的感覺;也使 IDE 、編譯期類型檢查器、代碼風格檢查器等工具更方便地檢測代碼語法,做靜態分析。同樣的,這給沒有類就缺點什么的軟件開發工程師一個更低的門檻去接觸js。
ES6 class 的 ES5 代碼實現
JavaScript語言的傳統方法是通過構造函數定義并生成新對象,這種寫法和傳統的面向對象語言差異較大。所以,ES6引入了Class這個概念作為對象的模板。
constructor
效果:ES6創建一個class會默認添加constructor方法,并在new調用時自動調用該方法。
ES5:
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構造函數。
類的公共方法都定義在類的prototype屬性上。可以使用Object.assign一次向類添加多個方法。
特別的:class的內部定義的方法都是不可枚舉的(non-enumerable),這一點與ES5的行為不一致。
ES5:
Object.keys(Person.prototype); // ['toString']
ES6:
Object.keys(Person.prototype); // Person {}
不可枚舉的代碼實現會在后面將ES6代碼用Babel轉碼之后解析。
new調用
效果: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.
getter 和 setter
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); } }
ES6 class 底層實現原理
下文主要用babel轉碼器分別對class中幾個主要的方法進行轉碼,分析ES5的實現方式。
Babel是如何編譯class的
將下面的代碼使用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; }
class類實現
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);
解析:
不使用new調用時,this指向window,所以instance instanceof Constructor為false,拋出異常。
通過調用_createClass函數,遍歷函數數組。key為方法名,若有value說明是有具體的 function 聲明,若無 value 說明使用了get 或 set 方法。
結尾
讀到這相信大家對class的實現有了更深的理解。最近筆者一邊在忙畢業設計,一邊整理了這道阿里前端面試題的解析,評論區歡迎對class實現這一問題進行討論。另外,class中的extend也是很有趣的實現,在下一篇文章會對class實現繼承進行解析。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。