256

我尝试将一些脚本加载到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>

4

25 回答 25

106

您必须使用eval()来执行您作为 DOM 文本插入的任何脚本代码。

MooTools 会自动为您执行此操作,我相信 jQuery 也会这样做(取决于版本。jQuery 版本 1.6+ 使用eval)。这省去了很多解析<script>标签和转义内容的麻烦,以及一堆其他“陷阱”。

一般来说,如果你eval()自己去,你想创建/发送没有任何 HTML 标记的脚本代码,例如<script>,因为这些不会eval()正确。

于 2009-07-29T01:04:25.300 回答
106

这是一种将所有脚本递归替换为可执行脚本的方法:

function nodeScriptReplace(node) {
        if ( nodeScriptIs(node) === true ) {
                node.parentNode.replaceChild( nodeScriptClone(node) , node );
        }
        else {
                var i = -1, children = node.childNodes;
                while ( ++i < children.length ) {
                      nodeScriptReplace( children[i] );
                }
        }

        return node;
}
function nodeScriptClone(node){
        var script  = document.createElement("script");
        script.text = node.innerHTML;

        var i = -1, attrs = node.attributes, attr;
        while ( ++i < attrs.length ) {                                    
              script.setAttribute( (attr = attrs[i]).name, attr.value );
        }
        return script;
}

function nodeScriptIs(node) {
        return node.tagName === 'SCRIPT';
}

示例调用:

nodeScriptReplace(document.getElementsByTagName("body")[0]);
于 2013-12-14T14:20:25.373 回答
97

这是您的问题的一个非常有趣的解决方案:http: //24ways.org/2005/have-your-dom-and-script-it-too

所以使用它而不是脚本标签:

<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />

于 2010-09-15T03:07:44.393 回答
49

您可以创建脚本,然后注入内容。

var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);

这适用于所有浏览器:)

于 2011-08-14T00:36:57.703 回答
30

我使用了这段代码,它工作正常

var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
    eval(arr[n].innerHTML)//run script inside div
于 2013-04-29T12:04:56.163 回答
21

每次我想动态插入脚本标签时我都会这样做!

  const html =
    `<script>
        alert(' there ! Wanna grab a '); 
    </script>`;

  const scriptEl = document.createRange().createContextualFragment(html);
  parent.append(scriptEl);

注意:使用 ES6

编辑1:为你们澄清 - 我已经看到很多答案使用appendChild并想让你们知道它的工作原理完全一样append

于 2020-06-29T15:30:19.127 回答
18

我在使用 innerHTML 时遇到了这个问题,我必须将一个 Hotjar 脚本附加到我的 Reactjs 应用程序的“head”标签中,并且它必须在附加后立即执行。

将动态节点导入“head”标签的一个很好的解决方案是React-helment模块。


此外,对于提议的问题,还有一个有用的解决方案:

innerHTML 中没有脚本标签!

事实证明,HTML5 不允许使用 innerHTML 属性动态添加脚本标签。所以下面的代码不会执行,也不会出现 Hello World 的警报!

element.innerHTML = "<script>alert('Hello World!')</script>";

这记录在 HTML5 规范中:

注意:使用 innerHTML 插入的脚本元素在插入时不会执行。

但请注意,这并不意味着 innerHTML 不受跨站点脚本的影响。可以通过innerHTML 执行JavaScript,而无需使用MDN 的innerHTML 页面上所示的标签。

解决方案:动态添加脚本

要动态添加脚本标签,您需要创建一个新的脚本元素并将其附加到目标元素。

您可以对外部脚本执行此操作:

var newScript = document.createElement("script");
newScript.src = "http://www.example.com/my-script.js";
target.appendChild(newScript);

和内联脚本:

var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript); 
target.appendChild(newScript);
于 2019-11-11T16:01:36.000 回答
6

你可以这样做:

var mydiv = document.getElementById("mydiv");
var content = "<script>alert(\"hi\");<\/script>";

mydiv.innerHTML = content;
var scripts = mydiv.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
    eval(scripts[i].innerText);
}
于 2018-06-26T16:34:20.633 回答
6

这是一个不使用eval脚本链接脚本以及模块的解决方案。

该函数接受 3 个参数:

  • html : 带有要插入的 html 代码的字符串
  • dest : 对目标元素的引用
  • append : 布尔标志,用于在目标元素 html 的末尾添加
function insertHTML(html, dest, append=false){
    // if no append is requested, clear the target element
    if(!append) dest.innerHTML = '';
    // create a temporary container and insert provided HTML code
    let container = document.createElement('div');
    container.innerHTML = html;
    // cache a reference to all the scripts in the container
    let scripts = container.querySelectorAll('script');
    // get all child elements and clone them in the target element
    let nodes = container.childNodes;
    for( let i=0; i< nodes.length; i++) dest.appendChild( nodes[i].cloneNode(true) );
    // force the found scripts to execute...
    for( let i=0; i< scripts.length; i++){
        let script = document.createElement('script');
        script.type = scripts[i].type || 'text/javascript';
        if( scripts[i].hasAttribute('src') ) script.src = scripts[i].src;
        script.innerHTML = scripts[i].innerHTML;
        document.head.appendChild(script);
        document.head.removeChild(script);
    }
    // done!
    return true;
}
于 2019-05-22T02:34:03.517 回答
5

对于仍在尝试执行此操作的任何人,不,您不能使用 注入脚本innerHTML,但是可以使用Bloband将字符串加载到脚本标记中URL.createObjectURL

我创建了一个示例,可让您将字符串作为脚本运行并获取通过 promise 返回的脚本的“导出”:

function loadScript(scriptContent, moduleId) {
    // create the script tag
    var scriptElement = document.createElement('SCRIPT');

    // create a promise which will resolve to the script's 'exports'
    // (i.e., the value returned by the script)
    var promise = new Promise(function(resolve) {
        scriptElement.onload = function() {
            var exports = window["__loadScript_exports_" + moduleId];
            delete window["__loadScript_exports_" + moduleId];
            resolve(exports);
        }
    });

    // wrap the script contents to expose exports through a special property
    // the promise will access the exports this way
    var wrappedScriptContent =
        "(function() { window['__loadScript_exports_" + moduleId + "'] = " + 
        scriptContent + "})()";

    // create a blob from the wrapped script content
    var scriptBlob = new Blob([wrappedScriptContent], {type: 'text/javascript'});

    // set the id attribute
    scriptElement.id = "__loadScript_module_" + moduleId;

    // set the src attribute to the blob's object url 
    // (this is the part that makes it work)
    scriptElement.src = URL.createObjectURL(scriptBlob);

    // append the script element
    document.body.appendChild(scriptElement);

    // return the promise, which will resolve to the script's exports
    return promise;
}

...

function doTheThing() {
    // no evals
    loadScript('5 + 5').then(function(exports) {
         // should log 10
        console.log(exports)
    });
}

我已经从我的实际实现中简化了这一点,所以没有承诺其中没有任何错误。但原理是有效的。

如果您不关心在脚本运行后取回任何值,那就更容易了;只需省略Promiseonload位。您甚至不需要包装脚本或创建全局window.__load_script_exports_属性。

于 2016-11-04T15:21:11.957 回答
5

这是一个递归函数,用于设置我在广告服务器中使用的元素的 innerHTML:

// o: container to set the innerHTML
// html: html text to set.
// clear: if true, the container is cleared first (children removed)
function setHTML(o, html, clear) {
    if (clear) o.innerHTML = "";

    // Generate a parseable object with the html:
    var dv = document.createElement("div");
    dv.innerHTML = html;

    // Handle edge case where innerHTML contains no tags, just text:
    if (dv.children.length===0){ o.innerHTML = html; return; }

    for (var i = 0; i < dv.children.length; i++) {
        var c = dv.children[i];

        // n: new node with the same type as c
        var n = document.createElement(c.nodeName);

        // copy all attributes from c to n
        for (var j = 0; j < c.attributes.length; j++)
            n.setAttribute(c.attributes[j].nodeName, c.attributes[j].nodeValue);

        // If current node is a leaf, just copy the appropriate property (text or innerHTML)
        if (c.children.length == 0)
        {
            switch (c.nodeName)
            {
                case "SCRIPT":
                    if (c.text) n.text = c.text;
                    break;
                default:
                    if (c.innerHTML) n.innerHTML = c.innerHTML;
                    break;
            }
        }
        // If current node has sub nodes, call itself recursively:
        else setHTML(n, c.innerHTML, false);
        o.appendChild(n);
    }
}

你可以在这里看到演示。

于 2017-05-02T08:08:59.727 回答
3

是的,您可以,但是您必须在 DOM 之外进行,并且顺序必须正确。

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    n.innerHTML = scr;
    document.body.appendChild(n);
}

...会提醒'foo'。这不起作用:

document.getElementById("myDiv").innerHTML = scr;

即使这样也行不通,因为节点是先插入的:

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    document.body.appendChild(n);
    n.innerHTML = scr;  
}
于 2009-07-29T12:23:05.107 回答
2

过滤您的脚本标签并使用 eval 运行它们中的每一个

var tmp=  document.createElement('div');
tmp.innerHTML = '<script>alert("hello")></script>';
[...tmp.children].filter(x => x.nodeName === 'SCRIPT').forEach(x => eval(x.innerText));
于 2021-01-07T22:36:03.103 回答
2

这是mmm 出色解决方案的更现代(和简洁)版本:

function executeScriptElements(containerElement) {
  const scriptElements = containerElement.querySelectorAll("script");

  Array.from(scriptElements).forEach((scriptElement) => {
    const clonedElement = document.createElement("script");

    Array.from(scriptElement.attributes).forEach((attribute) => {
      clonedElement.setAttribute(attribute.name, attribute.value);
    });
    
    clonedElement.text = scriptElement.text;

    scriptElement.parentNode.replaceChild(clonedElement, scriptElement);
  });
}

注意:我也尝试过使用cloneNode()outerHTML的替代解决方案,但没有奏效。

于 2021-09-15T09:42:39.287 回答
1

Krasimir Tsonev 有一个很好的解决方案,可以克服所有问题。他的方法不需要使用eval,所以不存在性能和安全问题。它允许您设置 innerHTML 字符串包含带有 js 的 html 并立即将其转换为 DOM 元素,同时还可以执行代码中存在的 js 部分。简短,简单,并且完全按照您的意愿工作。

享受他的解决方案:

http://krasimrtsonev.com/blog/article/Convert-HTML-string-to-DOM-element

重要笔记:

  1. 您需要用 div 标签包装目标元素
  2. 您需要用 div 标签包装 src 字符串。
  3. 如果你直接写 src 字符串并且它包含 js 部分,请注意正确编写结束脚本标记(在 / 之前使用 \),因为这是一个字符串。
于 2014-01-28T16:22:41.580 回答
1

使用$(parent).html(code)而不是parent.innerHTML = code.

以下还修复了使用的脚本和通过属性document.write加载的脚本。src不幸的是,即使这也不适用于 Google AdSense 脚本。

var oldDocumentWrite = document.write;
var oldDocumentWriteln = document.writeln;
try {
    document.write = function(code) {
        $(parent).append(code);
    }
    document.writeln = function(code) {
        document.write(code + "<br/>");
    }
    $(parent).html(html); 
} finally {
    $(window).load(function() {
        document.write = oldDocumentWrite
        document.writeln = oldDocumentWriteln
    })
}

来源

于 2014-05-07T08:27:04.810 回答
1

