摘要:下面是用實現(xiàn)轉(zhuǎn)成抽象語法樹如下還支持繼承以下是轉(zhuǎn)換結(jié)果最終的結(jié)果還是代碼,其中包含庫中的一些函數(shù)??梢允褂眯碌囊子谑褂玫念惗x,但是它仍然會創(chuàng)建構(gòu)造函數(shù)和分配原型。
這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 15 篇。
想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你!
如果你錯過了前面的章節(jié),可以在這里找到它們:
JavaScript 是如何工作的:引擎,運行時和調(diào)用堆棧的概述!
JavaScript 是如何工作的:深入V8引擎&編寫優(yōu)化代碼的5個技巧!
JavaScript 是如何工作的:內(nèi)存管理+如何處理4個常見的內(nèi)存泄漏 !
JavaScript 是如何工作的:事件循環(huán)和異步編程的崛起+ 5種使用 async/await 更好地編碼方式!
JavaScript 是如何工作的:深入探索 websocket 和HTTP/2與SSE +如何選擇正確的路徑!
JavaScript 是如何工作的:與 WebAssembly比較 及其使用場景 !
JavaScript 是如何工作的:Web Workers的構(gòu)建塊+ 5個使用他們的場景!
JavaScript 是如何工作的:Service Worker 的生命周期及使用場景!
JavaScript 是如何工作的:Web 推送通知的機制!
JavaScript是如何工作的:使用 MutationObserver 跟蹤 DOM 的變化!
JavaScript是如何工作的:渲染引擎和優(yōu)化其性能的技巧!
JavaScript是如何工作的:深入網(wǎng)絡(luò)層 + 如何優(yōu)化性能和安全!
JavaScript是如何工作的:CSS 和 JS 動畫底層原理及如何優(yōu)化它們的性能!
JavaScript是如何工作的:解析、抽象語法樹(AST)+ 提升編譯速度5個技巧!
現(xiàn)在構(gòu)建任何類型的軟件項目最流行的方法這是使用類。在這篇文章中,探討用 JavaScript 實現(xiàn)類的不同方法,以及如何構(gòu)建類的結(jié)構(gòu)。首先從深入研究原型工作原理,并分析在流行庫中模擬基于類的繼承的方法。 接下來是講如何將新的語法轉(zhuǎn)制為瀏覽器識別的語法,以及在 Babel 和 TypeScript 中使用它來引入ECMAScript 2015類的支持。最后,將以一些在 V8 中如何本機實現(xiàn)類的示例來結(jié)束本文。
概述在 JavaScript 中,沒有基本類型,創(chuàng)建的所有東西都是對象。例如,創(chuàng)建一個新字符串:
const name = "SessionStack";
接著在新創(chuàng)建的對象上調(diào)用不同的方法:
console.log(a.repeat(2)); // SessionStackSessionStack console.log(a.toLowerCase()); // sessionstack
與其他語言不同,在 JavaScript 中,字符串或數(shù)字的聲明會自動創(chuàng)建一個封裝值的對象,并提供不同的方法,甚至可以在基本類型上執(zhí)行這些方法。
另一個有趣的事實是,數(shù)組等復雜類型也是對象。如果檢查數(shù)組實例的類型,你將看到它是一個對象。列表中每個元素的索引只是對象中的屬性。當通過數(shù)組中的索引訪問一個元素時,實際上是訪問了數(shù)組對象的一個 key 值,并得到 key 對應的值。從數(shù)據(jù)的存儲方式看時,這兩個定義是相同的:
let names = [“SessionStack”]; let names = { “0”: “SessionStack”, “l(fā)ength”: 1 }
因此,訪問數(shù)組中的元素和對象的屬性耗時是相同的。我(本文作者)通過多次的努力才發(fā)現(xiàn)這一點的。就是不久,我(本文作者)不得不對項目中的一段關(guān)鍵代碼進行大規(guī)模優(yōu)化。在嘗試了所有簡單的可選項之后,最后用數(shù)組替換了項目中使用的所有對象。理論上,訪問數(shù)組中的元素比訪問哈希映射中的鍵要快且對性能沒有任何影響。在 JavaScript中,這兩種操作都是作為訪問哈希映射中的鍵來實現(xiàn)的,并且花費相同的時間。
使用原型模擬類一般的想到對象時,首先想到的是類。我們大都習慣于根據(jù)類及其之間的關(guān)系來構(gòu)建應用程序。盡管 JavaScript 中的對象無處不在,但該語言并不使用傳統(tǒng)的基于類的繼承,相反,它依賴于原型來實現(xiàn)。
在 JavaScript 中,每個對象通過原型連接著另一個對象。當嘗試訪問對象上的屬性或方法時,首先從對象本身開始查找,如果沒有找到任何內(nèi)容,則在對象的原型中繼續(xù)查找。
從一個簡單的例子開始:
function Component(content) { this.content = content; } Component.prototype.render = function() { console.log(this.content); }
在 Component 的原型上添加 render 方法,因為希望 Component 的每個實例都能有 render 方法。Component 任何實例調(diào)用此方法時,首先將在實例本身中執(zhí)行查找,如果沒有,接著從它的原型中執(zhí)行查找。
接著引入一個新的子類:
function InputField(value) { this.content = ``; }
如果想要 InputField 繼承 Component 并能夠調(diào)用它的 render 方法,就需要更改它的原型。當對子類的實例調(diào)用 render 方法時,不希望在它的空原型中查找,而應該從從 Component 上的原型查找:
InputField.prototype = Object.create(new Component());
通過這種方式,就可以在 Component 的原型中找到 render 方法。為了實現(xiàn)繼承,需要將 InputField 的原型連接到 Component 的實例上,大多數(shù)庫都使用 Object.setPrototypeOf 方法來實現(xiàn)這一點。
然而,這不是唯一一件事要做的,每次繼承一個類,需要:
將子類的原型指向父類的實例。
在子類構(gòu)造函數(shù)中調(diào)用的父構(gòu)造函數(shù),完成父構(gòu)造函數(shù)中的初始化邏輯。
如上所述,如果希望繼承基類的的所有特性,那么每次都需要執(zhí)行這個復雜的邏輯。當創(chuàng)建多個類時,將邏輯封裝在可重用函數(shù)中是有意義的。這就是開發(fā)人員最初解決基于類繼承的方法——通過使用不同的庫來模擬它。
這些解決方案越來越流行,造成了 JS 中明顯缺少了一些類型的現(xiàn)象。這就是為什么在 ECMAScript 2015 的第一個主要版本中引入了類,繼承的新語法。
類的轉(zhuǎn)換當 ES6 或 ECMAScript 2015 中的新特性被提出時,JavaScript 開發(fā)人員不能等待所有引擎和瀏覽器都開始支持它們。為實現(xiàn)瀏覽器能夠支持新的特性一個好方法是通過 轉(zhuǎn)換 (Transpiling) ,它允許將 ECMAScript 2015 中編寫的代碼轉(zhuǎn)換成任何瀏覽器都能理解的 JavaScript 代碼,當然也包括使用基于類的繼承編寫類的轉(zhuǎn)換功能。
Babel最流行的 JavaScript 編譯器之一就是 Babel,宏觀來說,它分3個階段運行代碼:解析(parsing),轉(zhuǎn)譯(transforming),生成(generation),來看看它是如何轉(zhuǎn)換的:
class Component { constructor(content) { this.content = content; } render() { console.log(this.content) } } const component = new Component("SessionStack"); component.render();
以下是 Babel 轉(zhuǎn)換后的樣式:
var Component = function () { function Component(content) { _classCallCheck(this, Component); this.content = content; } _createClass(Component, [{ key: "render", value: function render() { console.log(this.content); } }]); return Component; }();
如上所見,轉(zhuǎn)換后的代碼就可在任何瀏覽器執(zhí)行了。 此外,還添加了一些功能, 這些是 Babel 標準庫的一部分。
_classCallCheck 和_createClass 作為函數(shù)包含在編譯文件中。
_classCallCheck 函數(shù)的作用在于確保構(gòu)造方法永遠不會作為函數(shù)被調(diào)用,它會評估函數(shù)的上下文是否為 Component 對象的實例,以此確定是否需要拋出異常。
_createClass 用于處理創(chuàng)建對象屬性,函數(shù)支持傳入構(gòu)造函數(shù)與需定義的鍵值對屬性數(shù)組。函數(shù)判斷傳入的參數(shù)(普通方法/靜態(tài)方法)是否為空對應到不同的處理流程上。
為了探究繼承的實現(xiàn)原理,分析繼承的 Component 的 InputField 類。。
class InputField extends Component { constructor(value) { const content = ``; super(content); } }
使用 Babel 處理上述代碼,得到如下代碼:
var InputField = function (_Component) { _inherits(InputField, _Component); function InputField(value) { _classCallCheck(this, InputField); var content = ""; return _possibleConstructorReturn(this, (InputField.__proto__ || Object.getPrototypeOf(InputField)).call(this, content)); } return InputField; }(Component);
在本例中, Babel 創(chuàng)建了 _inherits 函數(shù)幫助實現(xiàn)繼承。
以 ES6 轉(zhuǎn) ES5 為例,具體過程:
編寫ES6代碼
babylon 進行解析
解析得到 AST
plugin 用 babel-traverse 對 AST 樹進行遍歷轉(zhuǎn)譯
得到新的 AST樹
用 babel-generator 通過 AST 樹生成 ES5 代碼
Babel 中的抽象語法樹AST 包含多個節(jié)點,且每個節(jié)點只有一個父節(jié)點。 在 Babel 中,每個形狀樹的節(jié)點包含可視化類型、位置、在樹中的連接等信息。 有不同類型的節(jié)點,如 string,numbers,null等,還有用于流控制(if)和循環(huán)(for,while)的語句節(jié)點。 并且還有一種特殊類型的節(jié)點用于類。它是基節(jié)點類的一個子節(jié)點,通過添加字段來擴展它,以存儲對基類的引用和作為多帶帶節(jié)點的類的主體。
把下面的代碼片段轉(zhuǎn)換成一個抽象語法樹:
class Component { constructor(content) { this.content = content; } render() { console.log(this.content) } }
下面是以下代碼片段的抽象語法樹:
Babel 的三個主要處理步驟分別是: 解析(parse),轉(zhuǎn)換 (transform),生成 (generate)。
解析將代碼解析成抽象語法樹(AST),每個js引擎(比如Chrome瀏覽器中的V8引擎)都有自己的AST解析器,而Babel是通過 Babylon 實現(xiàn)的。在解析過程中有兩個階段: 詞法分析 和 語法分析 ,詞法分析階段把字符串形式的代碼轉(zhuǎn)換為 令牌 (tokens)流,令牌類似于AST中節(jié)點;而語法分析階段則會把一個令牌流轉(zhuǎn)換成 AST的形式,同時這個階段會把令牌中的信息轉(zhuǎn)換成AST的表述結(jié)構(gòu)。
轉(zhuǎn)換在這個階段,Babel接受得到AST并通過babel-traverse對其進行 深度優(yōu)先遍歷,在此過程中對節(jié)點進行添加、更新及移除操作。這部分也是Babel插件介入工作的部分。
生成將經(jīng)過轉(zhuǎn)換的AST通過babel-generator再轉(zhuǎn)換成js代碼,過程就是 深度優(yōu)先遍歷整個AST,然后構(gòu)建可以表示轉(zhuǎn)換后代碼的字符串。
在上面的示例中,首先生成兩個 MethodDefinition 節(jié)點的代碼,然后生成類主體節(jié)點的代碼,最后生成類聲明節(jié)點的代碼。
使用 TypeScript 進行轉(zhuǎn)換另一個利用轉(zhuǎn)換的流行框架是 TypeScript。它引入了一種用于編寫 JavaScript 應用程序的新語法,該語法被轉(zhuǎn)換為任何瀏覽器或引擎都可以執(zhí)行的 EMCAScript 5。下面是用 Typescript 實現(xiàn) Component :
class Component { content: string; constructor(content: string) { this.content = content; } render() { console.log(this.content) } }
轉(zhuǎn)成抽象語法樹如下:
Typescript 還支持繼承:
class InputField extends Component { constructor(value: string) { const content = ``; super(content); } }
以下是轉(zhuǎn)換結(jié)果:
var InputField = /** @class */ (function (_super) { __extends(InputField, _super); function InputField(value) { var _this = this; var content = ""; _this = _super.call(this, content) || this; return _this; } return InputField; }(Component));
最終的結(jié)果還是 ECMAScript 5 代碼,其中包含 TypeScript 庫中的一些函數(shù)。封 __extends 中的邏輯與在第一節(jié)中討論的邏輯相同。
隨著 Babel 和 TypeScript 被廣泛采用,標準類和基于類的繼承成為了構(gòu)造 JavaScript 應用程序的標準方式,這推動了在瀏覽器中引入對類的原生支持。
類的原生支持2014年,Chrome 引入了對 類的原生支持,這允許在不需要任何庫或轉(zhuǎn)換器的情況下執(zhí)行類聲明語法。
本地實現(xiàn)類的過程就是我們所說的語法糖。這只是一種奇特的語法,它可以編譯成語言中已經(jīng)支持的相同的原語。可以使用新的易于使用的類定義,但是它仍然會創(chuàng)建構(gòu)造函數(shù)和分配原型。
V8的支持撯著,看看在 V8 中對 ECMAScript 2015 類的本機支持的工作原理。正如在 前一篇文章 中所討論的,首先必須將新語法解析為有效的 JavaScript 代碼并添加到 AST 中,因此,作為類定義的結(jié)果,一個具有ClassLiteral 類型的新節(jié)點被添加到樹中。
這個節(jié)點存儲了一些信息。首先,它將構(gòu)造函數(shù)作為一個多帶帶的函數(shù)保存,還保存類屬性的列表,這些屬性包括 方法、getter、setter、公共字段或私有字段。該節(jié)點還存儲對父類的引用,該類將繼承父類,而父類將再次存儲構(gòu)造函數(shù)、屬性列表和父類。
一旦這個新的類 ClassLiteral 被 轉(zhuǎn)換成代碼,它又被轉(zhuǎn)換成函數(shù)和原型。
原文:
https://blog.sessionstack.com...
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行l(wèi)og 調(diào)試,這邊順便給大家推薦一個好用的BUG監(jiān)控工具 Fundebug。
你的點贊是我持續(xù)分享好東西的動力,歡迎點贊!
交流干貨系列文章匯總?cè)缦?,覺得不錯點個Star,歡迎 加群 互相學習。
https://github.com/qq44924588...
我是小智,公眾號「大遷世界」作者,對前端技術(shù)保持學習愛好者。我會經(jīng)常分享自己所學所看的干貨,在進階的路上,共勉!
關(guān)注公眾號,后臺回復福利,即可看到福利,你懂的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/101334.html
摘要:使用新的易用的類定義,歸根結(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 工作原理的第...
摘要:為了方便大家共同學習,整理了之前博客系列的文章,目前已整理是如何工作這個系列,可以請猛戳博客查看。以下列出該系列目錄,歡迎點個星星,我將更友動力整理理優(yōu)質(zhì)的文章,一起學習。 為了方便大家共同學習,整理了之前博客系列的文章,目前已整理 JavaScript 是如何工作這個系列,可以請猛戳GitHub博客查看。 以下列出該系列目錄,歡迎點個星星,我將更友動力整理理優(yōu)質(zhì)的文章,一起學習。 J...
摘要:在他的重學前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學習。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。 開篇 前端開發(fā)是一個非常特殊的行業(yè),它的歷史實際上不是很長,但是知識之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研...
摘要:在他的重學前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學習。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。開篇 前端開發(fā)是一個非常特殊的行業(yè),它的歷史實際上不是很長,但是知識之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系...
閱讀 2533·2021-10-12 10:11
閱讀 1285·2021-10-11 10:58
閱讀 3346·2019-08-30 15:54
閱讀 780·2019-08-30 13:59
閱讀 726·2019-08-29 13:07
閱讀 1472·2019-08-26 11:55
閱讀 2204·2019-08-26 10:44
閱讀 2765·2019-08-23 18:25