脚本可以用innerHTML插入吗?

Can scripts be inserted with innerHTML?

提问人:Craig 提问时间:7/29/2009 最后编辑:Mosh FeuCraig 更新时间:10/31/2023 访问量:284091

问:

我尝试使用.脚本似乎加载到 DOM 中,但它从未执行过(至少在 Firefox 和 Chrome 中)。有没有办法让脚本在插入时执行?innerHTML<div>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>

javascript html dom innerhtml

评论


答:

108赞 zombat 7/29/2009 #1

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

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

通常,如果您自己要这样做,则希望在没有任何 HTML 标记(例如 )的情况下创建/发送脚本代码,因为这些标记不会正确。eval()<script>eval()

评论

12赞 Craig 7/29/2009
我真正想做的是加载一个外部脚本,而不仅仅是评估一些本地脚本。使用 innerHTML 添加脚本标签比创建脚本 DOM 元素并将其添加到正文要短得多,我正在尝试使我的代码尽可能短。您是否必须创建 dom 脚本元素并将它们添加到 dom 中,而不仅仅是使用 innerHTML 之类的东西?有没有办法在函数中使用 document.write 来做到这一点?
5赞 Ariel Popovsky 7/29/2009
正如 zombat 建议的那样,使用 Javascript 框架来加载外部脚本,不要试图重新发明轮子。JQuery 使这变得非常简单,只需包含 JQuery 并调用:$.getScript(url)。您还可以提供一个回调函数,该函数将在加载脚本后执行。
2赞 zombat 7/29/2009
Ariel 是对的。我很感激尝试保持您的代码简短,并且添加标签可能很短,但它不起作用。在它被运行之前,这一切都只是纯文本。可悲的是,它不解析 HTML 标签,所以你最终会遇到一连串的问题。<script>innerHTMLeval()eval()
35赞 buley 10/13/2011
eval() 不是解决任何问题的好方法。
2赞 Youstay Igo 11/4/2015
我自己尝试了 eval()。这是一个可怕的想法。你每次都必须评估整个事情。即使你声明了一个变量名称和值,你也必须每次重新声明/重新 eval() 它才能使其工作。这是一场错误的噩梦。
3赞 mwilcox 7/29/2009 #2

是的,你可以,但你必须在 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;  
}

评论

21赞 Wichert Akkerman 1/31/2012
值得一提的是:这似乎不适用于当前的浏览器。
100赞 user447963 9/15/2010 #3

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

因此,请使用它而不是脚本标签:

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

评论

0赞 Youstay Igo 11/4/2015
你们如何添加
0赞 fregante 3/10/2016
在属性中编写大量代码不是很实用。此外,这需要存在并加载一个附加文件。陌陌的解决方案与其说是妥协,不如说是妥协。onload
0赞 user3526 4/12/2016
两个缺点:1.它不能调用通过innerHTML(以及此IMG标签)添加的任何脚本/函数,因为它们在浏览器方面不存在2。如果 “.removeChild()” 之前的部分内联代码引发异常,则不会删除 img 元素。
0赞 newshorts 8/30/2016
一个快速的观点。如果您在其上方加载更大的图像,此解决方案可能无法为您提供准确的结果(因为它们可能比空 gif 下载时间更长)。
24赞 Danny '365CSI' Engelman 10/20/2016
您可以将触发器图像 Base64 编码为(这不会执行网络请求)实际上......您不需要映像,引用不存在的映像而不是使用(但这将执行网络请求)<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">onloadonerror
52赞 Pablo Moretti 8/14/2011 #4

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

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

这适用于所有浏览器:)

评论

1赞 Eli Grey 8/14/2011
除非文档中没有任何其他脚本元素。请改用。document.documentElement
4赞 Pablo Moretti 8/14/2011
不是必需的,因为您正在从另一个脚本编写脚本。<script> var g = document.createElement('script'); var s = document.getElementsByTagName('script')[0]; //reference this script g.text = "alert(\"hi\");" s.parentNode.insertBefore(g, s); </script>
3赞 Eli Grey 8/15/2011
谁说它来自另一个剧本?您可以在没有元素的情况下运行 JavaScript。例如 和。如果你想成为技术,这在非 HTML/SVG 文档中也不起作用,因为名称间距不明确。<script><img onerror="..." src="#"><body onload="...">
2赞 geoyws 2/9/2015
Facebook 在他们的 SDK 中使用了 Pablo 的答案。developers.facebook.com/docs/javascript/quickstart/v2.2#loading
-1赞 Knowledge Serve 1/22/2013 #5

