JavaScript是如何工作的:深入類和繼承內部原理 + Babel和TypeScript之間轉換

来源:https://www.cnblogs.com/moon0201/archive/2019/02/28/10452001.html
-Advertisement-
Play Games

現在構建任何類型的軟體項目最流行的方法這是使用類。在這篇文章中,探討用 JavaScript 實現類的不同方法,以及如何構建類的結構。首先從深入研究原型工作原理,並分析在流行庫中模擬基於類的繼承的方法。 接下來是講如何將新的語法轉製為瀏覽器識別的語法,以及在 Babel 和 TypeScript 中 ...


現在構建任何類型的軟體項目最流行的方法這是使用類。在這篇文章中,探討用 JavaScript 實現類的不同方法,以及如何構建類的結構。首先從深入研究原型工作原理,並分析在流行庫中模擬基於類的繼承的方法。 接下來是講如何將新的語法轉製為瀏覽器識別的語法,以及在 Babel 和 TypeScript 中使用它來引入ECMAScript 2015類的支持。最後,將以一些在 V8 中如何本機實現類的示例來結束本文。

概述

在 JavaScript 中,沒有基本類型,創建的所有東西都是對象。例如,創建一個新字元串:

const name = "SessionStack";

接著在新創建的對象上調用不同的方法:

console.log(a.repeat(2)); // SessionStackSessionStack
console.log(a.toLowerCase()); // sessionstack

與其他語言不同,在 JavaScript 中,字元串或數字的聲明會自動創建一個封裝值的對象,並提供不同的方法,甚至可以在基本類型上執行這些方法。

另一個有趣的事實是,數組等複雜類型也是對象。如果檢查數組實例的類型,你將看到它是一個對象。列表中每個元素的索引只是對象中的屬性。當通過數組中的索引訪問一個元素時,實際上是訪問了數組對象的一個 key 值,並得到 key 對應的值。從數據的存儲方式看時,這兩個定義是相同的:

let names = [“SessionStack”];

let names = {
0”: “SessionStack”,
“length”: 1
}

因此,訪問數組中的元素和對象的屬性耗時是相同的。我(本文作者)通過多次的努力才發現這一點的。就是不久,我(本文作者)不得不對項目中的一段關鍵代碼進行大規模優化。在嘗試了所有簡單的可選項之後,最後用數組替換了項目中使用的所有對象。理論上,訪問數組中的元素比訪問哈希映射中的鍵要快且對性能沒有任何影響。在 JavaScript中,這兩種操作都是作為訪問哈希映射中的鍵來實現的,並且花費相同的時間。

使用原型模擬類

一般的想到對象時,首先想到的是類。我們大都習慣於根據類及其之間的關係來構建應用程式。儘管 JavaScript 中的對象無處不在,但該語言並不使用傳統的基於類的繼承,相反,它依賴於原型來實現。

在 JavaScript 中,每個對象通過原型連接著另一個對象。當嘗試訪問對象上的屬性或方法時,首先從對象本身開始查找,如果沒有找到任何內容,則在對象的原型中繼續查找。

從一個簡單的例子開始:

function Component(content) {
this.content = content;
}

Component.prototype.render = function() {
console.log(this.content);
}

在 Component 的原型上添加 render 方法,因為希望 Component 的每個實例都能有 render 方法。Component 任何實例調用此方法時,首先將在實例本身中執行查找,如果沒有,接著從它的原型中執行查找。

接著引入一個新的子類:

function InputField(value) {
this.content = `<input type="text" value="${value}" />`;
}

如果想要 InputField 繼承 Component 並能夠調用它的 render 方法,就需要更改它的原型。當對子類的實例調用 render方法時,不希望在它的空原型中查找,而應該從從 Component 上的原型查找:

InputField.prototype = Object.create(new Component());

通過這種方式,就可以在 Component 的原型中找到 render 方法。為了實現繼承,需要將 InputField 的原型連接到 Component的實例上,大多數庫都使用 Object.setPrototypeOf 方法來實現這一點。

然而,這不是唯一一件事要做的,每次繼承一個類,需要:

  • 將子類的原型指向父類的實例。
  • 在子類構造函數中調用的父構造函數,完成父構造函數中的初始化邏輯。

如上所述,如果希望繼承基類的的所有特性,那麼每次都需要執行這個複雜的邏輯。當創建多個類時,將邏輯封裝在可重用函數中是有意義的。這就是開發人員最初解決基於類繼承的方法——通過使用不同的庫來模擬它。

這些解決方案越來越流行,造成了 JS 中明顯缺少了一些類型的現象。這就是為什麼在 ECMAScript 2015 的第一個主要版本中引入了類,繼承的新語法。

