问题描述
我正在尝试在页面加载时替换整个DOM,以对用户创建的淘汰页面进行无js后退.
I am trying to replace the whole DOM on page load to do a no-js fallback for a user created knockout page.
我用它代替了DOM,但是当我执行此操作时,新文档中包含的脚本没有运行.我想知道是否有强迫他们运行的方法.
I have it replacing the DOM, but when I do the scripts included in the new document aren't running. I was wondering if theres any way of forcing them to run.
<!DOCTYPE html>
<html>
<head>
<title>Title1</title>
</head>
<body>
Hello world <!-- No JS enabled content -->
</body>
<script type="text/javascript">
var model = { 'template' : '\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eTitle2\u003c/title\u003e\u003cscript type=\"text/javascript\"\u003ealert(\"test\");\u003c/script\u003e\u003c/head\u003e\u003cbody\u003eHello world2\u003c/body\u003e\u003c/html\u003e' };
document.documentElement.innerHTML = model.template;
</script>
</html>
模板包含以下已编码
<html>
<head>
<title>aaa</title>
<script type='text/javascript'>alert('hello world');</script>
</head>
<body>
Hello world <!-- JS enabled content -->
</body>
</html>
如何获取警报?
推荐答案
您发现,分配给 innerHTML
的文本中 script
标记中的代码不执行.有趣的是,尽管如此,在我尝试过的每种浏览器上,都创建了 元素并将其放置在DOM中.
As you've discovered, the code in the script
tags in the text you assign to innerHTML
is not executed. Interestingly, though, on every browser I've tried, the script
elements are created and placed in the DOM.
这意味着很容易编写一个函数来按顺序运行它们,而无需使用 eval
及其对作用域的怪异作用:
This means it's easy to write a function to run them, in order, and without using eval
and its weird effect on scope:
function runScripts(element) {
var scripts;
// Get the scripts
scripts = element.getElementsByTagName("script");
// Run them in sequence (remember NodeLists are live)
continueLoading();
function continueLoading() {
var script, newscript;
// While we have a script to load...
while (scripts.length) {
// Get it and remove it from the DOM
script = scripts[0];
script.parentNode.removeChild(script);
// Create a replacement for it
newscript = document.createElement('script');
// External?
if (script.src) {
// Yes, we'll have to wait until it's loaded before continuing
newscript.onerror = continueLoadingOnError;
newscript.onload = continueLoadingOnLoad;
newscript.onreadystatechange = continueLoadingOnReady;
newscript.src = script.src;
}
else {
// No, we can do it right away
newscript.text = script.text;
}
// Start the script
document.documentElement.appendChild(newscript);
// If it's external, wait for callback
if (script.src) {
return;
}
}
// All scripts loaded
newscript = undefined;
// Callback on most browsers when a script is loaded
function continueLoadingOnLoad() {
// Defend against duplicate calls
if (this === newscript) {
continueLoading();
}
}
// Callback on most browsers when a script fails to load
function continueLoadingOnError() {
// Defend against duplicate calls
if (this === newscript) {
continueLoading();
}
}
// Callback on IE when a script's loading status changes
function continueLoadingOnReady() {
// Defend against duplicate calls and check whether the
// script is complete (complete = loaded or error)
if (this === newscript && this.readyState === "complete") {
continueLoading();
}
}
}
}
自然,脚本不能使用 document.write
.
请注意我们如何创建 new script
元素.只是将现有文档移到文档的其他位置是行不通的,浏览器已将其标记为已运行(即使不是).
Note how we have to create a new script
element. Just moving the existing one elsewhere in the document doesn't work, it's been marked by the browser as having been run (even though it wasn't).
以上内容适用于大多数在文档正文中某个元素上使用 innerHTML
的人,但是它对您不起作用,因为您实际上是在 document.documentElement .这意味着我们从这一行中得到的 NodeList
:
The above will work for most people using innerHTML
on an element somewhere in the body of the document, but it won't work for you, because you're actually doing this on the document.documentElement
. That means the NodeList
we get back from this line:
// Get the scripts
scripts = element.getElementsByTagName("script");
当我们向 document.documentElement
中添加更多脚本时,
...将继续扩展.因此,在您的特定情况下,您必须先将其转换为数组:
...will keep expanding as we add further scripts to the document.documentElement
. So in your particular case, you have to turn it into an array first:
var list, scripts, index;
// Get the scripts
list = element.getElementsByTagName("script");
scripts = [];
for (index = 0; index < list.length; ++index) {
scripts[index] = list[index];
}
list = undefined;
...以及稍后在 continueLoading
中,您必须手动从数组中删除条目:
...and later in continueLoading
, you have to manually remove entries from the array:
// Get it and remove it from the DOM
script = scripts[0];
script.parentNode.removeChild(script);
scripts.splice(0, 1); // <== The new line
对于大多数人(不是您)来说,这是一个完整的示例,包括执行诸如函数声明之类的脚本(如果使用 eval
会弄乱):实时复制 |实时源
Here's a complete example for most people (not you), including the scripts doing things like function declarations (which would be messed up if we used eval
): Live Copy | Live Source
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Run Scripts</title>
</head>
<body>
<div id="target">Click me</div>
<script>
document.getElementById("target").onclick = function() {
display("Updating div");
this.innerHTML =
"Updated with script" +
"<div id='sub'>sub-div</div>" +
"<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js'></scr" + "ipt>" +
"<script>" +
"display('Script one run');" +
"function foo(msg) {" +
" display(msg); " +
"}" +
"</scr" + "ipt>" +
"<script>" +
"display('Script two run');" +
"foo('Function declared in script one successfully called from script two');" +
"$('#sub').html('updated via jquery');" +
"</scr" + "ipt>";
runScripts(this);
};
function runScripts(element) {
var scripts;
// Get the scripts
scripts = element.getElementsByTagName("script");
// Run them in sequence (remember NodeLists are live)
continueLoading();
function continueLoading() {
var script, newscript;
// While we have a script to load...
while (scripts.length) {
// Get it and remove it from the DOM
script = scripts[0];
script.parentNode.removeChild(script);
// Create a replacement for it
newscript = document.createElement('script');
// External?
if (script.src) {
// Yes, we'll have to wait until it's loaded before continuing
display("Loading " + script.src + "...");
newscript.onerror = continueLoadingOnError;
newscript.onload = continueLoadingOnLoad;
newscript.onreadystatechange = continueLoadingOnReady;
newscript.src = script.src;
}
else {
// No, we can do it right away
display("Loading inline script...");
newscript.text = script.text;
}
// Start the script
document.documentElement.appendChild(newscript);
// If it's external, wait for callback
if (script.src) {
return;
}
}
// All scripts loaded
newscript = undefined;
// Callback on most browsers when a script is loaded
function continueLoadingOnLoad() {
// Defend against duplicate calls
if (this === newscript) {
display("Load complete, next script");
continueLoading();
}
}
// Callback on most browsers when a script fails to load
function continueLoadingOnError() {
// Defend against duplicate calls
if (this === newscript) {
display("Load error, next script");
continueLoading();
}
}
// Callback on IE when a script's loading status changes
function continueLoadingOnReady() {
// Defend against duplicate calls and check whether the
// script is complete (complete = loaded or error)
if (this === newscript && this.readyState === "complete") {
display("Load ready state is complete, next script");
continueLoading();
}
}
}
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}
</script>
</body>
</html>
这是您的小提琴已更新为使用上述小提琴,我们在其中打开了 NodeList 放入数组:
And here's your fiddle updated to use the above where we turn the
NodeList
into an array:
HTML:
<body>
Hello world22
</body>
脚本:
var model = {
'template': '\t\u003chtml\u003e\r\n\t\t\u003chead\u003e\r\n\t\t\t\u003ctitle\u003eaaa\u003c/title\u003e\r\n\t\t\t\u003cscript src=\"http://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.1/jquery.min.js\"\u003e\u003c/script\u003e\r\n\t\t\t\u003cscript type=\u0027text/javascript\u0027\u003ealert($(\u0027body\u0027).html());\u003c/script\u003e\r\n\t\t\u003c/head\u003e\r\n\t\t\u003cbody\u003e\r\n\t\t\tHello world\r\n\t\t\u003c/body\u003e\r\n\t\u003c/html\u003e'
};
document.documentElement.innerHTML = model.template;
function runScripts(element) {
var list, scripts, index;
// Get the scripts
list = element.getElementsByTagName("script");
scripts = [];
for (index = 0; index < list.length; ++index) {
scripts[index] = list[index];
}
list = undefined;
// Run them in sequence
continueLoading();
function continueLoading() {
var script, newscript;
// While we have a script to load...
while (scripts.length) {
// Get it and remove it from the DOM
script = scripts[0];
script.parentNode.removeChild(script);
scripts.splice(0, 1);
// Create a replacement for it
newscript = document.createElement('script');
// External?
if (script.src) {
// Yes, we'll have to wait until it's loaded before continuing
newscript.onerror = continueLoadingOnError;
newscript.onload = continueLoadingOnLoad;
newscript.onreadystatechange = continueLoadingOnReady;
newscript.src = script.src;
} else {
// No, we can do it right away
newscript.text = script.text;
}
// Start the script
document.documentElement.appendChild(newscript);
// If it's external, wait
if (script.src) {
return;
}
}
// All scripts loaded
newscript = undefined;
// Callback on most browsers when a script is loaded
function continueLoadingOnLoad() {
// Defend against duplicate calls
if (this === newscript) {
continueLoading();
}
}
// Callback on most browsers when a script fails to load
function continueLoadingOnError() {
// Defend against duplicate calls
if (this === newscript) {
continueLoading();
}
}
// Callback on IE when a script's loading status changes
function continueLoadingOnReady() {
// Defend against duplicate calls and check whether the
// script is complete (complete = loaded or error)
if (this === newscript && this.readyState === "complete") {
continueLoading();
}
}
}
}
runScripts(document.documentElement);
今天,当您阅读问题时,我才想到这种方法.我从未见过使用过它,但是它可以在IE6,IE8,Chrome 26,Firefox 20和Opera 12.15中使用.
This approach just occurred to me today when reading your question. I've never seen it used before, but it works in IE6, IE8, Chrome 26, Firefox 20, and Opera 12.15.
这篇关于用javascript替换DOM并运行新脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!