从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());

    });`

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

31赞 Firas Nizam 4/29/2013 #6

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

var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
    eval(arr[n].innerHTML)//run script inside div

评论

1赞 gsinha 7/6/2014
谢谢。它解决了我将 Disqus Universal 代码添加到使用 TinyBox2 Jquery 插件创建的模态弹出窗口的问题。
5赞 Jose Gómez 7/16/2015
遗憾的是,当脚本包含稍后将调用的函数时,此解决方案不起作用。
133赞 mjs 12/14/2013 #7

下面是一个以递归方式将所有脚本替换为可执行脚本的方法:

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]);

评论

13赞 davidmh 4/3/2014
我有点惊讶你的答案,它一直向下。恕我直言,这是最好的解决方案,这种方法甚至允许您限制具有特定 url 或内容的脚本。
0赞 Bao Thai 4/21/2017
[0] 的用途是什么?你能使用nodeScriptReplace(document.getElementById().html吗);
0赞 mjs 8/5/2017
@BaoThai 是的。您可以。
0赞 Dave 12/4/2017
在 IWebBrowser2 中似乎没有帮助;我可以确认使用 createElement 重新创建脚本标签,但我仍然无法通过 InvokeScript() 调用它们。
1赞 Johannes Ewald 9/15/2021
很棒的解决方案,谢谢:)如果有人正在寻找更现代的版本:stackoverflow.com/a/69190644/1343851
1赞 user3198805 1/29/2014 #8

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

享受他的解决方案:

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

重要提示:

  1. 您需要用 div 标签包装目标元素
  2. 您需要用 div 标签包装 src 字符串。
  3. 如果您直接编写 src 字符串并且它包含 js 部分,请注意正确编写结束脚本标签(在 / 之前使用 \),因为这是一个字符串。
1赞 iirekm 5/7/2014 #9

使用 代替 。$(parent).html(code)parent.innerHTML = code

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

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
    })
}

评论

1赞 Stavm 11/23/2016
这里有点晚了,但任何可能使用此方法的人都注意到,在 JQuery 中,您需要使用 $.loadScript(url) 而不是 <script src=“url></script> 加载脚本 - 后者将导致浏览器上出现已弃用的 Synchronous XMLHttpRequest 错误。
5赞 jamesblacklock 11/4/2016 #10

对于仍在尝试执行此操作的任何人,不,您不能使用 注入脚本,但可以使用 和 将字符串加载到脚本标记中。innerHTMLBlobURL.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)
    });
}

我已经从我的实际实现中简化了这一点,所以不能保证其中没有任何错误。但这个原则是有效的。

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

评论

1赞 iPherian 4/22/2017
我刚刚尝试过,它可以在 chrome 57 上运行。脚本标记上的 innerHTML 执行文本。
0赞 jamesblacklock 5/3/2017
这很有趣,它以前不起作用。我想知道这种行为是跨浏览器还是仅在 chrome 57 中。
5赞 Adnan Korkmaz 5/2/2017 #11

下面是一个递归函数,用于设置我在广告服务器中使用的元素的 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);
    }
}

您可以在此处查看演示。

0赞 gabriel garcia 10/6/2017 #12

我对这个问题的解决方案是设置一个 Mutation Observer 来检测节点,然后将其替换为具有相同 src 的新节点。例如:<script></script><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 11/16/2018
谢谢你对我投反对票,但没有说出原因。爱你们所有人。
0赞 Jan Turoň 8/20/2020
说明:此代码片段(直接添加到 HTML)确保将来添加为 innerHTML 的任何外部脚本都将被解析。我发现这是一个不错的通用想法,但我没有投赞成票,因为担心添加的脚本是否可以执行两次?改变变异的对象也让我感到害怕——我不确定它在某些情况下是否不能产生无限循环。document.createElement
1赞 Gray 2/10/2018 #13

尝试使用 template 和 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>

评论

1赞 Soul 3/23/2018
这不适用于 Microsoft Edge,还有其他解决方法吗?
7赞 Jake 6/27/2018 #14

你可以这样做:

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);
}
0赞 pixelherodev 10/14/2018 #15

加布里埃尔·加西亚(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
}

评论

0赞 gabriel garcia 6/15/2019
您确实注意到,每次将元素附加到文档中时,您都会添加一个新元素,对吗?顺便说一句,我想知道你为什么说我的代码不起作用。MutationObserver
0赞 pixelherodev 6/20/2019
@gabrielgarcia 我说你的代码不起作用,因为我试过了,它根本不起作用。现在看,这完全有可能是我,而不是你,我真诚地为我的措辞方式道歉。现在修复它。
0赞 pixelherodev 6/20/2019
回复:每次将元素添加到文档中时添加 MutationObserver,您在说什么?DOMContentLoaded,我在这里引用 MDN 的话,“当初始 HTML 文档完全加载和解析时触发,而无需等待样式表、图像和子帧完成加载。那是一次,而且只有一次。此外,这个脚本在我的网站上没有问题,调试显示它只发生一次,所以它在实践中和理论上都是一次。
1赞 gabriel garcia 6/21/2019
你是对的......我错过了它。我也深表歉意。
0赞 pixelherodev 6/23/2019
@gabrielgarcia 没问题:)
7赞 colxi 5/22/2019 #16

这里有一个不使用 的解决方案,它适用于脚本、链接脚本以及模块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;
}

评论

0赞 Kevin B 7/24/2019
我的意思是。。。在脚本标签上附加代码内容是一种评估,不是吗?
1赞 colxi 7/24/2019
@KevinB 有臭名昭著的差异......尝试一下,你会看到最明显的一个eval('console.log(this)')
0赞 Kevin B 7/24/2019
所以上下文不同,而且?它仍然只是一个评估。
1赞 colxi 7/24/2019
@KevinB 不,这不是一个评估。试试这个..然后尝试从评估外部访问....祝你好运,你将需要它eval('let b=100')b
1赞 Bezzzo 3/19/2020
对我有用。干杯
1赞 naden 9/6/2019 #17

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

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

请注意:里面的作用域指的是 iframe,因此您必须使用上面示例中的示范器来访问父文档。srcdoctop

26赞 daniellalasa 11/12/2019 #18

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

将动态 Node 导入 “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);

评论

5赞 Martin 7/23/2020
元素具有 HTMLScriptElement 接口,因此只需设置内联代码,无需创建和追加新的文本节点。newScriptnewScript.text = "alert('Hello World!')";
1赞 daniellalasa 3/30/2021
@Martin 当然,在编程世界中有很多不同的实现方式!此外,这:)更清晰和可维护。
-1赞 Luis Tiago Flores Cristóvão 4/25/2020 #19

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

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

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

51赞 Tilak Madichetti 6/29/2020 #20

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

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

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

注意:这使用 ES6。

我见过很多答案使用,它的工作原理与 .appendChildappend

评论

0赞 Yash Vekaria 11/30/2021
你可以使用这个不错的 React 组件 - github.com/christo-pr/dangerously-set-html-content
0赞 Tilak Madichetti 12/5/2021
这是一个相对较新的解决方案,可以解决一个老问题:P
1赞 eeerrrttt 2/11/2022
这不会运行脚本。
1赞 Neyelson Alves 12/21/2022
它确实按预期工作。我设法把所有东西都放在一行中。
2赞 Berci 5/12/2023
谢谢你的回答!很棒的解决方案!小澄清:并且工作方式不完全相同:接受文本/字符串,而方法只能用于将 Node 对象插入 DOM 中。在某些情况下,首选其中之一(如果要强制使用 Node 对象并避免使用代码中的字符串,或者要附加字符串)。但在大多数情况下,您可以使用其中任何一个。appendChild appendappend appendChild<p>
0赞 Jan Turoň 8/20/2020 #21

基于 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 atribute会执行。如果脚本未执行(添加为 innerHTML),则脚本将在 image onload 事件中执行。如果脚本已加载(由后端添加),则定义变量,并且 onload 不会第二次运行该脚本。script01

评论

2赞 Danny '365CSI' Engelman 8/23/2020
TNX,这是 2016 年的答案。我现在要做的是:<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>" onload="console.log(21,this)"/>
4赞 Ozan ERTÜRK 1/8/2021 #22

过滤脚本标签并使用 eval 运行每个标签

var tmp=  document.createElement('div');
tmp.innerHTML = '<script>alert("hello")></script>';
[...tmp.children].filter(x => x.nodeName === 'SCRIPT').forEach(x => eval(x.innerText));

评论

0赞 Vael Victus 4/3/2022
当您相信自己的输入时,简单明了。这是我的实现:[...document.querySelector('#content').children].filter(x => x.nodeName === 'SCRIPT').forEach(x => eval(x.innerText));
12赞 Johannes Ewald 9/15/2021 #23

这是 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 的替代解决方案,但没有奏效。

评论

1赞 lionelbrits 4/13/2022
就我而言,添加的 HTML 包含两个脚本标记,第一个加载外部脚本,第二个内联引用它。这会导致一个错误,指出未定义外部脚本导出的变量。如果我直接包含两个脚本标签,代码就可以工作。我的猜测是,在执行第二个脚本标记时,第一个脚本标记尚未完全完成。
3赞 WickyNilliams 11/23/2021 #24

我自己的风格,使用现代JS和打字稿。不知道为什么人们在querySelector就在那里时过滤等等。tagName

对我来说很有魅力:

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)
  })
}
-1赞 admirhodzic 2/14/2022 #25

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

    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)
      })      
  })
0赞 Neyelson Alves 12/21/2022 #26

单线解决方案如下:

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

评论

0赞 Michael 2/26/2023
所以问题是,当我尝试序列化包含脚本标签的 HTML 时,“<”被替换为“\x3C”,通过将序列化的 HTML 分配回 innerHTML 的元素来破坏我的反序列化它们。