摘要:使用新的易用的類定義,歸根結(jié)底也是要?jiǎng)?chuàng)建構(gòu)造函數(shù)和修改原型。首先,它把構(gòu)造函數(shù)當(dāng)成多帶帶的函數(shù)且包含類屬性集。該節(jié)點(diǎn)還儲(chǔ)存了指向父類的指針引用,該父類也并儲(chǔ)存了構(gòu)造函數(shù),屬性集和及父類引用,依次類推。
原文請查閱這里,略有刪減,本文采用知識(shí)共享署名 4.0 國際許可協(xié)議共享,BY Troland。
本系列持續(xù)更新中,Github 地址請查閱這里。
這是 JavaScript 工作原理的第十五章。
如今使用類來組織各種軟件工程代碼是最常用的方法。本章將會(huì)探索實(shí)現(xiàn) JavaScript 類的不同方法及如何構(gòu)建類繼承。我們將深入理解原型繼承及分析使用流行的類庫模擬實(shí)現(xiàn)基于類繼承的方法。接下來,將會(huì)介紹如何使用轉(zhuǎn)換器為語言添加非原生支持的語法功能和如何在 Babel 和 TypeScript 中運(yùn)用以支持 ECMAScript?2015 類。最后介紹幾個(gè) V8 原生支持實(shí)現(xiàn)類的例子。
概述JavaScript 沒有原始類型且一切皆對象。比如,如下字符串:
const name = "SessionStack";
可以立即調(diào)用新創(chuàng)建對象上的不同方法:
console.log(a.repeat(2)); // 輸出 SessionStackSessionStack console.log(a.toLowerCase()); // 輸出 sessionstack
JavaScript 和其它語言不一樣,聲明一個(gè)字符串或者數(shù)值會(huì)自動(dòng)創(chuàng)建一個(gè)包含值的對象及提供甚至可以在原始類型上運(yùn)行的不同方法。
另外一個(gè)有趣的事實(shí)即諸如數(shù)組的復(fù)雜數(shù)據(jù)類型也是對象。當(dāng)使用 typeof 來檢查一個(gè)數(shù)組實(shí)例的時(shí)候會(huì)輸出 object。數(shù)組中每個(gè)元素的索引值即對象的屬性。所以通過數(shù)組索引來訪問元素的時(shí)候,實(shí)際上是在訪問一個(gè)數(shù)組對象的屬性然后獲得屬性值。當(dāng)涉及到數(shù)據(jù)存儲(chǔ)方式的時(shí)候,以下兩種定義是相同的:
let names = [“SessionStack”]; let names = { “0”: “SessionStack”, “l(fā)ength”: 1 }
因此,訪問數(shù)組元素和對象屬性的速度是一樣的。我走了很多彎路才發(fā)現(xiàn)該事實(shí)。以前有段時(shí)間,我得對項(xiàng)目中某段至關(guān)重要的代碼進(jìn)行大量的性能優(yōu)化。當(dāng)試驗(yàn)過其它簡單的辦法之后,我把所有的對象替換為數(shù)組。按理說,訪問數(shù)組元素會(huì)比訪問哈希圖的鍵值更快。然而,我驚奇地發(fā)現(xiàn)沒有半點(diǎn)性能的提升。在 JavaScript 中,所有的操作都是由訪問哈希圖中的鍵來實(shí)現(xiàn)的且耗時(shí)相同。
使用原型模擬類當(dāng)談到對象的時(shí)候,首先映上眼簾的即類。開發(fā)人員習(xí)慣于使用類和類之間的關(guān)聯(lián)來組織程序。雖然 JavaScript 中一切皆對象,但是并沒有使用經(jīng)典的基于類的繼承。而是使用原型來實(shí)現(xiàn)繼承。
在 JavaScript 中,每個(gè)對象關(guān)聯(lián)其原型對象。當(dāng)訪問對象的一個(gè)方法或?qū)傩缘臅r(shí)候,首先在對象自身進(jìn)行搜索。如果沒有找到,則在對象原型上進(jìn)行查找。
讓我們以定義基礎(chǔ)類的構(gòu)造函數(shù)為例:
function Component(content) { this.content = content; } Component.prototype.render = function() { console.log(this.content); }
在原型上添加 render 函數(shù),這樣 Component 的實(shí)例就可以使用該方法。當(dāng)調(diào)用該 Component 類實(shí)例的方法的時(shí)候,首先在實(shí)例上查詢該方法。然后在原型上找到該渲染方法。
現(xiàn)在,嘗試擴(kuò)展 component 類,引入新的子類。
function InputField(value) { this.content = ``; }
如果想要 InputField 擴(kuò)展 component 類的方法且可以調(diào)用其 render 方法,就需要更改其原型。當(dāng)調(diào)用子類的實(shí)例方法的時(shí)候,肯定不希望在一個(gè)空原型上進(jìn)行查找(這里其實(shí)所有對象都一個(gè)共同的原型,這里原文不夠嚴(yán)謹(jǐn))。該查找會(huì)延續(xù)到 Component 類上。
InputField.prototype = Object.create(new Component());
這樣,就可以在 Component 類的原型上找到 render 方法。為了實(shí)現(xiàn)繼承,需要把 InputField 的原型設(shè)置為Component 類的實(shí)例。大多數(shù)庫使用 Object.setPrototypeOf 來實(shí)現(xiàn)繼承。
然而,還有其它事情需要做。每次擴(kuò)展類,所需要做的事如下:
設(shè)置子類的原型為父類的實(shí)例
在子類的構(gòu)建函數(shù)中調(diào)用父類構(gòu)造函數(shù),這樣才可以執(zhí)行父類構(gòu)造函數(shù)的初始化邏輯。
引入訪問父類的方法。當(dāng)重寫父類方法的時(shí)候,會(huì)想要調(diào)用父類方法的原始實(shí)現(xiàn)。
正如你所見,當(dāng)想要實(shí)現(xiàn)所有基于類繼承的功能的時(shí)候,每次都需要執(zhí)行這么復(fù)雜的邏輯步驟。當(dāng)需要?jiǎng)?chuàng)建這么多類的時(shí)候,即意味著需要把這些邏輯封裝為可重用的函數(shù)。這就是開發(fā)者當(dāng)初通過各種類庫來模擬從而解決基于類的繼承的問題。這些解決方案是如此流行,以至于迫切需要語言集成該功能。這就是為什么 ECMAScript 2015 的第一個(gè)重要修訂版中引入了支持基于類繼承的創(chuàng)建類的語法。
類轉(zhuǎn)換當(dāng)在 ES6 或者 ECMAScript 2015 中提議新功能時(shí),JavaScript 開發(fā)者社區(qū)就迫不及待想要引擎和瀏覽器實(shí)現(xiàn)支持。一種好的實(shí)現(xiàn)方法即通過代碼轉(zhuǎn)換。它允許使用 ECMAScript 2015 來進(jìn)行代碼編寫然后轉(zhuǎn)換為任何瀏覽器均可以運(yùn)行的 JavaScript 代碼。這包括使用基于類的繼承來編寫類并轉(zhuǎn)換為可執(zhí)行代碼。
Babel 是最為流行的轉(zhuǎn)換器之一。讓我們通過 babel 轉(zhuǎn)換 component 類來了解代碼轉(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)換為可在任意環(huán)境中運(yùn)行的 ECMAScript 5 代碼。另外,引入了額外的函數(shù)。它們是 Babel 標(biāo)準(zhǔn)庫的一部分。編譯后的文件中引入了 _classCallCheck 和 _createClass 函數(shù)。第一個(gè)函數(shù)保證構(gòu)造函數(shù)永遠(yuǎn)不會(huì)被當(dāng)成普通函數(shù)調(diào)用。這是通過檢查函數(shù)執(zhí)行上下文是否為一個(gè) Component 對象實(shí)例來實(shí)現(xiàn)的。代碼檢查 this 是否指向這樣的實(shí)例。第二個(gè)函數(shù) _createClass 通過傳入包含鍵和值的對象數(shù)組來創(chuàng)建對象(類)的屬性。
為了理解繼承的工作原理,讓我們分析一下繼承自 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);
本例中,在 _inherits 函數(shù)中封裝了繼承邏輯。它執(zhí)行了前面所說的一樣的操作即設(shè)置子類的原型為父類的實(shí)例。
為了轉(zhuǎn)換代碼,Babel 執(zhí)行了幾次轉(zhuǎn)換。首先,解析 ES6 代碼并轉(zhuǎn)化成被稱為語法抽象樹的中間展示層,語法抽象樹在之前的文章有講過了。該樹會(huì)被轉(zhuǎn)換為一個(gè)不同的語法抽象樹,該樹上每個(gè)節(jié)點(diǎn)會(huì)轉(zhuǎn)換為對應(yīng)的 ECMAScript 5 節(jié)點(diǎn)。最后,把語法抽象樹轉(zhuǎn)換為 ES5 代碼。
Babel 中的語法抽象樹AST 由節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)只有一個(gè)父節(jié)點(diǎn)。Babel 中有一種基礎(chǔ)類型節(jié)點(diǎn)。該節(jié)點(diǎn)包含節(jié)點(diǎn)的內(nèi)容及在代碼中的位置的信息。有各種不同類型的節(jié)點(diǎn)比如字面量表示字符串,數(shù)值,空值等等。也有控制流(if) 和 循環(huán)(for, while)的語句節(jié)點(diǎn)。另外,還有一種特殊類型的類節(jié)點(diǎn)。它是基礎(chǔ)節(jié)點(diǎn)類的子類,通過添加字段變量來存儲(chǔ)基礎(chǔ)類的引用和把類的正文作為多帶帶的節(jié)點(diǎn)來拓展自身。
轉(zhuǎn)化以下代碼片段為語法抽象樹:
class Component { constructor(content) { this.content = content; } render() { console.log(this.content) } }
以下為該代碼片段的語法抽象樹的大概情況:
創(chuàng)建語法抽象樹后,每個(gè)節(jié)點(diǎn)轉(zhuǎn)換為其對應(yīng)的 ECMAScript 5 節(jié)點(diǎn)然后轉(zhuǎn)化為遵循 ECMAScript 5 標(biāo)準(zhǔn)規(guī)范的代碼。這是通過尋找離根節(jié)點(diǎn)最遠(yuǎn)的節(jié)點(diǎn)然后轉(zhuǎn)換為代碼。然后,他們的父節(jié)點(diǎn)通過使用每個(gè)子節(jié)點(diǎn)生成的代碼片段來轉(zhuǎn)化為代碼,依次類推。該過程被稱為 depth-first traversal 即深度優(yōu)先遍歷。
以上示例,首先生成兩個(gè) MethodDefinition 節(jié)點(diǎn),之后類正文節(jié)點(diǎn)的代碼,最后是 ClassDeclaration 節(jié)點(diǎn)的代碼。
使用 TypeScript 進(jìn)行轉(zhuǎn)換TypeScript 是另一個(gè)流行的框架。它引入了一種編寫 JavaScript 程序的新語法,然后轉(zhuǎn)換為任意瀏覽器或引擎可以運(yùn)行的 EMCAScript 5 代碼。以下為使用 Typescript 實(shí)現(xiàn) component 類的代碼:
class Component { content: string; constructor(content: string) { this.content = content; } render() { console.log(this.content) } }
以下為語法抽象樹示意圖:
同樣支持繼承。
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é)果包含了一些來自 TypeScript 的類庫代碼。__extends 中封裝了和之前第一部分討論的一樣的繼承邏輯。
隨著 Babel 和 TypeScript 的廣泛使用,標(biāo)準(zhǔn)類和基于類的繼承漸漸成為組織 JavaScript 程序的標(biāo)準(zhǔn)方式。這就推動(dòng)了瀏覽器原生支持類。
類的原生支持2014 年,Chrome 原生支持類。這就可以不使用任意庫或者轉(zhuǎn)換器來實(shí)現(xiàn)聲明類的語法。
類的原生實(shí)現(xiàn)的過程即被稱為語法糖的過程。這只是一個(gè)優(yōu)雅的語法可以被轉(zhuǎn)換為語言早已支持的相同的原語。使用新的易用的類定義,歸根結(jié)底也是要?jiǎng)?chuàng)建構(gòu)造函數(shù)和修改原型。
V8 引擎支持情況讓我們了解下 V8 是如何原生支持 ES6 類的。如前面文章所討論的那樣,首先解析新語法為可運(yùn)行的 JavaScript 代碼并添加到 AST 樹中。類定義的結(jié)果即在語法抽象樹中添加一個(gè) ClassLiteral 類型的新節(jié)點(diǎn)。
該節(jié)點(diǎn)包含了一些信息。首先,它把構(gòu)造函數(shù)當(dāng)成多帶帶的函數(shù)且包含類屬性集。這些屬性可以是一個(gè)方法,一個(gè) getter, 一個(gè) setter, 一個(gè)公共變量或者私有變量。該節(jié)點(diǎn)還儲(chǔ)存了指向父類的指針引用,該父類也并儲(chǔ)存了構(gòu)造函數(shù),屬性集和及父類引用,依次類推。
一旦把新的 ClassLiteral 轉(zhuǎn)換為字節(jié)碼,再將其轉(zhuǎn)化為各種函數(shù)和原型。
本系列持續(xù)更新中,Github 地址請查閱這里。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/52705.html
摘要:使用新的易用的類定義,歸根結(jié)底也是要?jiǎng)?chuàng)建構(gòu)造函數(shù)和修改原型。首先,它把構(gòu)造函數(shù)當(dāng)成單獨(dú)的函數(shù)且包含類屬性集。該節(jié)點(diǎn)還儲(chǔ)存了指向父類的指針引用,該父類也并儲(chǔ)存了構(gòu)造函數(shù),屬性集和及父類引用,依次類推。 原文請查閱這里,略有刪減,本文采用知識(shí)共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第...
摘要:下面是用實(shí)現(xiàn)轉(zhuǎn)成抽象語法樹如下還支持繼承以下是轉(zhuǎn)換結(jié)果最終的結(jié)果還是代碼,其中包含庫中的一些函數(shù)。可以使用新的易于使用的類定義,但是它仍然會(huì)創(chuàng)建構(gòu)造函數(shù)和分配原型。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 15 篇。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 如果你錯(cuò)過了前面的章節(jié),可以在這里找到它們: JavaScript 是...
摘要:為了更加高效的網(wǎng)絡(luò)層,它需要不僅僅只是扮演套接字管理員的角色。用套接字池來組織套接字,以源來分組套接字,每個(gè)套接字池強(qiáng)制限制其連接數(shù)和安全約束。協(xié)商是一個(gè)為計(jì)算機(jī)網(wǎng)絡(luò)提供通信安全的加密協(xié)議。 原文請查閱這里,略有改動(dòng),本文采用知識(shí)共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第十二章...
摘要:為了更加高效的網(wǎng)絡(luò)層,它需要不僅僅只是扮演套接字管理員的角色。用套接字池來組織套接字,以源來分組套接字,每個(gè)套接字池強(qiáng)制限制其連接數(shù)和安全約束。協(xié)商是一個(gè)為計(jì)算機(jī)網(wǎng)絡(luò)提供通信安全的加密協(xié)議。 原文請查閱這里,略有改動(dòng),本文采用知識(shí)共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第十二章...
閱讀 936·2021-11-23 09:51
閱讀 997·2021-11-18 10:02
閱讀 1928·2021-09-10 11:27
閱讀 3148·2021-09-10 10:51
閱讀 788·2019-08-29 15:13
閱讀 2070·2019-08-29 11:32
閱讀 2506·2019-08-29 11:25
閱讀 3051·2019-08-26 11:46