今天來簡單聊聊如何讓 innerHTML 進來的 scrip 代碼跑起來的問題。 前臺請求一個介面,介面返回一些 HTML 標簽拼接成的字元串,以供前端直接 innerHTML 生成 DOM 元素,這樣的做法非常普遍。但是你是否遇到過,如果字元串中拼接的 HTML 標簽中有 script 標簽,那麼
今天來簡單聊聊如何讓 innerHTML 進來的 scrip 代碼跑起來的問題。
前臺請求一個介面,介面返回一些 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 標簽
思路非常簡單,構造新的 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 屬性即可。
方案二:eval 大法
事實上,如果是 inline JavaScript,方案一求得的 text,可以直接 eval 之。
但是如果是外聯 js 文件,同上,需要新建 script 標簽然後指定 src 屬性。
方案三:document.write
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 字樣也消失了,很顯然,非同步請求後重啟輸入流,將頁面全部覆蓋掉了。
方案四:jQuery html() 方法
使用封裝過的方法無疑是個好辦法:
<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,無疑是最佳方案,沒必要重覆造輪子了。
方案五: 利用 img 標簽
黑魔法,如果後臺介面可控的話。
比方說我希望 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>
其他: 特殊的 IE
事實上,一定條件下,innerHTML 進來的 script 腳本,在 IE(IE9-?)下是可以觸發的。
需要滿足的條件如下:
- 腳本有 defer 屬性
- 腳本並不是 innerHTML 的第一個元素( script 元素必須位於 "有作用域的元素" 之後。如果通過innerHTML 插入的字元串開頭就是一個 "無作用域的元素",那麼 IE 會在解析這個字元串前先刪除該元素)
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>
參考:
- Have Your DOM and Script It Too
- Can scripts be inserted with innerHTML?
- http://w3help.org/zh-cn/causes/BX9029
- https://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
- https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML