innerHTML是否可以插入脚本?

289
我尝试使用innerHTML在一个<div>中加载一些脚本。看起来脚本已经被加载到DOM中,但是它们从未被执行过(至少在Firefox和Chrome中是这样)。有没有办法在使用innerHTML插入脚本时让它们执行?
示例代码:

<!DOCTYPE html>
<html>
  <body onload="document.getElementById('loader').innerHTML = '<script>alert(\'hi\')<\/script>'">
    Shouldn't an alert saying 'hi' appear?
    <div id="loader"></div>
  </body>
</html>

26个回答

0

我对这个问题的解决方案是设置一个Mutation Observer来检测<script></script>节点,然后用相同的src替换它为一个新的<script></script>节点。例如:

let parentNode = /* node to observe */ void 0
let observer = new MutationObserver(mutations=>{
    mutations.map(mutation=>{
        Array.from(mutation.addedNodes).map(node=>{
            if ( node.parentNode == parentNode ) {
                let scripts = node.getElementsByTagName('script')
                Array.from(scripts).map(script=>{
                    let src = script.src
                    script = document.createElement('script')
                    script.src = src
                    return script
                })
            }
        })
    })
})
observer.observe(document.body, {childList: true, subtree: true});

1
谢谢你们在不说原因的情况下给我点踩。爱你们所有人。 - gabriel garcia
说明:这个片段(直接添加到HTML中)确保任何未来作为innerHTML添加的外部脚本都将被解析。我认为这是一个不错的通用想法,但由于担心通过document.createElement添加的脚本可能会被执行两次而没有点赞。同时,更改变异对象让我感到害怕 - 我不确定在某些情况下它是否会产生无限循环。 - Jan Turoň

0

以下是单行解决方案:

document.getElementsByTagName("head")[0].append(document.createRange().createContextualFragment('<script src="https://google.com/file.js"></script>'));

问题在于,当我尝试序列化包含脚本标签的HTML时,"<"被替换为"\x3C",这破坏了我的反序列化,因为我将序列化的HTML赋值回一个元素的innerHTML。 - Michael

0

Danny '365CSI' Engelman 的评论基础上,这里提供一个通用解决方案:

<script>
  alert("This script always runs.");
  script01 = true;
</script>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
 onload="if(typeof script01==='undefined') eval(this.previousElementSibling.innerHTML)">

将此用作innerHTML(即由XMLHttpRequest加载)或直接插入(即由PHP后端插入),脚本始终只加载一次。

说明:作为innerHTML加载的脚本不会被执行,但是onload内容属性会被执行。如果脚本未被执行(作为innerHTML添加),则脚本将在图像onload事件中执行。如果脚本已加载(由后端添加),则定义了script01变量,并且onload不会再次运行脚本。


2
谢谢,那是2016年的答案。现在我会这样做:<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>" onload="console.log(21,this)"/> - Danny '365CSI' Engelman

-1

简单,无 eval,无函数:

    fetch('/somepage')
    .then(x=>x.text())
    .then(x=>{
      divDestination.innerHTML=x;
        divDestination.querySelectorAll("script")
        .forEach(x=>{
          var sc=document.createElement("script");
          sc.appendChild(document.createTextNode(x.innerText));
          divDestination.appendChild(sc)
      })      
  })

-1

在innerHTML中执行(JavaScript)标签

用具有class属性class="javascript"的div替换您的script元素,并使用</div>关闭它

不要更改要执行的内容(先前在脚本标记中,现在在div标记中)

在页面中添加样式...

<style type="text/css"> .javascript { display: none; } </style>

现在使用jquery运行eval(Jquery js应已包含)

   $('.javascript').each(function() {
      eval($(this).text());

    });`

你可以在我的博客这里探索更多内容。


-1

对我来说,最好的方法是通过 innerHtml 插入新的 HTML 内容,然后使用

setTimeout(() => {
        var script_el = document.createElement("script")
        script_el.src = 'script-to-add.js'
        document.body.appendChild(script_el)
    }, 500)

setTimeout 不是必需的,但它效果更好。这对我有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接