[JavaScript] 淺談Jscex實現原理

摘要:[JavaScript] 淺談Jscex實現原理

前言

之前的文章中,我們介紹了如何透過Jscex來提高JavaScript程式碼的可讀性,

利用Jscex撰寫JavaScript解決了我們面對JavaScript大量Callback Function時造成程式碼複雜的問題,

讓我們能用同步的方法配合關鍵字$await來撰寫非同步的程式,也增加了可維護性,

在今天的文章之中,想和大家簡單介紹一下Jscex的實現原理,

它利用JavaScript實現了一個JIT Compiler的環境,來即時編譯產生原生JavaScript的程式碼以供執行,

首先我們先來看看Jscex所包含的幾個模組。

模組介紹

在Jscex中,各模組依據各自的職責切分乾淨,關注於各自任務的實現,

我們可以從下圖之中,了解到各模組之間的依賴關係,而所有的模組都依賴於最上面的核心底層模組(Core)

Jscex:模組關係

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:實現原理

在開發時期,假設我們有一組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

點我展開瀏覽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時學習,

在本文中如果有說明錯誤不正確的地方,也請大家多多見諒並不吝指教,

也希望大家能夠多多一起使用並且討論,希望這篇文章對大家有幫助 ^_^

參考文章:

  1. UglifyJS有個不錯的JavaScript解析器
  2. Jscex 開發指南
  3. Simplify Asynchronous Programming with Tasks