摘要:[JavaScript] 淺談Jscex實現原理
前言
在之前的文章中,我們介紹了如何透過Jscex來提高JavaScript程式碼的可讀性,
利用Jscex撰寫JavaScript解決了我們面對JavaScript大量Callback Function時造成程式碼複雜的問題,
讓我們能用同步的方法配合關鍵字$await來撰寫非同步的程式,也增加了可維護性,
在今天的文章之中,想和大家簡單介紹一下Jscex的實現原理,
它利用JavaScript實現了一個JIT Compiler的環境,來即時編譯產生原生JavaScript的程式碼以供執行,
首先我們先來看看Jscex所包含的幾個模組。
模組介紹
在Jscex中,各模組依據各自的職責切分乾淨,關注於各自任務的實現,
我們可以從下圖之中,了解到各模組之間的依賴關係,而所有的模組都依賴於最上面的核心底層模組(Core)
Jscex.js - 核心模組
Jscex的核心底層,其他模組都依賴於它,主要包含了一組Logger的實現,
以及常用的JavaScript操作函數(map、format...等)
jscex-parser.js - 解析器模組
Jscex的JavaScript程式碼解析器,可以將JavaScript解析為AST,
主要移植於UglifyJS的實現,提供編譯器模組編譯的來源。
只會在開發環境下使用
jscex-jit.js - 編譯器模組
JIT編譯器模組可根據parser所轉換出來的AST陣列,基於builderbase構造器基礎模組,
透過非同步模組的建造器,產生compiled過後的程式碼,並使用eval執行它。
只會在開發環境下使用
jscex-async.js - 非同步模組
主要實現了類似C# 4.0的非同步Task模型,以及AsyncBuilder建造器。
jscex-async-powerpack.js - 非同步增加模組
主要是針對非同步模組的進一步擴充,實現了whenAll、sleep或是onevent等函數,
還提供了在node.js中常用的兩個擴充方法fromStandard和fromCallback,讓使用更方便。
jscex-promise.js - 非同步Promise模組
主要實現非同步Promise/A模型,以及PromiseBuilder建造器
實現原理
在開發環境之中,Jscex透過JIT Compiler即時的將非同步的程式碼編譯為同步的代碼並執行
(當然在線上環境,我們可以完全透過AOT Compiler事先做轉換)
主要實現的流程可以參考下圖
在開發時期,假設我們有一組Jscex的非同步原始碼
var calculate = eval(Jscex.compile("async", function (a, b) {
var result = $await($.ajaxAsync({
url: '/WebApi/CalculateService.svc',
type: 'GET',
data: { a: a, b: b },
dataType: "json"
}));
return result.data.sum;
}));
這段程式碼會先經過jscex-parser解析器模組轉換為AST
而jscex-jit編譯器模組,會根據AST的內容,基於jscex-builderbase構建器模組當作基礎,
並透過compile內帶入的第一個參數來選擇建造器 (本例中參數為async,建造器即為AsyncBuilder)
將非同步代碼轉換為原生的JavaScript程式碼,例如上面的程式碼會轉換為
// Jscexified:
/* async << function (a, b) { */ (function (a, b) {
var _builder_$0 = Jscex.builders["async"];
return _builder_$0.Start(this,
_builder_$0.Delay(function () {
/* var result = $await($.ajaxAsync({ */ return _builder_$0.Bind($.ajaxAsync({
/* "url": "/WebApi/CalculateService.svc", */ "url": "/WebApi/CalculateService.svc",
/* "type": "GET", */ "type": "GET",
/* "data": { */ "data": {
/* "a": a, */ "a": a,
/* "b": b */ "b": b
/* }, */ },
/* "dataType": "json" */ "dataType": "json"
/* })); */ }), function (result) {
/* return result.data.sum; */ return _builder_$0.Return(result.data.sum);
});
})
);
/* } */ })
最後瀏覽器或node.js會執行的是compile過後的程式碼。
而在Production 環境,我們會透過Jscex AOT compiler事先轉換程式碼,
在執行時就不在需要JIT compiler和parser模組,只需要依賴於構建器和非同步模組,
也省掉了JIT編譯所造成的效能消耗
結語
Jscex除了能夠替我們帶來更高可讀性的JavaScript程式碼之外,
它在各模組的設計上也是切割乾淨、職責明確,很值得我們在設計Library時學習,
在本文中如果有說明錯誤不正確的地方,也請大家多多見諒並不吝指教,
也希望大家能夠多多一起使用並且討論,希望這篇文章對大家有幫助 ^_^
參考文章: