ES6中的class是如何實(shí)現(xiàn)的?

今天強(qiáng)行被某大廠社招面試了一波派歌,洋洋灑灑(kengchibiedu)半個(gè)小時(shí)弯囊,以面試官的一句“面試反饋還挺好的痰哨,希望以后保持聯(lián)系”告終。時(shí)間節(jié)點(diǎn)發(fā)生在安安穩(wěn)穩(wěn)回學(xué)校做畢設(shè)的時(shí)候常挚,這場(chǎng)突如其來(lái)的面試就顯得格外刺激作谭,基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)和算法題都回答一二,但是也暴露了對(duì)框架和ES6底層代碼實(shí)現(xiàn)的不求甚解奄毡。不經(jīng)過(guò)刻意準(zhǔn)備的面試,也是原始狀態(tài)的真實(shí)體現(xiàn)贝或。

這篇文章主要講解面試中的一個(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 趟佃、編譯期類(lèi)型檢查器、代碼風(fēng)格檢查器等工具更方便地檢測(cè)代碼語(yǔ)法昧捷,做靜態(tài)分析闲昭。同樣的,這給沒(méi)有類(lèi)就缺點(diǎn)什么的軟件開(kāi)發(fā)工程師一個(gè)更低的門(mén)檻去接觸js靡挥。

ES6 class 的 ES5 代碼實(shí)現(xiàn)

JavaScript語(yǔ)言的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)定義并生成新對(duì)象序矩,這種寫(xiě)法和傳統(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的classconstructor是構(gòu)造方法租幕,對(duì)應(yīng)的是ES5中的構(gòu)造函數(shù)Personthis關(guān)鍵字則代表實(shí)例對(duì)象拧簸。

里面的class類(lèi)可以看做是構(gòu)造函數(shù)的另一種寫(xiě)法劲绪,由typeof Person === 'function'truePerson === Person.prototype.constructortrue可以得出狡恬,類(lèi)的數(shù)據(jù)類(lèi)型就是函數(shù)珠叔,類(lèi)本身指向構(gòu)造函數(shù)。也可以說(shuō)class的底層依然是function構(gòu)造函數(shù)弟劲。

類(lèi)的公共方法都定義在類(lèi)的prototype屬性上祷安。可以使用Object.assign一次向類(lèi)添加多個(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類(lèi)必須使用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ì)象上,否則都是定義在原型上读整。類(lèi)的所有實(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)屬性新的寫(xiě)法米间。

靜態(tài)方法

類(lèi)相當(dāng)于實(shí)例的原型强品,所有在類(lèi)中定義的方法,都會(huì)被實(shí)例繼承屈糊。如果在一個(gè)方法前的榛,加上static關(guān)鍵字,就表示該方法不會(huì)被實(shí)例繼承逻锐,而是直接通過(guò)類(lèi)來(lái)調(diào)用夫晌,這就稱(chēng)為“靜態(tài)方法”。

注意:如果靜態(tài)方法包含this關(guān)鍵字昧诱,指的是類(lèi)晓淀。

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);
    }
}

其他class特性

  • 不存在變量提升
  • 不能覆蓋聲明

ES6 class實(shí)現(xiàn)原理(Babel是如何編譯class的)

下文主要用babel轉(zhuǎn)碼器分別對(duì)class中幾個(gè)主要的方法進(jìn)行轉(zhuǎn)碼妆丘,分析ES5的實(shí)現(xiàn)方式锄俄。

class

下面將實(shí)例二的代碼使用babel轉(zhuǎn)碼器轉(zhuǎn)換成ES5代碼,按照代碼結(jié)構(gòu)和功能分塊進(jìn)行講解勺拣。

  1. 運(yùn)行模式
"use strict";//class默認(rèn)開(kāi)啟嚴(yán)格模式
  1. 私有函數(shù):JS開(kāi)發(fā)者在變量名或函數(shù)名前綴加下劃線奶赠,一般表示私有。

前綴加下劃線表示私有僅僅是一個(gè)約定俗成的習(xí)慣药有,澄清意圖毅戈,并沒(méi)有做其他處理。由于ECMAScript草案中并沒(méi)有定義私有變量的方法愤惰,所以在此限定之下仍可以在函數(shù)外或作用域外訪問(wèn)該函數(shù)或變量苇经。

_instanceof和_classCallCheck的作用
檢查聲明的class類(lèi)是否通過(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è)置 configurabletrue赖淤。以上兩個(gè)布爾值是為了限制 Object.keys() 之類(lèi)的方法被遍歷到。通過(guò)判斷 value 是否存在谅河,來(lái)判斷是否是 getter 和 setter咱旱。如果存在 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;
}
  1. class類(lèi)實(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 Constructorfalse狐粱,拋出異常。
  • 通過(guò)調(diào)用_createClass函數(shù)胆数,遍歷函數(shù)數(shù)組肌蜻。key為方法名,若有value說(shuō)明是有具體的 function 聲明必尼,若無(wú) value 說(shuō)明使用了get 或 set 方法蒋搜。
  • Person類(lèi)是通過(guò)聲明一個(gè)IIFE實(shí)現(xiàn)的,IIFE是立即執(zhí)行函數(shù)判莉,創(chuàng)建即執(zhí)行豆挽。可以保證變量的作用域限制在函數(shù)內(nèi)券盅,避免命名沖突帮哈。

結(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)行解析僵缺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市踩叭,隨后出現(xiàn)的幾起案子磕潮,更是在濱河造成了極大的恐慌,老刑警劉巖容贝,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件自脯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡斤富,警方通過(guò)查閱死者的電腦和手機(jī)膏潮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)满力,“玉大人焕参,你說(shuō)我怎么就攤上這事∮投睿” “怎么了叠纷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)潦嘶。 經(jīng)常有香客問(wèn)我涩嚣,道長(zhǎng),這世上最難降的妖魔是什么掂僵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任航厚,我火速辦了婚禮,結(jié)果婚禮上看峻,老公的妹妹穿的比我還像新娘阶淘。我一直安慰自己,他們只是感情好互妓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布溪窒。 她就那樣靜靜地躺著盗誊,像睡著了一般收叶。 火紅的嫁衣襯著肌膚如雪侈玄。 梳的紋絲不亂的頭發(fā)上稍味,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天驮吱,我揣著相機(jī)與錄音进萄,去河邊找鬼逊拍。 笑死爱咬,一個(gè)胖子當(dāng)著我的面吹牛交胚,可吹牛的內(nèi)容都是我干的份汗。 我是一名探鬼主播盈电,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼杯活!你這毒婦竟也來(lái)了匆帚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤旁钧,失蹤者是張志新(化名)和其女友劉穎吸重,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體歪今,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嚎幸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寄猩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫉晶。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖田篇,靈堂內(nèi)的尸體忽然破棺而出车遂,到底是詐尸還是另有隱情,我是刑警寧澤斯辰,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站坡疼,受9級(jí)特大地震影響彬呻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柄瑰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一闸氮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧教沾,春花似錦蒲跨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至堪唐,卻和暖如春巡语,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背淮菠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工男公, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人合陵。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓枢赔,卻偏偏與公主長(zhǎng)得像澄阳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子踏拜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • class的基本用法 概述 JavaScript語(yǔ)言的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)碎赢,定義并生成新對(duì)象。下面是一個(gè)例子: ...
    呼呼哥閱讀 4,070評(píng)論 3 11
  • Class 的基本語(yǔ)法 簡(jiǎn)介 JavaScript 語(yǔ)言中执隧,生成實(shí)例對(duì)象的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)揩抡。下面是一個(gè)例子。...
    huilegezai閱讀 517評(píng)論 0 0
  • 在ES5繼承的實(shí)現(xiàn)非常有趣的镀琉,由于沒(méi)有傳統(tǒng)面向?qū)ο箢?lèi)的概念峦嗤,Javascript利用原型鏈的特性來(lái)實(shí)現(xiàn)繼承,這其中...
    Daguo閱讀 25,894評(píng)論 10 44
  • 簡(jiǎn)介 JavaScript語(yǔ)言中屋摔,生成實(shí)例對(duì)象的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)烁设。 ES6引入了Class(類(lèi))這個(gè)概念,作...
    oWSQo閱讀 365評(píng)論 0 0
  • 浩言正語(yǔ)077:如果你停止钓试,就是谷底装黑;如果你還在繼續(xù),就是上坡弓熏。這是我知道關(guān)于人生低谷最好的闡述恋谭。(20180802)
    永遠(yuǎn)的浩子閱讀 137評(píng)論 0 1