摘要:中的同名的實際上就是我們在的原型繼承中使用的構(gòu)造函數(shù),所以中的是對中的構(gòu)造函數(shù)的一種包裝。我們發(fā)現(xiàn),在中設(shè)定的屬性被放在的構(gòu)造函數(shù)中,而方法則以鍵值對的形式傳入一個函數(shù)中。大家是不是對這種繼承模式似曾相識呢對了,這就是所謂的構(gòu)造函數(shù)竊取。
ES6中增加了一些新特性,但從底層的角度來說,只是一些語法糖。但是就我個人來說,如果不了解這些語法糖的本質(zhì),是用不安心的。那我們要如何揭開這些語法糖的真實面目呢?
Babel to the rescue! Babel是一款將ES6代碼轉(zhuǎn)換為ES5代碼的編譯器,從而讓我們可以無視瀏覽器的支持,直接享受ES6的新特性。同時,我們也可以通過研究Babel編譯出的ES5代碼,來揭開ES6的面紗。
ES6 ClassesES6中的Classes是在Javascript現(xiàn)有的原型繼承的基礎(chǔ)上引入的一種語法糖。Class語法并沒有引入一種新的繼承模式。它為對象創(chuàng)建和繼承提供了更清晰,易用的語法。
我們用class關(guān)鍵字來創(chuàng)建一個類,constructor關(guān)鍵字定義構(gòu)造函數(shù),用extends關(guān)鍵字來實現(xiàn)繼承,super來實現(xiàn)調(diào)用父類方法。
好,下面是一個ES6 class語法的完整例子:
//定義父類View class View { constructor(options) { this.model = options.model; this.template = options.template; } render() { return _.template(this.template, this.model.toObject()); } } //實例化父類View var view = new View({ template: "Hello, <%= name %>" }); //定義子類LogView,繼承父類View class LogView extends View { render() { var compiled = super.render(); console.log(compiled); } }
這段簡短的代碼就用到了上述的幾個關(guān)鍵詞。class語法的確的簡潔明確,借鑒了主流OO語言的語法,更易于理解。
然而我在用這段代碼時,又有些猶豫。這還是我熟悉的js原型繼承嗎,這真的是同一種繼承模式的一個語法糖嗎?
真相究竟是如何呢?我們就拿babel編譯之后的代碼作為切入口,來看看ES6 class語法的本質(zhì)。
下面是上述ES6代碼用babel編譯之后的結(jié)果:
"use strict"; var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; var _createClass = (function() { 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); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var View = (function() { function View(options) { _classCallCheck(this, View); this.model = options.model; this.template = options.template; } _createClass(View, [{ key: "render", value: function render() { return _.template(this.template, this.model.toObject()); } }]); return View; })(); var LogView = (function(_View) { _inherits(LogView, _View); function LogView() { _classCallCheck(this, LogView); _get(Object.getPrototypeOf(LogView.prototype), "constructor", this).apply(this, arguments); } _createClass(LogView, [{ key: "render", value: function render() { var compiled = _get(Object.getPrototypeOf(LogView.prototype), "render", this).call(this); console.log(compiled); } }]); return LogView; })(View);
這段代碼很長,我們只關(guān)注里面的函數(shù),可以得到它的結(jié)構(gòu)如下:
//用于得到原型鏈上屬性的方法的函數(shù) var _get = function get(_x, _x2, _x3) { //······ } //用于創(chuàng)建對象的函數(shù) var _createClass = (function() { //內(nèi)部函數(shù),定義對象的屬性 function defineProperties(target, props) { //······ } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); //用于實現(xiàn)繼承的函數(shù) function _inherits(subClass, superClass) { //······ subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); var View = (function() { //······ return View; })(); var LogView = (function(_View) { //······ })(View);View類的實現(xiàn)
我們從一個View類的創(chuàng)建開始分析
class View { constructor(options) { this.model = options.model; this.template = options.template; } render() { return _.template(this.template, this.model.toObject()); } } //ES5代碼 var View = (function() { function View(options) { _classCallCheck(this, View); this.model = options.model; this.template = options.template; } _createClass(View, [{ key: "render", value: function render() { return _.template(this.template, this.model.toObject()); } }]); return View; })();
我們從編譯之后的代碼中可以看出,View是一個IIFE,里面是一個同名的函數(shù)View,這個函數(shù)經(jīng)過 _createClass()函數(shù)的處理之后,被返回了。所以我們得出的第一點結(jié)論就是,ES6中的class實際就是函數(shù)。當然這點在各種文檔中已經(jīng)明確了,所以讓我們繼續(xù)分析。
IIFE中的同名的View實際上就是我們在ES5的原型繼承中使用的構(gòu)造函數(shù),所以ES6中的class是對ES5中的構(gòu)造函數(shù)的一種包裝。我們發(fā)現(xiàn),在class中設(shè)定的屬性被放在ES5的構(gòu)造函數(shù)中,而方法則以鍵值對的形式傳入一個_createClass()函數(shù)中。那么這個_createClass()函數(shù)又制造了什么魔法呢?
var _createClass = (function() { 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); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
_createClass也是一個IIFE,有一個內(nèi)部的函數(shù)defineProperties,這個函數(shù)遍歷屬性的描述符,進行描述符的默認設(shè)置,最后使用Object.defineProperty()方法來寫入對象的屬性。IIFE的renturn部分有兩個分支,一個是針對一個類的原型鏈方法,一個是靜態(tài)方法,我們看到原型鏈方法被寫入構(gòu)造函數(shù)的原型對象里,而靜態(tài)方法則被直接寫入構(gòu)造函數(shù)里,因此我們不用實例化對象就可以直接調(diào)用一個類的靜態(tài)方法了。
類繼承的實現(xiàn)js中的函數(shù)是對象,F(xiàn)unction構(gòu)造函數(shù)的prototype指向Object.prototype,因此可以寫入屬性
OK,到目前我們已經(jīng)搞清了ES6的class關(guān)鍵字是如何工作的,那么ES6中的繼承有是如何實現(xiàn)的呢?下面讓我們看看_inherits()函數(shù)。
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
_inherits()函數(shù)的關(guān)鍵部分便是subClass.prototype = Object.create(···)。通過Object.create()方法來指定新創(chuàng)建對象的原型,由此省去了對父類構(gòu)造函數(shù)的處理,達到了簡單的原型繼承效果。
然后我們來看看創(chuàng)建LogView類的代碼:
//ES6 class LogView extends View { render() { var compiled = super.render(); console.log(compiled); } } //ES5 var LogView = (function(_View) { _inherits(LogView, _View); function LogView() { _classCallCheck(this, LogView); _get(Object.getPrototypeOf(LogView.prototype), "constructor", this).apply(this, arguments); } _createClass(LogView, [{ key: "render", value: function render() { var compiled = _get(Object.getPrototypeOf(LogView.prototype), "render", this).call(this); console.log(compiled); } }]); return LogView; })(View);
LogView類和View類的最大不同便是增加了一個_get()函數(shù)的調(diào)用,我們仔細看這個_get()函數(shù)會發(fā)現(xiàn)它接收幾個參數(shù),子類的原型,"constructor"標識符,還有this。再看下面對super.render()的處理,同樣是用_get()函數(shù)來處理的。再看_get()函數(shù)的源碼,就可以發(fā)現(xiàn)_get()函數(shù)的作用便是遍歷對象的原型鏈,找出傳入的標識符對應(yīng)的屬性,把它用apply綁定在當前上下文上執(zhí)行。
大家是不是對這種繼承模式似曾相識呢?對了,這就是所謂的“構(gòu)造函數(shù)竊取”。當我們需要繼承父類的屬性時,在子類的構(gòu)造函數(shù)內(nèi)部調(diào)用父類構(gòu)造函數(shù),在加上Object.create()大法,然后就可以繼承父類的所有屬性和方法了。
結(jié)語以上就是筆者對ES6中的class做的一些解讀,依據(jù)是babel的編譯結(jié)果。今天剛好看到getify大神的一篇博客,將的是ES6中的箭頭函數(shù)其實并不是一個語法糖,而是一種新的不帶this的函數(shù)。他還進一步說了,錯誤的過程得到正確的結(jié)果,會導(dǎo)致很多后續(xù)的問題。
而本文的結(jié)論是建立在class是一種語法糖的基礎(chǔ)之上的,根據(jù)MDN的文檔,我們應(yīng)該可以確認這個結(jié)論是可靠的。而ES6中的特性,如箭頭函數(shù),并不都是語法糖,因此在后續(xù)的ES6探秘文章中,我們將用其他的途徑,來揭示ES6種種神奇魔法的秘密。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/78626.html
摘要:概述約定大于配置的功力讓我們?nèi)玢宕猴L,在我之前寫的文章從到也對比過和這兩個框架,不過最終以超高的代碼信噪比和易上手性讓我們映像頗深。至于,我想在非時代大家應(yīng)該不陌生吧,作用是配置容器,也即形式的容器的配置類所使用。 showImg(https://segmentfault.com/img/remote/1460000015822144); 概 述 SpringBoot 約定大于配置...
摘要:的新特性哪些適合使用我們參考使用進行開發(fā)的思考文章推薦的新特性,僅使用三星的。另外推薦閱讀探秘系列的新特性是否通過轉(zhuǎn)換后還有兼容問題團隊中又同學正在驗證,我們驗證的環(huán)境是,我們最終會使用三星特性加上兼容性的。 showImg(https://segmentfault.com/img/bVrjev); 最近項目中的一個模塊正式引入的ES6,由于是引入新技術(shù),也遇到了一些問題,下面分享下整...
摘要:接下來我們來看看源碼中的模塊是怎么應(yīng)用中間件的。如何實現(xiàn)中間件操作的。新的會從第一個中間件開始觸發(fā),這樣,在我們調(diào)用的時候,就會將中間件走一遍了。函數(shù)如果存在多個中間件,直接使用方法將各個中間件嵌套起來。 從redux-thunk引出思考 在使用redux-thunk進行異步action書寫的時候,我經(jīng)常好奇redux到底如何運作,讓asyncAction成為可能 為了探究,我們必須看...
摘要:使用新的易用的類定義,歸根結(jié)底也是要創(chuàng)建構(gòu)造函數(shù)和修改原型。首先,它把構(gòu)造函數(shù)當成單獨的函數(shù)且包含類屬性集。該節(jié)點還儲存了指向父類的指針引用,該父類也并儲存了構(gòu)造函數(shù),屬性集和及父類引用,依次類推。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第...
摘要:使用新的易用的類定義,歸根結(jié)底也是要創(chuàng)建構(gòu)造函數(shù)和修改原型。首先,它把構(gòu)造函數(shù)當成單獨的函數(shù)且包含類屬性集。該節(jié)點還儲存了指向父類的指針引用,該父類也并儲存了構(gòu)造函數(shù),屬性集和及父類引用,依次類推。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第...
閱讀 2491·2021-11-25 09:43
閱讀 2585·2021-11-16 11:50
閱讀 3280·2021-10-09 09:44
閱讀 3193·2021-09-26 09:55
閱讀 2834·2019-08-30 13:50
閱讀 1026·2019-08-29 13:24
閱讀 2068·2019-08-26 11:44
閱讀 2790·2019-08-26 11:37