提问人:Craig 提问时间:7/29/2009 最后编辑:Mosh FeuCraig 更新时间:10/31/2023 访问量:284091
脚本可以用innerHTML插入吗?
Can scripts be inserted with innerHTML?
问:
我尝试使用.脚本似乎加载到 DOM 中,但它从未执行过(至少在 Firefox 和 Chrome 中)。有没有办法让脚本在插入时执行?innerHTML
<div>
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>
答:
您必须使用 eval() 来执行作为 DOM 文本插入的任何脚本代码。
MooTools会自动为您执行此操作,我相信jQuery也会(取决于版本。 jQuery版本1.6+使用)。这节省了很多解析标签和转义内容的麻烦,以及一堆其他“陷阱”。eval
<script>
通常,如果您自己要这样做,则希望在没有任何 HTML 标记(例如 )的情况下创建/发送脚本代码,因为这些标记不会正确。eval()
<script>
eval()
评论
<script>
innerHTML
eval()
eval()
是的,你可以,但你必须在 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;
}
评论
这是一个非常有趣的问题解决方案:http://24ways.org/2005/have-your-dom-and-script-it-too
因此,请使用它而不是脚本标签:
<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />
评论
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
onload
onerror
您可以创建脚本,然后注入内容。
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>
<img onerror="..." src="#">
<body onload="...">
从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());
});`
你可以在这里探索更多,在我的博客上。
我使用了这段代码,它工作正常
var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
eval(arr[n].innerHTML)//run script inside div
评论
下面是一个以递归方式将所有脚本替换为可执行脚本的方法:
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]);
评论
Krasimir Tsonev 有一个很好的解决方案,可以克服所有问题。 他的方法不需要使用 eval,因此不存在性能或安全问题。 它允许您使用 js 设置包含 html 的 innerHTML 字符串,并立即将其转换为 DOM 元素,同时执行 js 部分存在于代码中。简短,简单,完全按照您的意愿工作。
享受他的解决方案:
http://krasimirtsonev.com/blog/article/Convert-HTML-string-to-DOM-element
重要提示:
- 您需要用 div 标签包装目标元素
- 您需要用 div 标签包装 src 字符串。
- 如果您直接编写 src 字符串并且它包含 js 部分,请注意正确编写结束脚本标签(在 / 之前使用 \),因为这是一个字符串。
使用 代替 。$(parent).html(code)
parent.innerHTML = code
下面还修复了使用和通过属性加载的脚本。不幸的是,即使这样也不适用于 Google AdSense 脚本。document.write
src
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
})
}
评论
对于仍在尝试执行此操作的任何人,不,您不能使用 注入脚本,但可以使用 和 将字符串加载到脚本标记中。innerHTML
Blob
URL.createObjectURL
我创建了一个示例,允许您将字符串作为脚本运行,并通过 promise 获取脚本的“导出”:
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)
});
}
我已经从我的实际实现中简化了这一点,所以不能保证其中没有任何错误。但这个原则是有效的。
如果您不在乎在脚本运行后取回任何值,那就更容易了;只是省略了 and 位。您甚至不需要包装脚本或创建全局属性。Promise
onload
window.__load_script_exports_
评论
下面是一个递归函数,用于设置我在广告服务器中使用的元素的 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);
}
}
您可以在此处查看演示。
我对这个问题的解决方案是设置一个 Mutation Observer 来检测节点,然后将其替换为具有相同 src 的新节点。例如:<script></script>
<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});
评论
document.createElement
尝试使用 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>
评论
你可以这样做:
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);
}
加布里埃尔·加西亚(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
这里有一个不使用 的解决方案,它适用于脚本、链接脚本以及模块。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;
}
评论
eval('console.log(this)')
eval('let b=100')
b
你也可以像这样包装你的,它将被执行:<script>
<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';
请注意:里面的作用域指的是 iframe,因此您必须使用上面示例中的示范器来访问父文档。srcdoc
top
我在使用 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 的 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);
评论
newScript
newScript.text = "alert('Hello World!')";
对我来说,最好的方法是通过innerHtml插入新的HTML内容,然后使用
setTimeout(() => {
var script_el = document.createElement("script")
script_el.src = 'script-to-add.js'
document.body.appendChild(script_el)
}, 500)
setTimeout 不是必需的,但它效果更好。这对我有用。
每次我想动态插入脚本标签时,我都会这样做:
const html =
`<script>
alert('👋 there ! Wanna grab a 🍺');
</script>`;
const scriptEl = document.createRange().createContextualFragment(html);
parent.append(scriptEl);
注意:这使用 ES6。
我见过很多答案使用,它的工作原理与 .appendChild
append
评论
appendChild
append
append
appendChild
<p>
基于 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 content atribute会执行。如果脚本未执行(添加为 innerHTML),则脚本将在 image onload 事件中执行。如果脚本已加载(由后端添加),则定义变量,并且 onload 不会第二次运行该脚本。script01
评论
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>" onload="console.log(21,this)"/>
过滤脚本标签并使用 eval 运行每个标签
var tmp= document.createElement('div');
tmp.innerHTML = '<script>alert("hello")></script>';
[...tmp.children].filter(x => x.nodeName === 'SCRIPT').forEach(x => eval(x.innerText));
评论
[...document.querySelector('#content').children].filter(x => x.nodeName === 'SCRIPT').forEach(x => eval(x.innerText));
这是 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);
});
}
注意:我还尝试过使用 cloneNode() 或 outerHTML 的替代解决方案,但没有奏效。
评论
我自己的风格,使用现代JS和打字稿。不知道为什么人们在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)
})
}
简单,没有评估,没有功能:
fetch('/somepage')
.then(x=>x.text())
.then(x=>{
divDestination.innerHTML=x;
divDestination.querySelectorAll("script")
.forEach(x=>{
var sc=document.createElement("script");
sc.appendChild(document.createTextNode(x.innerText));
divDestination.appendChild(sc)
})
})
单线解决方案如下:
document.getElementsByTagName("head")[0].append(document.createRange().createContextualFragment('<script src="https://google.com/file.js"></script>'));
评论