可以使用innerHTML插入脚本吗?

时间:2009-07-29 01:01:13

标签: javascript html dom innerhtml

我尝试在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>

22 个答案:

答案 0 :(得分:79)

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

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

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

答案 1 :(得分:74)

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

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

通常,如果您要自己eval(),则需要创建/发送脚本代码而不使用任何HTML标记,例如<script>,因为这些标记不会eval()正确。

答案 2 :(得分:68)

这是一个以递归方式替换所有脚本的方法:

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

答案 3 :(得分:38)

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

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

适用于所有浏览器:)

答案 4 :(得分:25)

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

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

答案 5 :(得分:5)

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

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

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

注意:已使用ES6

编辑1: 为大家澄清-我已经看到很多答案都使用appendChild,并想让大家知道它的工作原理与append

答案 6 :(得分:4)

对于仍在尝试执行此操作的任何人,不,您不能使用innerHTML注入脚本,但可以使用Blob和{{1将字符串加载到脚本代码中}}

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

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) }); } Promise位。您甚至不需要包装脚本或创建全局onload属性。

答案 7 :(得分: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));

答案 8 :(得分:2)

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

您可以看到演示here

答案 9 :(得分:1)

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

享受他的解决方案:

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

重要说明:

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

答案 10 :(得分: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
    })
}

Source

答案 11 :(得分:0)

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

let parentNode = /* your node */ 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});

答案 12 :(得分:0)

Danny'365CSI'Engelman的评论为基础,这是一种通用解决方案:

(SELECT ...
SUM(B) as Sum_of_transactions, COUNT(B) as Number_of_transactions 
...
GROUP BY A, C)

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

说明:不执行作为innerHTML加载的脚本,而是执行onload内容属性。如果未执行脚本(添加为innerHTML),则脚本将在图像加载事件中执行。如果脚本已加载(由后端添加),则将定义<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)"> 变量,并且onload将不会第二次运行脚本。

答案 13 :(得分:0)

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

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


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

innerHTML中没有脚本标签!

事实证明,HTML5不允许使用innerHTML属性动态添加脚本标签。因此,以下操作将不会执行,并且不会发出警告“ Hello World”!

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

这已记录在HTML5规范中:

  

注意:使用innerHTML插入的脚本元素在以下情况下不执行   它们被插入。

但是要注意,这并不意味着innerHTML可以安全地进行跨站点脚本编写。可以通过innerHTML执行JavaScript,而无需使用MDN's innerHTML page所示的标签。

解决方案:动态添加脚本

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

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

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

答案 14 :(得分:0)

您还可以像这样包装itemList.forEach { reservation -> val entityObj = reservation.getParseObject("object_entity") if (entityObj == null) createLog("FoundObj", "null") else createLog("FoundObj", entityObj.objectId.toString()) } ,它将被执行:

<script>

请注意:<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>'; 中的范围是指iframe,因此您必须像上例中那样使用srcdoc来访问父文档。

答案 15 :(得分:0)

这里的解决方案不使用first_list = [ ('1234', 'abcd', 'John Doe', 'good_status'), ('1234', 'efgh', 'John Doe', 'good_status'), ('1234', 'hijk', 'John Doe', 'bad_status'), ('5566', 'abjk', 'George Washington', 'good_status'), ('7889', 'zyxw', 'Jane Austin', 'bad_status') ] second_list = [ ('1234', 'John Doe', 'abcd efgh hijk'), ('5566', 'George Washington', 'abjk'), ('7889', 'Jane Austin', 'zyxw') ] def CreateDict(list1, list2): #Dictionary to be created. dictionary = {} #Go through each given data. for search_key in list2: dict_data = [search_key[1]] status = "" #Go through the data we must check for the status. for element in list1: #If the number and the names match. if search_key[0] == element[0] and search_key[1] == element[2]: #Check the status of each data. data = search_key[2].split(" ") for d in data: if d in element: # Data| status without the _status | add a space at the end. status += d+"_"+element[3].replace("_status", "") +" " #Remove the last space added on the status string. status = status[:len(status)-1] #Append the status data on the list. dict_data.append(status) #Append the dict_data using the number as a key. dictionary[str(search_key[0])] = dict_data #Return the dictionary. return dictionary print(CreateDict(first_list, second_list)) ,并且可以使用脚本链接脚本以及模块

该函数接受3个参数:

  • html :带有要插入的html代码的字符串
  • 目标:对目标元素的引用
  • 追加:布尔值标志,用于允许在目标元素html的末尾追加
eval

答案 16 :(得分: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
}

答案 17 :(得分:0)

您可以这样做:

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

答案 18 :(得分:0)

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

{{1}}

答案 19 :(得分: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;  
}

答案 20 :(得分:-1)

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

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

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

答案 21 :(得分:-2)

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

    });`

您可以在我的博客上探索更多here