可以用 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
)。这省去了解析<script>
标签和转义您的内容以及一堆其他“陷阱”的麻烦。
通常,如果您要自己去eval()
,您希望创建/发送不带任何 HTML 标记(例如 <script>
)的脚本代码,因为这些不会正确地 eval()
。
【讨论】:
我真正想做的是加载一个外部脚本,而不仅仅是评估一些本地脚本。使用 innerHTML 添加脚本标记比创建脚本 DOM 元素并将其添加到正文要短得多,我正在尝试使我的代码尽可能短。您是否必须创建 dom 脚本元素并将它们添加到 dom 中,而不仅仅是使用像 innerHTML 这样的东西?有没有办法在函数中使用 document.write 来做到这一点? 正如 zombat 建议的那样,使用 javascript 框架来加载外部脚本,不要试图重新发明***。 JQuery 使这非常容易,只需包含 JQuery 并调用:$.getScript(url)。您还可以提供一个回调函数,该函数将在脚本加载后执行。 爱丽儿是对的。我很欣赏尝试使您的代码保持简短,并添加带有innerHTML
的<script>
标记可能很短,但它不起作用。在通过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
所以用这个代替脚本标签:
<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />
【讨论】:
当插入到 ajax 请求的结果中时对我不起作用:缺少语法错误;脚本字符串开头的 before 语句 你们如何将 <img src... 行添加到您的页面代码中?你是 document.write() 还是使用 document.body.innerHTML+= 方法?两者对我来说都失败了:( 在onload
属性中编写大量代码不太实用。这也需要一个额外的文件存在并被加载。 momo's solution 不是妥协。
两个缺点:1.它不能调用通过innerHTML(连同这个IMG标签)添加的任何脚本/函数,因为它们在浏览器中不存在2.如果部分内联代码在 ".removeChild()" 抛出异常之前,不会删除 img 元素。
您可以 Base64 将您的触发图像编码为 <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
(这不会执行网络请求)实际上...您不需要图像,引用不存在的图像而不是 @ 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
。
没有必要,因为您正在从另一个脚本编写脚本。 <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>
谁说它来自另一个脚本?您可以在没有 <script>
元素的情况下运行 JavaScript。例如。 <img onerror="..." src="#">
和 <body onload="...">
。如果您想成为技术人员,由于不明确的命名空间,这在非 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
,并且适用于scripts、linked 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
注入脚本,但可以使用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)
);
我已经从我的实际实现中简化了这一点,所以没有承诺其中没有任何错误。但原理是有效的。
如果您不关心在脚本运行后取回任何值,那就更容易了;只需省略 Promise
和 onload
位。您甚至不需要包装脚本或创建全局 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】:你也可以像这样包装你的<script>
,它会被执行:
<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 来检测<script></script>
节点,然后将其替换为具有相同src 的新<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);
【讨论】:
感谢您在不说明原因的情况下对我投反对票。爱你们。 解释:这个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 年的答案。我现在会这样做:<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>" onload="console.log(21,this)"/>
【参考方案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 并用 </div>
关闭它
不要更改要执行的内容(以前在 script 标签中,现在在 div 标签中)
在页面中添加样式...
<style type="text/css"> .javascript display: none; </style>
现在使用 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将脚本标签插入到html元素中[重复]