让 innerHTML 进来的 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>

参考:

上一篇:[转]HTML5 script 标签的 crossorigin 属性到底有什么用?


下一篇:HTML5 script 标签的 crossorigin 和integrity属性的作用