ES6中的class是如何實(shí)現(xiàn)的(附Babel編譯的ES5代碼詳解)
序言
這篇文章主要講解面試中的一個(gè)問(wèn)題 - ES6中的class語(yǔ)法的實(shí)現(xiàn)?
ECMAScript 6 實(shí)現(xiàn)了class,class是一個(gè)語(yǔ)法糖,使得js的編碼更清晰、更人性化、風(fēng)格更接近面向?qū)ο蟮母杏X(jué);也使 IDE 、編譯期類型檢查器、代碼風(fēng)格檢查器等工具更方便地檢測(cè)代碼語(yǔ)法,做靜態(tài)分析。同樣的,這給沒(méi)有類就缺點(diǎn)什么的軟件開(kāi)發(fā)工程師一個(gè)更低的門檻去接觸js。
ES6 class 的 ES5 代碼實(shí)現(xiàn)
JavaScript語(yǔ)言的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)定義并生成新對(duì)象,這種寫法和傳統(tǒng)的面向?qū)ο笳Z(yǔ)言差異較大。所以,ES6引入了Class這個(gè)概念作為對(duì)象的模板。
constructor
效果:ES6創(chuàng)建一個(gè)class會(huì)默認(rèn)添加constructor方法,并在new調(diào)用時(shí)自動(dòng)調(diào)用該方法。
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是構(gòu)造方法,對(duì)應(yīng)的是ES5中的構(gòu)造函數(shù)Person,this關(guān)鍵字則代表實(shí)例對(duì)象。
里面的class類可以看做是構(gòu)造函數(shù)的另一種寫法,由typeof Person === 'function'為true;Person === Person.prototype.constructor為true可以得出,類的數(shù)據(jù)類型就是函數(shù),類本身指向構(gòu)造函數(shù)。也可以說(shuō)class的底層依然是function構(gòu)造函數(shù)。
類的公共方法都定義在類的prototype屬性上??梢允褂肙bject.assign一次向類添加多個(gè)方法。
特別的:class的內(nèi)部定義的方法都是不可枚舉的(non-enumerable),這一點(diǎn)與ES5的行為不一致。
ES5:
Object.keys(Person.prototype); // ['toString']
ES6:
Object.keys(Person.prototype); // Person {}
不可枚舉的代碼實(shí)現(xiàn)會(huì)在后面將ES6代碼用Babel轉(zhuǎn)碼之后解析。
new調(diào)用
效果:class類必須使用new調(diào)用,否則會(huì)報(bào)錯(cuò)。
ES5:
Person()// undefined
ES6:
Person() // TypeError: Class constructor Person cannot be invoked without 'new'
實(shí)例的屬性
效果:實(shí)例的屬性是顯式定義在this對(duì)象上,否則都是定義在原型上。類的所有實(shí)例共享一個(gè)原型對(duì)象,與ES5行為一致。
ES5:
function Person() { this.grade = { count: 0 }; }
ES6:
class Person { constructor() { this.grade = { count: 0 }; } }
此外還要關(guān)注新提案,Babel已經(jīng)支持實(shí)例屬性和靜態(tài)屬性新的寫法。
靜態(tài)方法
類相當(dāng)于實(shí)例的原型,所有在類中定義的方法,都會(huì)被實(shí)例繼承。如果在一個(gè)方法前,加上static關(guān)鍵字,就表示該方法不會(huì)被實(shí)例繼承,而是直接通過(guò)類來(lái)調(diào)用,這就稱為“靜態(tài)方法”。
注意:如果靜態(tài)方法包含this關(guān)鍵字,指的是類。
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 關(guān)鍵字,對(duì)某個(gè)屬性設(shè)置存值函數(shù)和取值函數(shù),攔截該屬性的存取行為,和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 底層實(shí)現(xiàn)原理
下文主要用babel轉(zhuǎn)碼器分別對(duì)class中幾個(gè)主要的方法進(jìn)行轉(zhuǎn)碼,分析ES5的實(shí)現(xiàn)方式。
Babel是如何編譯class的
將下面的代碼使用babel轉(zhuǎn)碼器轉(zhuǎn)換成ES5代碼,按照代碼結(jié)構(gòu)和功能分塊進(jìn)行講解。
class Person { constructor(name, age) { this.name = name; this.age = age; } toString() { return '(' + this.name + ',' + this.age + ')'; } } var p = new Person('Mia', 18);
運(yùn)行模式
"use strict";//class默認(rèn)開(kāi)啟嚴(yán)格模式
私有函數(shù):
JS開(kāi)發(fā)者在變量名或函數(shù)名前綴加下劃線,一般表示私有。
前綴加下劃線表示私有僅僅是一個(gè)約定俗成的習(xí)慣,澄清意圖,并沒(méi)有做其他處理。由于ECMAScript草案中并沒(méi)有定義私有變量的方法,所以在此限定之下仍可以在函數(shù)外或作用域外訪問(wèn)該函數(shù)或變量。
_instanceof和_classCallCheck的作用
檢查聲明的class類是否通過(guò)new的方式調(diào)用,否則會(huì)報(bào)錯(cuò)。
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函數(shù)有三個(gè)參數(shù),Constructor是傳入構(gòu)造函數(shù)Person,protoProps 是要添加到原型上的函數(shù)數(shù)組,staticProps 是要添加到構(gòu)造函數(shù)本身的函數(shù),即靜態(tài)方法。這里的第二個(gè)和第三個(gè)參數(shù)是可以缺省的,會(huì)在_createClass 函數(shù)體內(nèi)判斷。
_createClass 函數(shù)的作用是收集公有函數(shù)和靜態(tài)方法,將方法添加到構(gòu)造函數(shù)或構(gòu)造函數(shù)的原型中,并返回構(gòu)造函數(shù)。
defineProperties 是將方法添加到構(gòu)造函數(shù)或構(gòu)造函數(shù)的原型中的主要邏輯,遍歷函數(shù)數(shù)組,分別聲明其描述符。若enumerable 沒(méi)有被定義為true,則默認(rèn)為fals,設(shè)置 configurable 為true。以上兩個(gè)布爾值是為了限制 Object.keys() 之類的方法被遍歷到。如果存在 value,就為 descriptor 添加 value 和 writable 屬性,如果不存在,就直接使用 get 和 set 屬性。
最后,使用 Object.defineProperty 方法為構(gòu)造函數(shù)添加屬性。
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類實(shí)現(xiàn)
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調(diào)用時(shí),this指向window,所以instance instanceof Constructor為false,拋出異常。
通過(guò)調(diào)用_createClass函數(shù),遍歷函數(shù)數(shù)組。key為方法名,若有value說(shuō)明是有具體的 function 聲明,若無(wú) value 說(shuō)明使用了get 或 set 方法。
結(jié)尾
讀到這相信大家對(duì)class的實(shí)現(xiàn)有了更深的理解。最近筆者一邊在忙畢業(yè)設(shè)計(jì),一邊整理了這道阿里前端面試題的解析,評(píng)論區(qū)歡迎對(duì)class實(shí)現(xiàn)這一問(wèn)題進(jìn)行討論。另外,class中的extend也是很有趣的實(shí)現(xiàn),在下一篇文章會(huì)對(duì)class實(shí)現(xiàn)繼承進(jìn)行解析。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SVG動(dòng)畫vivus.js庫(kù)使用小結(jié)(實(shí)例代碼)
本文通過(guò)代碼給大家介紹SVG動(dòng)畫vivus.js庫(kù)使用小結(jié),代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-09-09JavaScript數(shù)據(jù)類型轉(zhuǎn)換實(shí)例(其他類型轉(zhuǎn)字符串、數(shù)值型、布爾類型)
這篇文章主要給大家介紹了關(guān)于JavaScript數(shù)據(jù)類型轉(zhuǎn)換的相關(guān)資料,本文分別介紹了其他類型轉(zhuǎn)為字符串、其他類型轉(zhuǎn)為數(shù)值型以及其他類型轉(zhuǎn)為布爾類型的方法,需要的朋友可以參考下2021-07-07JS遍歷數(shù)組和對(duì)象的區(qū)別及遞歸遍歷對(duì)象、數(shù)組、屬性的方法詳解
本文給大家js遍歷數(shù)組和遍歷對(duì)象的區(qū)別,一般來(lái)說(shuō)for用來(lái)遍歷數(shù)組對(duì)象而for-in用來(lái)遍歷非數(shù)組對(duì)象。接下來(lái)小編給大家?guī)?lái)了js遍歷數(shù)組和對(duì)象的區(qū)別及js遞歸遍歷對(duì)象、數(shù)組、屬性的方法詳解,一起看下吧2016-06-06微信小程序?qū)崿F(xiàn)手機(jī)振動(dòng)效果
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)手機(jī)振動(dòng)效果,手機(jī)振動(dòng)應(yīng)用場(chǎng)景以 wx.vibrateLong() 方法為例結(jié)合實(shí)例代碼給大家詳細(xì)講解,需要的朋友可以參考下2023-07-07AJAX使用了UpdatePanel后無(wú)法使用alert彈出腳本
今天在做項(xiàng)目的時(shí)候發(fā)現(xiàn)個(gè)問(wèn)題。。使用UpdatePanel后alert腳本就要報(bào)錯(cuò)了。2010-04-04Jquery和JS用外部變量獲取Ajax返回的參數(shù)值的方法實(shí)例(超簡(jiǎn)單)
Jquery和JS用外部變量獲取Ajax返回的參數(shù)值的方法實(shí)例(超簡(jiǎn)單),需要的朋友可以參考一下2013-06-06