簡體   English   中英

動態添加的腳本標簽的加載順序

[英]Load ordering of dynamically added script tags

我有一個打字稿應用程序,可以動態添加指向 JS 文件的腳本標簽。 由於某些限制,我無法在 html 文件中靜態定義這些腳本標簽,因此我通過 typescript 動態添加它們,如下所示:

for (let src of jsFiles) {
  let sTag = document.createElement('script');
  sTag.type = 'text/javascript';
  sTag.src = src;
  sTag.defer = true;
  document.body.appendChild(script);
}

現在,我注意到,當我動態添加腳本標簽時,它們似乎並不能保證它們的加載順序。 不幸的是, jsFiles數組具有相互依賴的腳本。 因此,數組中的第二個腳本只能在第一個腳本完全加載后加載。 第二個腳本引用了在第一個腳本中定義的函數。 有沒有一種方法可以在動態添加腳本時指定腳本的排序和執行順序(類似於在 html 文件中靜態定義腳本標簽時的排序方式)?

PS 我想避免使用 onload 回調來解決這個問題,因為我注意到我的應用程序的性能下降。 我的第二個腳本文件非常大,我假設這導致了降級。

我可以提到一些替代方案來克服該要求:

  1. 使用庫注入依賴項(AMD 或 CommonJS 模塊)
    只需使用模塊 ES2015: import / export ,或 CommonJS: require()
  2. 以編程方式創建script標記並設置回調onload以便在異步加載腳本時做出反應。 屬性async = true是默認設置的。
  3. 如果允許修改要注入的腳本,則在腳本末尾添加一行,其中包含一個objectarray ,用於跟蹤已加載的腳本。
  4. 您可以將腳本作為文本( XMLHttpRequest )獲取,然后按照所需的順序使用腳本構建一個string ,最后通過eval()執行文本腳本
  5. 而不太推薦但經常使用的選項,設置一個setInterval來檢查腳本是否已經執行。

我建議選擇第一個選項。 但出於學術目的,我將說明第二種選擇:

以編程方式創建script標記並設置回調onload以便在異步加載腳本時做出反應。

我想推薦一本關於腳本加載器的讀物: 深入了解腳本加載的陰暗面,值得花半個小時!

下面的例子是一個管理腳本注入的小模塊,這是它背后的基本思想:

let _scriptsToLoad = [
  'path/to/script1.js',
  'path/to/script2.js',
  'path/to/script3.js'
];

function createScriptElement() {
  // gets the first script in the list
  let script = _scriptsToLoad.shift();
  // all scripts were loaded
  if (!script) return;
  let js = document.createElement('script');
  js.type = 'text/javascript';
  js.src = script;
  js.onload = onScriptLoaded;
  let s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(js, s);
}

function onScriptLoaded(event) {
  // loads the next script
  createScriptElement();
};

在此plunker 中,您可以按特定順序異步測試腳本注入:

主要思想是創建一個 API,允許您通過公開以下方法與要注入的腳本進行交互:

  • addScript :接收要加載的每個腳本的 URL 或 URL 列表。
  • load :運行任務以按指定順序加載腳本。
  • reset :清除腳本數組,或取消腳本加載。
  • afterLoad :在每個腳本加載后執行的回調。
  • onComplete :在所有腳本加載后執行的回調。

我喜歡Fluent Interface方法鏈技術,所以我以這種方式構建了模塊:

  scriptsLoader
    .reset()
    .addScript("script1.js")
    .addScript(["script2.js", "script3.js"])
    .afterLoad((src) => console.warn("> loaded from jsuLoader:", src))
    .onComplete(() => console.info("* ALL SCRIPTS LOADED *"))
    .load();

在上面的代碼中,我們首先加載"script1.js"文件,並執行afterLoad()回調,接下來,對"script2.js""script3.js"做同樣的"script3.js" ,在所有腳本加載完成后, onComplete()回調被執行。

我想避免使用 onload 回調來解決這個問題,因為我注意到我的應用程序性能下降。

如果您想要訂購文件......您需要等待每個文件的加載。 沒辦法。

這是我曾經寫過的一個實用函數:

/**
 * Utility : Resolves when a script has been loaded
 */
function include(url: string) {
  return new Promise<{ script: HTMLScriptElement }>((resolve, reject) => {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    script.onload = function() {
      resolve({ script });
    };

    document.getElementsByTagName('head')[0].appendChild(script);
  });
}

對於使用 jQuery 的任何人,我都改進了 @adeneo 腳本,因此它將按指定順序加載所有腳本。 它不進行鏈式加載,因此速度非常快,但如果您想要更快,請更改 50 毫秒的等待時間。

$.getMultiScripts = function(arr, path) {

    function executeInOrder(scr, code, resolve) {
        // if its the first script that should be executed
        if (scr == arr[0]) {
            arr.shift();
            eval(code);
            resolve();
            console.log('executed', scr);
        } else {
            // waiting
            setTimeout(function(){
                executeInOrder(scr, code, resolve);
            }, 50);
        }
    }

    var _arr = $.map(arr, function(scr) {

        return new Promise((resolve) => {
            jQuery.ajax({
                type: "GET",
                url: (path || '') + scr,
                dataType: "text",
                success: function(code) {
                    console.log('loaded  ', scr);
                    executeInOrder(scr, code, resolve);
                },
                cache: true
            });
        });

    });
        
    _arr.push($.Deferred(function( deferred ){
        $( deferred.resolve );
    }));
        
    return $.when.apply($, _arr);
}

如何使用:

var script_arr = [
    'myscript1.js', 
    'myscript2.js', 
    'myscript3.js'
];

$.getMultiScripts(script_arr, '/mypath/').done(function() {
    // all scripts loaded
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM