可以使用innerHTML插入脚本吗?

Cra*_*aig 194 html javascript dom innerhtml

我尝试使用innerHTMLa 将一些脚本加载到页面中<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>
Run Code Online (Sandbox Code Playgroud)

小智 80

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

因此,请使用此代替脚本标记:

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

  • 你可以将你的触发器图像Base64编码为``<img rel="nofollow noreferrer" src ="data:image/gif; base64,R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">``(这不会做网络请求)实际上......你不需要一个图像,引用一个不存在的图像而不是``onload``使用``onerror``(但这会做一个网络请求) (14认同)
  • 这太棒了! (11认同)
  • 非常好,并在所有浏览器中完全工作! (2认同)

zom*_*bat 78

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

MooTools会自动为您完成此操作,我确信jQuery也会这样(取决于版本.jQuery版本1.6+使用eval).这节省了解析<script>标签和转义内容的麻烦,以及一堆其他"陷阱".

通常,如果你eval()自己去做,你想创建/发送脚本代码而不使用任何HTML标记<script>,因为这些标记不eval()正确.

  • 对于任何问题,eval()都不是一个很好的解决方案. (18认同)
  • 我真正想要做的是加载一个外部脚本,而不仅仅是eval一些本地脚本.使用innerHTML添加脚本标记比创建脚本DOM元素并将其添加到正文要短得多,我正在努力使我的代码尽可能短.你是否必须创建dom脚本元素并将它们添加到dom而不是仅仅使用innerHTML之类的东西?有没有办法在函数中使用document.write执行此操作? (12认同)
  • 正如zombat建议的那样,使用Javascript框架加载外部脚本,不要试图重新发明轮子.JQuery使这非常简单,只需包含JQuery并调用:$ .getScript(url).您还可以提供一个回调函数,该函数将在加载脚本后执行. (5认同)
  • 阿里尔是对的.我很欣赏尝试保持代码简短,并且添加带有`innerHTML`的`<script>'标签可能很短,但它不起作用.它只是纯文本,直到它通过`eval()`运行.遗憾的是,`eval()`不解析HTML标记,因此最终会出现一系列问题. (2认同)
  • 我自己尝试过eval()。这是一个可怕的想法。您必须评估整个过程“每次”。即使您声明了变量名和值,也必须每次重新声明/重新赋值才能使它起作用。这是错误的噩梦。 (2认同)

mom*_*omo 70

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

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

        return node;
}
function nodeScriptIs(node) {
        return node.tagName === 'SCRIPT';
}
function nodeScriptClone(node){
        var script  = document.createElement("script");
        script.text = node.innerHTML;
        for( var i = node.attributes.length-1; i >= 0; i-- ) {
                script.setAttribute( node.attributes[i].name, node.attributes[i].value );
        }
        return script;
}
Run Code Online (Sandbox Code Playgroud)

示例电话:

nodeScriptReplace(document.getElementsByTagName("body")[0]);
Run Code Online (Sandbox Code Playgroud)

  • 我有点惊讶你的答案一直都在下降.恕我直言,这是最好的解决方案,这种方法甚至允许您限制具有特定网址或内容的脚本. (8认同)

Pab*_*tti 39

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

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

这适用于所有浏览器:)

  • 没有必要,因为您正在从另一个脚本编写脚本.`<script>``var g = document.createElement('script');``var s = document.getElementsByTagName('script')[0]; //引用这个脚本``g.text ="alert(\"hi \");"``s.parentNode.insertBefore(g,s);``</ script>` (4认同)
  • 谁说这是来自另一个剧本?您可以在没有`<script>`元素的情况下运行JavaScript.例如`<img rel="nofollow noreferrer" onerror ="..."src ="#">`和`<body onload ="...">`.如果您想要技术性,由于不明显的命名空间,这在非HTML/SVG文档中不起作用. (3认同)
  • Facebook在他们的SDK中使用了Pablo的答案.https://developers.facebook.com/docs/javascript/quickstart/v2.2#loading (2认同)

Fir*_*zam 25

我用过这段代码,工作正常

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

  • 不幸的是,当脚本包含稍后将调用的函数时,此解决方案不起作用. (3认同)

p.d*_*iel 14

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

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


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

innerHTML 中没有脚本标签!

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

element.innerHTML = "<script>alert('Hello World!')</script>";
Run Code Online (Sandbox Code Playgroud)

这记录在 HTML5 规范中:

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

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

解决方案:动态添加脚本

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

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

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

和内联脚本:

var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript); 
target.appendChild(newScript);
Run Code Online (Sandbox Code Playgroud)

  • 您的 newScript 元素具有 [HTMLScriptElement 接口](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement),因此您只需使用 newScript.text = "alert 设置内联代码即可('Hello World!')";` 无需创建和附加新的文本节点。 (5认同)

Til*_*ddy 13

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

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

  const scriptEl = document.createRange().createContextualFragment(html);
  parent.append(scriptEl);
Run Code Online (Sandbox Code Playgroud)

注意:使用 ES6

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

  • 谢谢您的回答!很好的解决方案!小澄清:“appendChild”和“append”的工作方式并不完全相同:“append”接受文本/字符串,而“appendChild”方法只能用于将 Node 对象插入到 DOM 中。在某些情况下,首选其中之一(如果您想强制使用 Node 对象并避免在代码中使用像“&lt;p&gt;”这样的字符串,或者如果您想附加字符串)。但在大多数情况下,您可以使用其中任何一种。 (3认同)
  • 这不会运行脚本。 (2认同)

Joh*_*ald 12

这是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);
  });
}
Run Code Online (Sandbox Code Playgroud)

注意:我还尝试过使用cloneNode()outerHTML的替代解决方案,但这不起作用。


Jak*_*ake 7

你可以这样做:

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);
}
Run Code Online (Sandbox Code Playgroud)


Jay*_*rby 5

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

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

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)
    });
}
Run Code Online (Sandbox Code Playgroud)

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

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


Adn*_*maz 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在此处查看演示。


col*_*lxi 5

这里一个解决方案,不使用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;
}
Run Code Online (Sandbox Code Playgroud)