類的轉換

當 ES6 或 ECMAScript 2015 中的新特性被提出時,JavaScript 開發人員不能等待所有引擎和瀏覽器都開始支持它們。為實現瀏覽器能夠支持新的特性一個好方法是通過 轉換 (Transpiling) ,它允許將 ECMAScript 2015 中編寫的代碼轉換成任何瀏覽器都能理解的 JavaScript 代碼,當然也包括使用基於類的繼承編寫類的轉換功能。

Babel

最流行的 JavaScript 編譯器之一就是 Babel,巨集觀來說,它分3個階段運行代碼:解析(parsing),轉譯(transforming),生成(generation),來看看它是如何轉換的:

class Component {
constructor(content) {
this.content = content;
}

render() {
console.log(this.content)
}
}

const component = new Component('SessionStack');
component.render();

以下是 Babel 轉換後的樣式:

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

如上所見,轉換後的代碼就可在任何瀏覽器執行了。 此外,還添加了一些功能, 這些是 Babel 標準庫的一部分。

_classCallCheck 和_createClass 作為函數包含在編譯文件中。

  • _classCallCheck 函數的作用在於確保構造方法永遠不會作為函數被調用,它會評估函數的上下文是否為 Component對象的實例,以此確定是否需要拋出異常。
  • _createClass 用於處理創建對象屬性,函數支持傳入構造函數與需定義的鍵值對屬性數組。函數判斷傳入的參數(普通方法/靜態方法)是否為空對應到不同的處理流程上。

為了探究繼承的實現原理,分析繼承的 Component 的 InputField 類。。

class InputField extends Component {
constructor(value) {
const content = `<input type="text" value="${value}" />`;
super(content);
}
}

使用 Babel 處理上述代碼,得到如下代碼:

 var InputField = function (_Component) {
_inherits(InputField, _Component);

function InputField(value) {
_classCallCheck(this, InputField);

var content = '<input type="text" value="' + value + '" />';
return _possibleConstructorReturn(this, (InputField.__proto__ || Object.getPrototypeOf(InputField)).call(this, content));
}

return InputField;
}(Component);

在本例中, Babel 創建了 _inherits 函數幫助實現繼承。

以 ES6 轉 ES5 為例,具體過程:

  • 編寫ES6代碼
  • babylon 進行解析
  • 解析得到 AST
  • plugin 用 babel-traverse 對 AST 樹進行遍歷轉譯
  • 得到新的 AST樹
  • 用 babel-generator 通過 AST 樹生成 ES5 代碼

代碼部署後可能存在的BUG沒法實時知道,事後為瞭解決這些BUG,花了大量的時間進行log 調試。

Babel 中的抽象語法樹

AST 包含多個節點,且每個節點只有一個父節點。 在 Babel 中,每個形狀樹的節點包含可視化類型、位置、在樹中的連接等信息。 有不同類型的節點,如 stringnumbersnull等,還有用於流控制(if)和迴圈(for,while)的語句節點。 並且還有一種特殊類型的節點用於類。它是基節點類的一個子節點,通過添加欄位來擴展它,以存儲對基類的引用和作為單獨節點的類的主體。

把下麵的代碼片段轉換成一個抽象語法樹:

class Component {
constructor(content) {
this.content = content;
}

render() {
console.log(this.content)
}
}

下麵是以下代碼片段的抽象語法樹:

Babel 的三個主要處理步驟分別是: 解析(parse),轉換 (transform),生成 (generate)。

解析

將代碼解析成抽象語法樹(AST),每個js引擎(比如Chrome瀏覽器中的V8引擎)都有自己的AST解析器,而Babel是通過 Babylon 實現的。在解析過程中有兩個階段: 詞法分析 和 語法分析 ,詞法分析階段把字元串形式的代碼轉換為 令牌 (tokens)流,令牌類似於AST中節點;而語法分析階段則會把一個令牌流轉換成 AST的形式,同時這個階段會把令牌中的信息轉換成AST的表述結構。

轉換

在這個階段,Babel接受得到AST並通過babel-traverse對其進行 深度優先遍歷,在此過程中對節點進行添加、更新及移除操作。這部分也是Babel插件介入工作的部分。

生成

將經過轉換的AST通過babel-generator再轉換成js代碼,過程就是深度優先遍歷整個AST,然後構建可以表示轉換後代碼的字元串。

在上面的示例中,首先生成兩個 MethodDefinition 節點的代碼,然後生成類主體節點的代碼,最後生成類聲明節點的代碼。

使用 TypeScript 進行轉換

另一個利用轉換的流行框架是 TypeScript。它引入了一種用於編寫 JavaScript 應用程式的新語法,該語法被轉換為任何瀏覽器或引擎都可以執行的 EMCAScript 5。下麵是用 Typescript 實現 Component :

