1. 程式人生 > >動態插入DOM元素並執行指令碼

動態插入DOM元素並執行指令碼

在 HTML 中指令碼以 <script> 來標記,通過設定其內容或src屬性執行內聯指令碼或外部指令碼。 本文討論動態地插入指令碼標籤時瀏覽器對它的解析、下載和執行行為。 動態插入指令碼的場景可能包括使用 AJAX 獲取指令碼並動態執行(多用於效能優化), 以及執行時決定執行頁面模板中的某段指令碼(多用於單頁非同步)。

動態執行指令碼還有其他方式,比如evalnew Function,這些不在本文的討論範圍。

執行內聯指令碼

為了插入內聯指令碼,可以建立一個script元素並設定其內容,插入到 DOM 即可立即執行。 例如:

var script = document
.createElement('script'); script.text = 'console.log("foo")'; document.body.appendChild(script);

以下寫法是等價的

script.text = 'console.log("foo")';
script.innerText = 'console.log("foo")';
script.innerHTML = 'console.log("foo")';

需要注意的是內聯指令碼是否能夠執行仍然受制於CSP策略指令, 該策略是由Content-Security-Policy響應頭(rfc7762

)控制的。 例如下列設定將會禁止執行harttle.com以外的任何內聯指令碼。

Content-Security-Policy: script-src harttle.com;

執行外部指令碼

插入並執行外部指令碼的方法與內聯指令碼類似,只需設定script.src屬性並插入到 DOM。 例如:

var script = document.createElement('script');
script.src = 'foo.js';
document.body.appendChild(script);

與內聯指令碼不同的是,外部指令碼的插入是非同步的不會阻塞 DOM 解析。 詳見

非同步渲染的下載和阻塞行為一文。

此外有一個細節可能需要注意:一旦設定了src屬性,<script> 標籤本身的所有內容就不會再被執行了。

innerHTML

innerHTML屬性可用來設定 DOM 內容,但不可用來插入並執行<script>。 下面的內聯指令碼和外部指令碼都不會被執行:

document.body.innerHTML = '<script src="foo.js"></script>'
document.body.innerHTML = '<script>console.log("foo")</script>'

在設定 innerHTML 時,瀏覽器會初始化一個新的 HTML Parser 來解析它。 只要與該 Parser 關聯的 DOM 啟用了 JavaScript(通常是啟用的),指令碼的 scripting flag 就為真, 但是即便如此,HTML 片段的解析過程中,指令碼是不會執行的

Create a new HTML parser, and associate it with the just created Document node. – 12.4 Parsing HTML fragments, WHATWG

The scripting flag can be enabled even when the parser was originally created for the HTML fragment parsing algorithm, even though script elements don’t execute in that case. – 12.2.3.5 Other parsing state flags, WHATWG

事實上,設定innerHTMLouterHTML都不執行指令碼,但document.write()是會同步執行的。

When inserted using the document.write() method, script elements execute (typically blocking further script execution or HTML parsing), but when inserted using innerHTML and outerHTML attributes, they do not execute at all. – 4.12.1 The script element WHATWG

jQuery DOM Eval

我們知道使用 jQuery html() 方法時插入的指令碼總是執行的,jQuery 會檢查傳入的內容,並執行其中的每一個指令碼。 原始碼在src/core/DOMEval.js

function DOMEval( code, doc ) {
    doc = doc || document;
    var script = doc.createElement( "script" );
    script.text = code;
    doc.head.appendChild( script ).parentNode.removeChild( script );
}