今天來簡單聊聊如何讓 innerHTML 進來的 script 代碼跑起來的問題。
前台請求一個接口,接口返回一些 HTML 標簽拼接成的字符串,以供前端直接 innerHTML 生成 DOM 元素,這樣的做法非常普遍。但是你是否遇到過,如果字符串中拼接的 HTML 標簽中有 script 標簽,那麼該段腳本是無法執行的,這並不是 bug,而是 w3c 的文檔規定的。
比如如下這段代碼,innerHTML 插入的腳本(alert)並不會執行:
<div id="myDiv">
</div>
<script>
// 模擬接口返回數據
var strVar = "";
strVar += "<script>alert('hello world')<\/script>";
document.getElementById('myDiv').innerHTML = strVar;
</script>
那麼問題來了,如何使得 innerHTML 進來的 script 代碼能夠跑起來?
思路非常簡單,構造新的 script 標簽,然後該標簽的 innerHTML 賦值為需要渲染的腳本。偽代碼如下:
var s = document.createElement('script');
s.innerHTML = text;
document.body.appendChild(s);
當然如果要寫的完美一點,執行完 s 後還需要 remove 掉。如何獲取 text 值?兩個方法,其一可以正則匹配 strVal,提取 script 標簽內的內容,這點和 BigRender 類似,而 BigRender 除了提取 script 標簽,還需要提取 style 標簽,無疑更復雜;第二個方法是可以用 document.getElementsByTagName('script') 獲取插入 DOM 但並未執行的 script 腳本,但是這樣會把頁面所有腳本全部提取,所以還需要判斷。(可以獲取某一節點下的 script 標簽)
如果是外部的 js 文件,需要為新建的 script 元素添加 src 屬性即可。
事實上,如果是 inline JavaScript,方案一求得的 text,可以直接 eval 之。
但是如果是外聯 js 文件,同上,需要新建 script 標簽然後指定 src 屬性。
document.write 接收一個字符串作為參數,並且支持 script 標簽以及其他 HTML 標簽拼成的字符串,看起來似乎是完美的方案。不過鑒於 document.write 的怪屬性,只適合 strVal 在首頁輸出流中的渲染情況,如果是異步的請求,就可以放棄了。
舉個栗子:
hello world
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<div>
<script>
$.get('data.php', function(data) {
document.write(data);
})
</script>
</div>
目的似乎是為了在 div 中嵌入 data,但是 hello world 字樣也消失了,很顯然,異步請求後重啟輸入流,將頁面全部覆蓋掉了。
使用封裝過的方法無疑是個好辦法:
<div id="myDiv"></div>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$.get('data.php', function(data) {
$('#myDiv').html(data);
})
</script>
如果已經引入了 jQuery,無疑是最佳方案,沒必要重復造輪子了。
黑魔法,如果後台接口可控的話。
比方說我希望 innerHTML 的內容如下:
<div></div>
<script>
alert('hello world');
</script>
我們可以這樣構造 img:
<div id='myDiv'></div>
<script>
var str = "<div></div>";
str += "<img src='empty.gif' onload='alert(\"hello world\"); this.parentNode.removeChild(this);' />";
document.getElementById('myDiv').innerHTML = str;
</script>
事實上,一定條件下,innerHTML 進來的 script 腳本,在 IE(IE9-?)下是可以觸發的。
需要滿足的條件如下:
code:
<body>
<div id='myDiv'></div>
<script type="text/javascript">
var strVar = "";
strVar += "<span style='display: none'>hack ie</span><script defer='true'>";
strVar += "alert('hello world');";
strVar += "<\/script>";
document.getElementById("myDiv").innerHTML = strVar;
</script>
</body>
參考: