[英]Can scripts be inserted with innerHTML?
我嘗試在<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>
這是一種將所有腳本遞歸替換為可執行腳本的方法:
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]);
您必須使用eval()來執行您作為 DOM 文本插入的任何腳本代碼。
MooTools 會自動為您執行此操作,我相信 jQuery 也會這樣做(取決於版本。jQuery 版本 1.6+ 使用eval
)。 這省去了很多解析<script>
標簽和轉義你的內容的麻煩,以及一堆其他的“陷阱”。
通常,如果您要自己eval()
它,您希望創建/發送沒有任何 HTML 標記(例如<script>
)的腳本代碼,因為這些不會正確eval()
。
這是您的問題的一個非常有趣的解決方案:http: //24ways.org/2005/have-your-dom-and-script-it-too
所以使用它而不是腳本標簽:
<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />
您可以創建腳本,然后注入內容。
var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);
這適用於所有瀏覽器:)
我使用了這段代碼,它工作正常
var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
eval(arr[n].innerHTML)//run script inside div
每次我想動態插入腳本標簽時我都會這樣做!
const html =
`<script>
alert('👋 there ! Wanna grab a 🍺');
</script>`;
const scriptEl = document.createRange().createContextualFragment(html);
parent.append(scriptEl);
注意:使用 ES6
編輯 1:為你們澄清 - 我已經看到很多答案使用appendChild
並想讓你們知道它與append
完全一樣
我在使用 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);
你可以這樣做:
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);
}
這是一個不使用eval
的解決方案,它適用於腳本、鏈接腳本以及模塊。
該函數接受 3 個參數:
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;
}
對於仍在嘗試執行此操作的任何人,不,您不能使用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)
});
}
我已經從我的實際實現中簡化了這一點,所以沒有承諾其中沒有任何錯誤。 但原理是有效的。
如果您不關心在腳本運行后取回任何值,那就更容易了; 只需省略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);
}
}
你可以在這里看到演示。
是的,您可以,但是您必須在 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;
}
過濾您的腳本標簽並使用 eval 運行它們中的每一個
var tmp= document.createElement('div');
tmp.innerHTML = '<script>alert("hello")></script>';
[...tmp.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的替代解決方案,但沒有奏效。
Krasimir Tsonev 有一個很好的解決方案,可以克服所有問題。 他的方法不需要使用eval,所以不存在性能和安全問題。 它允許您設置 innerHTML 字符串包含帶有 js 的 html 並立即將其轉換為 DOM 元素,同時還可以執行代碼中存在的 js 部分。 簡短,簡單,並且完全按照您的意願工作。
享受他的解決方案:
http://krasimrtsonev.com/blog/article/Convert-HTML-string-to-DOM-element
重要筆記:
使用$(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
})
}
嘗試使用模板和 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>
您也可以像這樣包裝您的<script>
,它將被執行:
<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';
請注意: srcdoc
中的范圍是指 iframe,因此您必須像上面的示例一樣使用top
來訪問父文檔。
我對這個問題的解決方案是設置一個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});
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
}
基於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 屬性是。 如果腳本未執行(添加為 innerHTML),則在圖像 onload 事件中執行腳本。 如果腳本已加載(由后端添加),則定義了script01
變量,並且 onload 將不會第二次運行該腳本。
單行解決方案如下:
document.getElementsByTagName("head")[0].append(document.createRange().createContextualFragment('<script src="https://google.com/file.js"></script>'));
從 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());
});`
你可以在這里探索更多,在我的博客。
對我來說最好的方法是通過 innerHtml 插入新的 HTML 內容,然后使用
setTimeout(() => {
var script_el = document.createElement("script")
script_el.src = 'script-to-add.js'
document.body.appendChild(script_el)
}, 500)
setTimeout 不是必需的,但效果更好。 這對我有用。
我自己的轉折,使用現代 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)
})
}
簡單,沒有評估,沒有功能:
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)
})
})
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.