我尝试在<div>上使用innerHTML加载一些脚本到页面中。脚本似乎加载到DOM中,但它从未执行(至少在Firefox和Chrome中)。有一种方法让脚本执行时插入他们与innerHTML?
示例代码:
<!DOCTYPE html >
< html >
<身体onload = " . getelementbyid(机)。innerHTML = '<script>alert(\'hi\')<\/script>'">
难道不应该出现“hi”的提醒吗?
< div id = "装载机" > < / div >
身体< / >
< / html >
是的,你可以,但你必须在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;
}
从innerHTML执行(Java Script)标签
将脚本元素替换为具有class="javascript"类属性的div,并以</div>关闭
不要改变你想要执行的内容(以前是在script标签,现在是在div标签)
在你的页面中添加一个样式…
<style type="text/css"> .javascript {display: none;} > < /风格
现在使用jquery运行eval (jquery js应该已经包含在内)
$('.javascript').each(function() {
eval($(this).text());
});`
你可以在我的博客上探索更多。
下面是一个递归地将所有脚本替换为可执行脚本的方法:
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]);
对于任何仍然试图这样做的人来说,不,您不能使用innerHTML注入脚本,但可以使用Blob和URL.createObjectURL将字符串加载到脚本标记中。
我已经创建了一个例子,让你运行一个字符串作为脚本,并通过一个承诺获得脚本的“exports”:
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)
});
}
我从我的实际实现中简化了它,所以不能保证它没有任何错误。但是这个原理是可行的。
如果你不关心在脚本运行后得到什么值,那就更简单了;只要省略承诺和加载部分。您甚至不需要包装脚本或创建全局窗口。__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);
}
}
你可以在这里看到演示。
Gabriel Garcia提到的mutationobserver是正确的,但对我来说不太管用。我不确定这是因为浏览器的怪癖还是因为我自己的错误,但最终适合我的版本是:
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]);
}
}
这是一个load函数开始的例子:
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
}
这里的解决方案不使用eval,与脚本、链接脚本以及模块一起工作。
该函数接受3个参数:
要插入的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;
}
我有这个问题与innerHTML,我不得不追加一个Hotjar脚本的“头部”标签我的Reactjs应用程序,它将必须立即执行追加后。
将动态节点导入“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);
根据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添加),那么脚本将在image onload事件中执行。如果脚本已加载(由后端添加),则定义script01变量,onload将不会第二次运行脚本。