在 DOM 中動態插入并執行腳本
在 HTML 中腳本以 <script> 來標記,通過設置其內容或 src 屬性執行內聯腳本或外部腳本。 本文討論動態地插入腳本標簽時瀏覽器對它的解析、下載和執行行為。 動態插入腳本的場景可能包括使用 AJAX 獲取腳本并動態執行(多用于性能優化), 以及運行時決定執行頁面模板中的某段腳本(多用于單頁異步)。
動態執行腳本還有其他方式,比如 eval 和 new 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
事實上,設置 innerHTML 和 outerHTML 都不執行腳本,但 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 );
}
來自:http://harttle.com/2017/01/16/dynamic-script-insertion.html