網站搜尋

javascript - requirejs - 多次調用 require 的性能


我想知道在一個有多個模組的專案中使用 RequireJS 的正確方式是什麼,關於多次 require 調用且依賴較少與一次 require 所有依賴的性能差異。

假設在一個應用程式中,我需要載入一些模組:gmaps, jquery, module1, module2, module3。其中一些模組的使用相當獨立。因此,問題是以下哪一種方案是建議的(假設這段程式碼是載入到頁面的主要模組):

require(['gmaps'], function(gmaps){
   gmaps.use();
});

require(['jquery','module1'], function(jquery, module1){
   module1.use();
});

require(['jquery','module2'], function(jquery, module2){
   module2.use();
});

require(['jquery','module3'], function(jquery, module3){
   module3.use();
});

對抗

require(['jquery','module1','module1','module1','gmaps'], function(jquery, module1,module2,module3,gmaps){
   module1.use();
   module2.use();
   module3.use();
   gmaps.use();
});

換句話說,require 的效能損耗是多少,以及最佳做法是什麼。

答案#1

這裡對這個問題的答案是「視情況而定」。我以一個在大型應用程式中使用過 RequireJS,但並未徹底閱讀 RequireJS 代碼的人身份來說。(只是指出,完全了解 RequireJS 內部運作的人可能會有不同的解釋。)require 的成本可以分為三種成本情境:

  1. 如果模組從未被加載過,require 會從伺服器加載文件、執行該文件、執行模組的工廠函數,並返回模組的參考。(從網絡加載文件的成本通常遠超其他成本。)

  2. 如果模組已經被加載但從未被 require,require 會執行模組的工廠函數並返回模組的參考。(這通常會發生在優化的應用程式中。)

  3. 如果模組已經被加載並需要,require 會返回該模組的參考。

成本情境1 > 成本情境2 > 成本情境3。

首先,讓我們考慮每個檔案只有一個模組的情況。應用程式未經過優化。我有一個名為 module1 的模組,它幾乎很少被使用。在主應用程式中的使用情況可以這樣建模:

define(["module1", <bunch of other required modules>],
    function (module1, <bunch of other modules variables>) {

    [...]

    if (rare_condition_happening_once_in_a_blue_moon)
        module1.use();

    [...]
});

在這裡,即使我不使用該模組,我也總是支付成本情景第1的價格。更好的做法是:

define([<bunch of required modules>],
    function (<bunch of module variables>) {

    [...]

    if (rare_condition_happening_once_in_a_blue_moon)
        require(["module1"], function (module1) {
            module1.use();
        });

    [...]
});

這樣一來,我就只會在非常偶爾的情況下支付加載模組的代價。

那麼,如果我需要反覆使用 module 呢?這可以被建模為:

define(["module1", <bunch of other required modules>],
    function (module1, <bunch of other modules variables>) {

    [...]

    for(iterate a gazillion times)
        module1.use();

    [...]
});

在這種情況下,成本情景編號 1 只支付一次,就這樣。如果我像這樣使用 require:

define([<bunch of required modules>],
    function (<bunch of module variables>) {

    [...]

    for(iterate a gazillion times)
        require(["module1"], function (module1) {
            module1.use();
        });

    [...]
});

我正在支付成本情境編號 1 一次,還有成本情境編號 3 (無數次 - 1)。最終,module1 應該包含在 define 呼叫的需求中,還是放在單獨的 require 呼叫中,取決於你的應用程序的具體情況。

如果應用程式是透過使用 r.js 或自製優化來進行優化,分析將會有所改變。如果應用程式被優化,使得所有模組都在一個檔案中,每次在上述情況中你支付情境 1 的成本,你將改為支付情境 2 的成本。

為了完整起見,我要補充說明,在你事先不知道可能想要載入的模組的情況下,使用 require 是必要的。

define([<bunch of other required modules>],
    function (<bunch of other modules variables>) {

    [...]

    require(<requirements unknown ahead of time>, function(m1, m2, ...) {
        m1.foo();
        m2.foo();
        [...]
    });

    [...]
});

答案#2

這取決於你想要的載入行為。

當你需要一個尚未被請求的模組時,將會發出 HTTP 請求來載入該模組。如果該模組至少已被載入一次,它將被存儲在 require 的快取中,因此當你第二次請求該模組時,將使用快取的值。

所以,這是一個在懶加載和急切加載之間的選擇。

需要解決的問題是,每個模組都可以定義它的依賴關係——所以你的第一種方法可能就是「最佳實踐」。最終,你會想使用 r.js 將你的應用程式構建成單一的 JS 檔案。