class Component {
content: string;
constructor(content: string) {
this.content = content;
}
render() {
console.log(this.content)
}
}

轉成抽象語法樹如下:

Typescript 還支持繼承:

class InputField extends Component {
constructor(value: string) {
const content = `<input type="text" value="${value}" />`;
super(content);
}
}

以下是轉換結果:

var InputField = /** @class */ (function (_super) {
__extends(InputField, _super);
function InputField(value) {
var _this = this;
var content = "<input type=\"text\" value=\"" + value + "\" />";
_this = _super.call(this, content) || this;
return _this;
}
return InputField;
}(Component));

最終的結果還是 ECMAScript 5 代碼,其中包含 TypeScript 庫中的一些函數。封 __extends 中的邏輯與在第一節中討論的邏輯相同。

隨著 Babel 和 TypeScript 被廣泛採用,標準類和基於類的繼承成為了構造 JavaScript 應用程式的標準方式,這推動了在瀏覽器中引入對類的原生支持。

類的原生支持

2014年,Chrome 引入了對 類的原生支持,這允許在不需要任何庫或轉換器的情況下執行類聲明語法。

本地實現類的過程就是我們所說的語法糖。這隻是一種奇特的語法,它可以編譯成語言中已經支持的相同的原語。可以使用新的易於使用的類定義,但是它仍然會創建構造函數和分配原型。

V8的支持

撯著,看看在 V8 中對 ECMAScript 2015 類的本機支持的工作原理。首先必須將新語法解析為有效的 JavaScript 代碼並添加到 AST 中,因此,作為類定義的結果,一個具有ClassLiteral 類型的新節點被添加到樹中。

這個節點存儲了一些信息。首先,它將構造函數作為一個單獨的函數保存,還保存類屬性的列表,這些屬性包括 方法、getter、setter、公共欄位或私有欄位。該節點還存儲對父類的引用,該類將繼承父類,而父類將再次存儲構造函數、屬性列表和父類。

一旦這個新的類 ClassLiteral 被 轉換成代碼,它又被轉換成函數和原型。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 註:本文翻譯自 https://web.dev/fast/remove-unused-code,Written by Houssein Djirdeh。如有翻譯錯誤請指正。 像 npm 這樣的包管理器通過允許任何人輕鬆地下載和使用超過50萬公共包來讓JavaScript世界變得更好。但我們經常引入一 ...
  • 一、前言 浮動元素以脫離標準流的方式來實現元素的向左或向右浮動。float浮動屬性的四個參數:left:元素向左浮動;right:元素向右浮動;none:預設值,元素不浮動;inherit:繼承父元素的float屬性值。 舉個慄子 父元素是否註意到自己有個浮動的子div呢? 從中我們可以發現,處於標 ...
  • flex方向 flex方向由flex-direction特性決定,用於定義彈性佈局模式。flex-direction共有4種模式:從左向右、從右向左、從上往下、從下往上。 主軸 主軸的起點與終點定義了容器元素的開始和結束邊緣。 交叉軸 交叉軸的起點與終點定義了容器的頂部與底部。 從左向右:flex- ...
  • 1、用 js 寫出幾種去除 string 空格的方法。 (1)、使用js迴圈 (2)使用正則 (3)使用JQ 2、如何獲取瀏覽器URL中查詢字元串中的參數? 3、比較 typeof 與 instanceof ? 相同點:都常用來判斷一個變數是否為空,或者是什麼類型的。 不同點:tyoeof返回字元串 ...
  • html: js: 之前查到的控制input只能輸入數字且保留小數點後三位,原文鏈接忘記了,侵刪。 ...
  • 1 安裝sass npm install --save-loader npm install --save-dev node-sass 2 安裝依賴 cnpm install stylus-loader css-loader style-loader --save-dev ...
  • 基本思路 紅色:為可見區域 黑色方框:元素,不可見。 通過絕對定位方式,把黑色方框,移動到紅色可見區別,來實現圖片切換。 實例 創建幻燈實例對象 源碼:https://pan.baidu.com/s/1-A0GkZ2hOpuVJ5dg1y6mnQ 樣本:http://js.zhuamimi.cn/% ...
  • http-equiv l 設定頁面使用的字元集 l IE8瀏覽器去模擬特定版本的IE瀏覽器的渲染方式,以此來解決部分相容問題。 l 自動刷新並指向新頁面 l 如果網頁過期,那麼自動刪除本地cookie l 強制頁面在當前視窗中以獨立頁面顯示,可以防止自己的網頁被別人當作一個frame頁調用 l 緩存 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...