用javascript替换DOM并运行新脚本 [英] Replace DOM with javascript and run new scripts

查看:35
本文介绍了用javascript替换DOM并运行新脚本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在页面加载时替换整个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并运行新脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