可以使用innerHTML插入脚本吗?


223

我想一些脚本加载到使用页面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>

Answers:


88

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

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

通常,如果您要eval()亲自进行操作,则希望创建/发送没有任何HTML标记(例如)的脚本代码<script>,因为这些标记将无法eval()正常使用。


12
我真正想做的是加载一个外部脚本,而不仅仅是评估一些本地脚本。使用innerHTML添加脚本标签比创建脚本DOM元素并将其添加到正文要短得多,我正在尝试使代码尽可能短。您是否需要创建dom脚本元素并将其添加到dom中,而不仅仅是使用innerHTML之类的元素?有没有办法从一个函数中使用document.write?
克雷格

5
正如zombat所建议的那样,请使用Javascript框架加载外部脚本,而不要重蹈覆辙。JQuery使这个过程非常容易,只需包含JQuery并调用:$ .getScript(url)。您还可以提供一个回调函数,该函数将在脚本加载后立即执行。
阿里埃勒·波波夫斯基

2
Ariel是对的。我感谢尝试使您的代码简短,并添加带有的<script>标签innerHTML可能很短,但这是行不通的。在通过之前,所有内容都是纯文本eval()。可悲的是,eval()它不解析HTML标记,因此最终会带来一系列问题。
zombat

21
eval()并不是解决任何问题的好方法。
buley 2011年

2
我自己尝试过eval()。这是一个可怕的想法。你必须EVAL整个事情每次。即使您声明了变量名和值,也必须每次重新声明/重新评估()才能使它起作用。这是错误的噩梦。
Youstay Igo

88

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

因此,使用它代替脚本标签:

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


12
这太棒了!

当插入到ajax请求的结果中时,对我不起作用:语法错误丢失;在脚本字符串开头的语句之前
Oliver 2014年

您如何将&lt; img src ...行添加到页面代码中?您使用document.write()还是使用document.body.innerHTML + =方法?两者都对我来说是失败的:(
Youstay Igo 2015年

onload属性内编写大量代码不是很实际。另外,这需要存在一个附加文件并进行加载。momo的解决方案不太妥协。
fregante

15
您可以将Base64编码的触发图像编码为<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">(这不会执行网络请求)实际上...您不需要图像,引用不存在的图像并且可以onload使用它onerror(但它将执行网络请求)
Danny '365CSI'Engelman

82

这是一种用可执行文件递归替换所有脚本的方法:

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

示例调用:

nodeScriptReplace(document.getElementsByTagName("body")[0]);

9
我很惊讶您的答案一路下滑。恕我直言,这是最好的解决方案,该方法甚至允许您使用特定的URL或内容来限制脚本。
davidmh 2014年

1
@ inf3rno是还是不是?它曾经工作过,有人声称有什么不同吗?
毫米

[0]的目的是什么?你可以使用nodeScriptReplace(document.getElementById()。html);
宝泰

@BaoThai是的。您可以。
mmm

在IWebBrowser2中似乎没有帮助;我可以确认使用createElement重新创建了脚本标签,但是仍然无法通过InvokeScript()调用它们。
戴夫

46

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

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

这适用于所有浏览器:)


1
除非文档中没有其他脚本元素。使用document.documentElement代替。
伊莱·格雷

4
不需要,因为您是从另一个脚本编写脚本。 <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
谁说这是来自另一个脚本?您可以运行不带<script>元素的JavaScript 。例如<img onerror="..." src="#"><body onload="...">。如果您想成为技术专家,由于命名空间不明确,这在非HTML / SVG文档中将不起作用。
伊莱·格雷

2
Facebook在其SDK中使用Pablo的答案。developers.facebook.com/docs/javascript/quickstart/v2.2#loading
geoyws

30

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

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

1
谢谢。它解决了我将Disqus通用代码添加到使用TinyBox2 Jquery插件创建的模式弹出窗口的问题。
gsinha 2014年

3
不幸的是,当脚本包含稍后将要调用的函数时,此解决方案将无法使用。
JoseGómez2015年

7

我用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);

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

我已经从我的实际实现中简化了此操作,因此无法保证其中没有任何错误。但是原理是可行的。

如果您不希望在脚本运行后重新获得任何价值,那会更加容易。只需删除Promiseonload位即可。您甚至不需要包装脚本或创建global window.__load_script_exports_属性。


1
我刚刚尝试过,它可在chrome 57上运行。script标签上的innerHTML执行文本。
iPherian '17

很有意思,以前没用过。我想知道这种行为是跨浏览器还是仅适用于
Chrome57。– JayArby

4

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

您可以在此处查看演示。


3

您可以这样做:

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

3

这里的解决方案不使用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;
}

我的意思是...在脚本标签后加上代码内容是一个评估,不是吗?
凯文B

@KevinB有一个臭名昭著的区别...尝试eval('console.log(this)'),您将看到最明显的
区别

所以上下文是不同的?它仍然只是一个评估。
凯文B

@KevinB不,它不是一个评估。试试这个eval('let b=100')..然后尝试b从外部访问.. 祝你好运,你将需要它
colxi

为我工作。干杯
Bezzzo

1

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

享受他的解决方案:

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

重要笔记:

  1. 您需要使用div标签包装目标元素
  2. 您需要使用div标签包装src字符串。
  3. 如果您直接编写src字符串,并且其中包含js部分,请注意正确编写关闭脚本标签(在/之前加\),因为这是一个字符串。

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

资源


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

1

尝试使用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
这不适用于Microsoft Edge,还有其他解决方法吗?
灵魂

1

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

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

请注意:内部范围srcdoc是指iframe,因此您必须使用top如上例中的来访问父文档。


0

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

16
物有所值:在当前浏览器上似乎不起作用。
Wichert Akkerman

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

1
感谢您对我的投票,没有说为什么。爱你们
加布里埃尔·加西亚

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
}

您确实注意到,MutationObserver每次将元素添加到文档中时,您都在添加新元素,对吧?顺便说一句,我想知道为什么你说我的代码不起作用。
加布里埃尔·加西亚

@gabrielgarcia我说您的代码不起作用,因为我尝试了它,但是根本不起作用。现在看,这完全有可能是我本人,而不是您,我对此表示诚挚的歉意。立即修复。
pixelherodev19年

回复:每次将元素添加到文档中时,都添加MutationObserver,这是在说什么?我在这里引用MDN来引用DOMContentLoaded,“当初始HTML文档已完全加载并解析,而无需等待样式表,图像和子帧完成加载时,就会触发。” 那是一次,而且只有一次。此外,此脚本在我的网站上没有任何问题,调试显示它只发生一次,因此在实践和理论上都是一次。
pixelherodev19年

1
你是对的...我错了。我也道歉。
加布里埃尔·加西亚

@gabrielgarcia没问题:)
pixelherodev

0

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

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

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


-1

从innerHTML执行(Java脚本)标签

将Script元素替换为具有类属性class =“ javascript”的div,然后将其关闭 </div>

请勿更改要执行的内容(以前它在脚本标签中,现在在div标签中)

在页面中添加样式...

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

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

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

    });`

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.