【问题标题】:Replace DOM with javascript and run new scripts用 javascript 替换 DOM 并运行新脚本
【发布时间】:2013-06-27 04:10:02
【问题描述】:

我正在尝试在页面加载时替换整个 DOM,以便为用户创建的淘汰页面执行无 js 后备。

我用它替换了 DOM,但是当我这样做时,新文档中包含的脚本没有运行。我想知道是否有任何方法可以强制他们运行。

<!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>

如何让警报运行?

【问题讨论】:

  • 如果它是无 JS 后备,我假设您在没有启用 JavaScript 的情况下测试它,所以脚本不运行只是合乎逻辑的......还是我在这里遗漏了什么?
  • 这里有一个类似的问题:stackoverflow.com/q/1197575/575527
  • @BenjaminGruenbaum 这是在启用 JS 的情况下运行,dom 被正确替换,警报脚本不运行,另一个运行。
  • @LukeMcGregor 啊,所以你正在做相反的事情。在这种情况下,您不能将这样的 JavaScript 作为文本插入。请参阅 Joseph 链接到的问题。
  • @LukeMcGregor: “如果没有启用脚本,则应显示顶部内容,如果有则显示底部内容。” 为什么不使用noscript

标签: javascript html


【解决方案1】:

正如您所发现的,您分配给innerHTML 的文本中script 标记中的代码不会被执行。不过有趣的是,在我尝试过的每个浏览器上,script 元素被创建并放置在 DOM 中。

这意味着很容易编写一个函数来按顺序运行它们,并且不使用eval 及其对作用域的奇怪影响:

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 元素。仅将现有的移动到文档中的其他位置是行不通的,它已被浏览器标记为已运行(即使它没有运行)。

以上内容适用于大多数在文档正文某处的元素上使用innerHTML 的人,但它不适用于您,因为您实际上是在document.documentElement 上执行此操作。这意味着我们从这一行返回的NodeList

// Get the scripts
scripts = element.getElementsByTagName("script");

...随着我们向document.documentElement 添加更多脚本,将继续扩展。所以在你的特殊情况下,你必须先把它变成一个数组:

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 中,您必须手动从数组中删除条目:

// Get it and remove it from the DOM
script = scripts[0];
script.parentNode.removeChild(script);
scripts.splice(0, 1); // <== The new line

这是一个适用于大多数人(不是你)的完整示例,包括执行函数声明之类的脚本(如果我们使用 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>

这里是your fiddle updated to use the above,我们将NodeList 转换为数组:

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。

【讨论】:

  • 嘿,这看起来非常好,并且使我上面的示例完美运行,所以我将标记为答案。但是,如果我有一个外部脚本(例如 jquery 引用),它在使用此方法进行排序时确实存在一些问题,它会在脚本执行之后加载。有没有办法避免这种情况?这是一个 JSFiddle 来演示我的意思 jsfiddle.net/6YwnK ,看看错误控制台
  • @LukeMcGregor:问题不在于订单关闭,而在于该功能完全不适合外部脚本。我不知道我是怎么回事。我已经更新,并且还处理了一个您使用的独特问题,不会影响到大多数其他人。
  • 是的,我在第一次尝试时添加了 src 和类型位,它解决了加载问题,我错过了继续加载位以强制顺序加载。生病将其添加到我的代码中,谢谢:)
猜你喜欢
  • 2022-11-11
  • 1970-01-01
  • 2010-12-02
  • 1970-01-01
  • 2014-03-05
  • 2021-01-08
  • 2018-07-19
  • 2021-01-10
  • 1970-01-01
相关资源
最近更新 更多