尝试使用模板和 document.importNode。这是一个例子:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
<h1 id="hello_world">Sample</h1>
<script type="text/javascript">
 var div = document.createElement("div");
  var t = document.createElement('template');
  t.innerHTML =  "Check Console tab for javascript output: Hello world!!!<br/><script type='text/javascript' >console.log('Hello world!!!');<\/script>";
  
  for (var i=0; i < t.content.childNodes.length; i++){
    var node = document.importNode(t.content.childNodes[i], true);
    div.appendChild(node);
  }
 document.body.appendChild(div);
</script>
 
</body>
</html>

于 2018-02-09T16:44:05.727 回答
1

你也可以<script>这样包装你的,它会被执行:

<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';

请注意:里面的范围srcdoc是指iframe,所以你必须使用top上面例子中的like来访问父文档。

于 2019-09-06T08:28:39.037 回答
0

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

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});
于 2017-10-06T13:34:52.123 回答
0

Gabriel Garcia 提到的 MutationObservers 是在正确的轨道上,但对我来说并不是很有效。我不确定这是因为浏览器的怪癖还是由于我的错误,但最终为我工作的版本如下:

document.addEventListener("DOMContentLoaded", function(event) {
    var observer = new MutationObserver(mutations=>{
        mutations.map(mutation=>{
            Array.from(mutation.addedNodes).map(node=>{
                if (node.tagName === "SCRIPT") {
                    var s = document.createElement("script");
                    s.text=node.text;
                    if (typeof(node.parentElement.added) === 'undefined')
                        node.parentElement.added = [];
                    node.parentElement.added[node.parentElement.added.length] = s;
                    node.parentElement.removeChild(node);
                    document.head.appendChild(s);
                }
            })
        })
    })
    observer.observe(document.getElementById("element_to_watch"), {childList: true, subtree: true,attributes: false});
};

当然,您应该替换element_to_watch为正在修改的元素的名称。

node.parentElement.added用于存储添加到document.head. 在用于加载外部页面的函数中,您可以使用以下内容删除不再相关的脚本标签:

function freeScripts(node){
    if (node === null)
        return;
    if (typeof(node.added) === 'object') {
        for (var script in node.added) {
            document.head.removeChild(node.added[script]);
        }
        node.added = {};
    }
    for (var child in node.children) {
        freeScripts(node.children[child]);
    }
}

还有一个加载函数开始的例子:

function load(url, id, replace) {
    if (document.getElementById(id) === null) {
        console.error("Element of ID "+id + " does not exist!");
        return;
    }
    freeScripts(document.getElementById(id));
    var xhttp = new XMLHttpRequest();
    // proceed to load in the page and modify innerHTML
}
于 2018-10-13T20:47:45.840 回答
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 content 属性是。如果脚本未执行(添加为 innerHTML),则在图像 onload 事件中执行脚本。如果脚本已加载(由后端添加),则script01定义变量并且 onload 将不会第二次运行脚本。

于 2020-08-20T06:17:53.763 回答
0

我自己的转折,使用现代 JS 和 typescript。tagName当 querySelector 就在那里时,不确定为什么人们会过滤等。

对我很有魅力:

function makeScriptsExecutable(el: Element) {
  el.querySelectorAll("script").forEach(script => {
    const clone = document.createElement("script")

    for (const attr of script.attributes) {
      clone.setAttribute(attr.name, attr.value)
    }

    clone.text = script.innerHTML
    script.parentNode?.replaceChild(clone, script)
  })
}
于 2021-11-23T15:36:26.497 回答
0

简单,没有评估,没有功能:

    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)
      })      
  })
于 2022-02-14T08:19:57.947 回答
-1

从 innerHTML 执行(Java 脚本)标签

将您的脚本元素替换为具有类属性 class="javascript" 的 div 并将其关闭</div>

不要更改要执行的内容(以前在 script 标签中,现在在 div 标签中)

在页面中添加样式...

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

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

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

    });`

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

于 2013-01-22T14:29:12.847 回答
-1

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

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

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

于 2020-04-25T15:43:15.703 回答