最近更新: 2007-02-17

Load and Execute JavaScript on Demand, by createElement

Tags: ajax javascript

在《Load and Execute JavaScript on Demand》一文中,我說明了利用 XMLHttpRequest 動態載入外部 JavaScript 程式的技巧。然而那技巧是用於應付某些特殊情境之手段,其限制與特點如下列所示:

  1. 受限於 XMLHttpRequest 之安全性管制,不能載入不同主機的外部程式。
  2. 易於掌握載入狀況及執行順序。
  3. 執行方式較具彈性,甚至可以選擇性執行片段。例如《Load and Execute JavaScript on Demand》之範例程式第74-79行之註解內容,原意是截取夾雜在一般 HTML 文件中的 JavaScript 程式碼,且僅僅執行此片段程式。此亦為該程式實作之最初用途,因為外部 JavaScript 程式碼並非位於純粹 .js 文件中,而是夾雜在網誌的文章中。

XMLHttpRequest 之外,還有一個適用於較一般性情境的動態載入技巧:利用 document.createElement('script') 實踐 (See also: dynamic load javascript from javascriptDynamically Loading External JavaScript Files)。

document.createElement('script') 動態載入技巧之特點恰與 XMLHttpRequest 相反:

  1. 可以載入跨網域的外部 JavaScript 程式。
  2. 不易掌握載入狀況及執行順序。一樣是以非同步模式載入,但不像 XMLHttpRequest 具有 onreadystatechange 事件可掌握載入狀況;也不能載入之後再依序執行。
  3. 無法選擇執行片段。瀏覽器載入外部 JavaScript 程式後便立即執行,不能選擇執行時機,也不能僅執行片段內容。因此外部文件必須是一個純 JavaScript 文件。

下列是一個基本的外部 JavaScript 程式。

test_v1.js
window.alert('This external JavaScript file is loaded!');

下列示範 document.createElement('script') 載入外部 JavaScript 程式的基本型式,並嘗試以《Rendering images with title and box》所採取的方法掌握外部程式載入狀況。

<script type="text/javascript">
function dynamicLoadJs1() {
    var js = document.createElement('script');
    js.type = 'text/javascript';
    js.src = 'test_v1.js';
    js.onload = function() { // not work in IE

        window.alert('Loaded')
        window.alert('js.complete is ' + js.complete);
    };
    document.getElementsByTagName('head')[0].appendChild(js);
}

if (window.attachEvent) window.attachEvent('onload', dynamicLoadJs1);
else window.addEventListener('load', dynamicLoadJs1, false);
</script>

藉由上述示範,可以證明 document.createElement('script') 確實可以載入外部 JavaScript 程式,但卻無法掌握其載入狀況。元素的 onload 事件在 IE 中不會作用;至於 complete 屬性,我個人已知在 IE, Firefox 及 Opera 三種瀏覽器中皆為 undefined ,是無效的。這意味著我們必須要求外部程式主動改變某些狀態以告知它已經載入了,主控端則要輪詢狀態值。譬諭如我們要 JavaScript 去載入一個文件,然後每隔一段時間就要問 JavaScript 載入完了嗎?

根據上述發現的問題進行修正。首先,我在主控端定義一個狀態值 loadingScripts 表示要載入的外部程式數目,並以 setInterval 設定輪詢動作。藉由查詢 loadingScripts 之值或一個應該由外部程式定義之項目是否已定義這兩種狀態之變化掌握載入狀況。修正後的動態載入型式如下列。

<script type="text/javascript">
function dynamicLoadJs2() {
    window.loadingScript = 1;
    var js = document.createElement('script');
    js.type = 'text/javascript';
    js.src = 'test_v2.js';
    document.getElementsByTagName('head')[0].appendChild(js);

    var interval = setInterval(
        function() {
            if ((window.loadingScript == 0) ||
                (externalData))
            {
                clearInterval(interval);
                window.alert('Loaded and external = ' + externalData);
            }
        },
        100 /* poll per 0.1 second*/
    );
}
if (window.attachEvent) window.attachEvent('onload', dynamicLoadJs2);
else window.addEventListener('load', dynamicLoadJs2, false);
</script>

外部程式則要在其最後一行將 loadingScripts 之值減一。

test_v2.js
function externalFunc() {
    window.alert('hello');
}

var externalData = 'Abc';

window.loadingScripts--;

document.createElement('script') 動態載入外部程式的技巧雖然可以載入跨網域的程式,卻有一些效能缺陷。

  1. 不易掌握載入狀況的問題。當外部程式不能配合主控端改變狀態值時,則必須要多繞幾個圈才能確保載入與執行狀況。
  2. 必須以輪詢模式掌握載入狀況。
  3. 因為需建立 script 元素並加入文件 DOM 的 head 節點中,因此通常要等到文件 DOM 結構建立之後才能開始載入外部程式,不像 XMLHttpRequest 方式可以並行載入。

若對非同步模式下的輪詢與事件驅動機制有興趣,可以參考《select() - I/O Multiplexer》。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2741057.html