可以用 innerHTML 插入脚本吗?

Posted

技术标签:

【中文标题】可以用 innerHTML 插入脚本吗?【英文标题】:Can scripts be inserted with innerHTML? 【发布时间】:2010-11-14 22:00:40 【问题描述】:

我尝试在<div> 上使用innerhtml 将一些脚本加载到页面中。脚本似乎加载到 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>

【问题讨论】:

【参考方案1】:

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

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

通常,如果您要自己去eval(),您希望创建/发送不带任何 HTML 标记(例如 &lt;script&gt;)的脚本代码,因为这些不会正确地 eval()

【讨论】:

我真正想做的是加载一个外部脚本,而不仅仅是评估一些本地脚本。使用 innerHTML 添加脚本标记比创建脚本 DOM 元素并将其添加到正文要短得多,我正在尝试使我的代码尽可能短。您是否必须创建 dom 脚本元素并将它们添加到 dom 中,而不仅仅是使用像 innerHTML 这样的东西?有没有办法在函数中使用 document.write 来做到这一点? 正如 zombat 建议的那样,使用 javascript 框架来加载外部脚本,不要试图重新发明***。 JQuery 使这非常容易,只需包含 JQuery 并调用:$.getScript(url)。您还可以提供一个回调函数,该函数将在脚本加载后执行。 爱丽儿是对的。我很欣赏尝试使您的代码保持简短,并添加带有innerHTML&lt;script&gt; 标记可能很短,但它不起作用。在通过eval() 运行之前,这一切都只是纯文本。可悲的是,eval() 不解析 HTML 标签,所以你最终会遇到一系列问题。 eval() 不是解决任何问题的好方法。 我自己试过 eval()。这是一个可怕的想法。你必须评估整个事情每次。即使您声明了一个变量名和值,您也必须每次重新声明/重新评估()它才能使其工作。这是一场错误的噩梦。【参考方案2】:

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

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

【讨论】:

我有点惊讶你的回答一直都在下降。恕我直言,这是最好的解决方案,这种方法甚至可以让您限制具有特定 url 或内容的脚本。 [0]的目的是什么?你能用 nodeScriptReplace(document.getElementById().html); 这太神奇了。完美运行 很棒的解决方案,谢谢 :) 如果有人正在寻找更现代的版本:***.com/a/69190644/1343851 这太棒了。谢谢。【参考方案3】:

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

所以用这个代替脚本标签:

&lt;img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" /&gt;

【讨论】:

当插入到 ajax 请求的结果中时对我不起作用:缺少语法错误;脚本字符串开头的 before 语句 你们如何将 <img src... 行添加到您的页面代码中?你是 document.write() 还是使用 document.body.innerHTML+= 方法?两者对我来说都失败了:( onload 属性中编写大量代码不太实用。这也需要一个额外的文件存在并被加载。 momo's solution 不是妥协。 两个缺点:1.它不能调用通过innerHTML(连同这个IMG标签)添加的任何脚本/函数,因为它们在浏览器中不存在2.如果部分内联代码在 ".removeChild()" 抛出异常之前,不会删除 img 元素。 您可以 Base64 将您的触发图像编码为 &lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"&gt;(这不会执行网络请求)实际上...您不需要图像,引用不存在的图像而不是 @ 987654326@ 使用onerror (但这会做一个网络请求)【参考方案4】:

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

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

这适用于所有浏览器:)

【讨论】:

除非文档中没有任何其他脚本元素。请改用document.documentElement 没有必要,因为您正在从另一个脚本编写脚本。 &lt;script&gt;var g = document.createElement('script');var s = document.getElementsByTagName('script')[0]; //reference this scriptg.text = "alert(\"hi\");"s.parentNode.insertBefore(g, s);&lt;/script&gt; 谁说它来自另一个脚本?您可以在没有 &lt;script&gt; 元素的情况下运行 JavaScript。例如。 &lt;img onerror="..." src="#"&gt;&lt;body onload="..."&gt;。如果您想成为技术人员,由于不明确的命名空间,这在非 HTML/SVG 文档中也不起作用。 Facebook 在他们的 SDK 中使用了 Pablo 的答案。 developers.facebook.com/docs/javascript/quickstart/v2.2#loading【参考方案5】:

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

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

【讨论】:

谢谢。它解决了我将 Disqus 通用代码添加到使用 TinyBox2 Jquery 插件创建的模式弹出窗口的问题。 不幸的是,当脚本包含稍后将调用的函数时,此解决方案不起作用。【参考方案6】:

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

【讨论】:

您的newScript 元素具有HTMLScriptElement interface,因此您只需使用newScript.text = "alert('Hello World!')"; 设置内联代码,无需创建和附加新的文本节点。 @Martin 当然,在编程世界中有很多不同的方式来实现东西!这也更清晰和可维护:)。【参考方案7】:

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

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

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

注意:使用 ES6

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

【讨论】:

你可以使用这个不错的 react 组件 - github.com/christo-pr/dangerously-set-html-content 这很完美,很容易成为这里最简单的解决方案。我不明白为什么它的票数不高。 它是一个相对较新的旧问题解决方案:P【参考方案8】:

你可以这样做:

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

【讨论】:

【参考方案9】:

这里的解决方案不使用eval,并且适用于scriptslinked scripts,以及modules

该函数接受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;

【讨论】:

我的意思是......在脚本标签中附加代码内容是一个评估,不是吗? @KevinB 有明显的不同...试试eval('console.log(this)'),你会看到最明显的一个 所以上下文不同,而且?它仍然只是一个评估。 @KevinB 不,这不是评估。试试这个eval('let b=100') .. 然后尝试从评估外部访问b .... 祝你好运,你会需要它 为我工作。干杯【参考方案10】:

对于仍在尝试执行此操作的任何人,不,您不能使用innerHTML 注入脚本,但可以使用BlobURL.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 位。您甚至不需要包装脚本或创建全局 window.__load_script_exports_ 属性。

【讨论】:

我刚试过,它适用于 chrome 57。脚本标签上的 innerHTML 执行文本。 这很有趣,它以前不起作用。我想知道这种行为是跨浏览器还是仅在 chrome 57 中。【参考方案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);
    

你可以看演示here。

【讨论】:

【参考方案12】:

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

【讨论】:

对于它的价值:这似乎不适用于当前的浏览器。【参考方案13】:

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

享受他的解决方案:

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

重要提示:

    你需要用div标签包裹目标元素 你需要用div标签包裹src字符串。 如果你直接写src字符串并且它包含js部分,请注意正确写结束脚本标签(在/之前带有\),因为这是一个字符串。

【讨论】:

【参考方案14】:

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

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

【讨论】:

【参考方案15】:

使用$(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

【讨论】:

这里有点晚了,但是任何可能使用此方法的人,请注意在 JQuery 中您需要使用 $.loadScript(url) 而不是 【参考方案16】:

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

【讨论】:

这不适用于 Microsoft Edge,还有其他解决方法吗?【参考方案17】:

你也可以像这样包装你的&lt;script&gt;,它会被执行:

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

请注意:srcdoc 中的作用域指的是 iframe,因此您必须像上例一样使用top 来访问父文档。

【讨论】:

【参考方案18】:

这是mmm's awesome solution 的更现代(更简洁)的版本:

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

【讨论】:

【参考方案19】:

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

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

【讨论】:

感谢您在不说明原因的情况下对我投反对票。爱你们。 解释:这个sn-p(直接添加到HTML)确保任何将来添加为innerHTML的外部脚本都将被解析。我发现这是一个很好的通用想法,但我没有赞成,因为担心document.createElement 添加的脚本是否可能被执行两次?改变变异对象也让我害怕——我不确定它在某些情况下是否不能产生无限循环。【参考方案20】:

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 我说你的代码不起作用,因为我试过了,但它根本不起作用。现在看,完全有可能是我,而不是你,我真诚地为我的措辞道歉。现在修复它。 re: 每次向文档中添加元素时添加一个MutationObserver,你在说什么? DOMContentLoaded,我在这里引用 MDN,“在初始 HTML 文档完全加载和解析时触发,无需等待样式表、图像和子框架完成加载。”这是一次,而且只有一次。此外,这个脚本在我的网站上运行没有问题,调试显示它只发生一次,所以它在实践中和理论上都是一次。 你是对的......我错过了它。我也很抱歉。 @gabrielgarcia 没问题 :)【参考方案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 内容属性会被执行。如果脚本未执行(添加为 innerHTML),则在图像 onload 事件中执行脚本。如果脚本已加载(由后端添加),则定义了 script01 变量,并且 onload 将不会第二次运行脚本。

【讨论】:

tnx,这是 2016 年的答案。我现在会这样做:&lt;img src="data:image/svg+xml,&lt;svg xmlns='http://www.w3.org/2000/svg'/&gt;" onload="console.log(21,this)"/&gt;【参考方案22】:

我自己的转折,使用现代 JS 和 typescript。当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)
  )

【讨论】:

【参考方案23】:

从 innerHTML 执行(Java 脚本)标签

将您的脚本元素替换为具有类属性 class="javascript" 的 div 并用 &lt;/div&gt; 关闭它

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

在页面中添加样式...

&lt;style type="text/css"&gt; .javascript display: none; &lt;/style&gt;

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

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

    );`

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

【讨论】:

【参考方案24】:

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

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

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

【讨论】:

以上是关于可以用 innerHTML 插入脚本吗?的主要内容,如果未能解决你的问题,请参考以下文章

用 innerHTML 添加 php 代码

使用innerhtml将脚本标签插入到html元素中[重复]

标签的innerHTML属性和html()

创建/附加节点与innerHTML

JavaScript中,可以用元素的innerHTML直接添加子元素吗?

用里面的脚本设置innerHTML [重复]