1

我正在尝试在页面加载时替换整个 DOM,以便为用户创建的淘汰页面执行 no-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>

如何让警报运行?

4

1 回答 1

5

正如您所发现的,script您分配给文本的标签中的代码innerHTML不会被执行。不过有趣的是,在我尝试过的每个浏览器上,都会创建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.

注意我们必须如何创建一个 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 | 直播源

<!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转换为数组的地方:

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。

于 2013-06-29T14:29:53.390 回答