页面加载完成后执行JavaScript

8

我有一个包含预渲染内容和未渲染内容的html页面。我希望能立即显示预渲染内容,然后开始渲染其余的内容。我不使用jQuery

请看以下代码片段。我已经尝试了多种方式,包括在结束body标签之前注入我的脚本,并将我的脚本作为回调提供给window.onloaddocument.body.onloaddocument.addEventListener('DOMContentLoaded')。但无论哪种情况,页面都不会显示预渲染内容直到其余的内容被渲染。

<html><head></head>
  <body>
    <header>What it is, my doge?</header>
    <div id="main"></div>
    <script>
      var main = document.getElementById('main');
      for (var i = 0; i < 500; i++)
        main.innerText += new Date();
    </script>
  </body>
</html>

<html><head></head>
  <body>
    <header>What it is, my doge?</header>
    <div id="main"></div>
    <script>
      var main = document.getElementById('main');
      document.body.onload = function() {
        for (var i = 0; i < 500; i++)
          main.innerText += new Date();
      };
    </script>
  </body>
</html>

<html><head></head>
      <body>
        <header>What it is, my doge?</header>
        <div id="main"></div>
        <script>
          var main = document.getElementById('main');
          window.onload = function() {
            for (var i = 0; i < 500; i++)
              main.innerText += new Date();
          };
        </script>
      </body>
    </html>

<html><head></head>
      <body>
        <header>What it is, my doge?</header>
        <div id="main"></div>
        <script>
          var main = document.getElementById('main');
          document.addEventListener('DOMContentLoaded', function() {
            for (var i = 0; i < 500; i++)
              main.innerText += new Date();
          });
        </script>
      </body>
    </html>

一个有效的方法是使用超时时间为0的window.setTimeout函数。然而,这只是将函数推迟到没有其他事情可做的时候。在这里,这是最佳实践吗?

<html><head></head>
<body>
    <header>What it is, my doge?</header>
    <div id="main"></div>
    <script>
      var main = document.getElementById('main');
      window.setTimeout(function() {
        for (var i = 0; i < 500; i++)
          main.innerText += new Date();
      }, 0);
    </script>
</body>
</html>


1
  1. 我们需要在这里编写代码。
  2. 你的描述与代码片段不符。
  3. window.onload 将在所有内容加载完成后触发。你只需要将调用放在 </body> 标签之前即可。
- mplungjan
这个小提琴正是你所描述的例子,显然它并不起作用。 - Matthew James Davis
任意地。可能通过knockout,或通过DOM API附加文档片段。 - Matthew James Davis
2
注意:setTimeout(function(){}, 0)方法似乎在FireFox中无法正常工作。它需要一个大约50-100毫秒的超时时间。 - JstnPwll
根据Mozilla的说法,setTimeout(function(){},0) 设置的最小延迟时间(根据HTML5规范定义)是4毫秒。当设置的值小于4毫秒时,它会被自动设置为4毫秒。 - Arthur Weborg
显示剩余3条评论
4个回答

13

在最佳实践方面,没有一个标准。在好的、常见的和可接受的实践方面,有一些方法。你已经掌握了其中之一:

setTimeout(function() { }, 1);

在这种情况下,该函数将在所有其他内联处理结束后 浏览器的最小超时期 内执行。
同样,如果您想确保您的函数在 某个条件为真 后不久运行,请使用间隔:
var readyCheck = setInterval(function() {
  if (readyCondition) {
    /* do stuff */
    clearInterval(readyCheck);
  }
}, 1);

我在自己的工作中使用了类似但更加通用的解决方案。我在头部定义一个辅助函数:

var upon = function(test, fn) {
    if (typeof(test) == 'function' && test()) {
        fn();
    } else if (typeof(test) == 'string' && window[test]) {
        fn();
    } else {
        setTimeout(function() { upon(test, fn); }, 50);
    }
}; // upon()

...当依赖项解决时,我会触发其他功能:

upon(function() { return MyNS.Thingy; }, function() {
  //  stuff that depends on MyNS.Thingy
});

upon(function() { return document.readyState == 'complete';}, function() {
  // stuff that depends on a fully rendered document
});

或者,如果您想要一种更具权威性的良好实践,请遵循 Google 的示例。创建一个外部的async脚本,并在第一个头部脚本之前注入它:
var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true;
s.src = '/path/to/script.js';
var header_scripts = document.getElementsByTagName('script')[0];
header_scripts.parentNode.insertBefore(s, header_scripts);

谷歌的解决方案理论上适用于所有浏览器(IE < 10?),可以尽快执行外部脚本而不会干扰文档加载。
如果您想要一种权威的常规做法,请查看jQuery的onready解决方案。

@MatthewJamesDavis 我知道两个原因。只有一个与此相关:一些浏览器不支持async属性。 - svidgen
@MatthewJamesDavis 请查看此处关于async的浏览器支持情况:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script#Browser_compatibility - svidgen
我建议你在回答中包含异步操作,以便未来的搜索者。 - Matthew James Davis
@svidgen:我可以直接在头部区域使用带有async属性的脚本标签而不是脚本注入吗?(如下)<script async src="/path/to/script.js" type="text/javascript"></script> - Soundar
@svidgen 对打扰感到抱歉。在setInterval解决方案中,clearInterval表达式应该在if块中吗?否则你的代码可能不会被调用。 - zlx_star
显示剩余2条评论

4
根据您的浏览器要求,您可以使用“async”标签,并在内容加载后导入脚本。这可能实现了与“setTimeout(func, 0)”相同的效果,但可能更少一些hacky。
请参见http://plnkr.co/edit/7DlNWNHnyX5s6UE8AFiU?p=preview HTML:
...
<body>
  <h1 id="main">Hello Plunker!</h1>
  <script async src="script.js"></script>
</body>
...

script.js:

for(var i=0; i<500; ++i) {
  document.getElementById('main').innerText += new Date();
}

这种方法更加规范。setTimeout方法的意思是“嘿,停下一切,下载这个脚本,然后继续做你原本要做的事情,最后执行脚本”,而async则表示“完成所有任务后,无论何时脚本准备好了就执行它”。 - Matthew James Davis

2

我以前就用过这个方法:

var everythingLoaded = setInterval(function() {
  if (/loaded|complete/.test(document.readyState)) {
    clearInterval(everythingLoaded);
    init(); // this is the function that gets called when everything is loaded
  }
}, 10);

0
我认为你想要做的是在标签上使用onload事件。这样,在处理javascript之前,首先会出现“它是什么,我的doge?”消息。
我还在循环内设置了一个超时,这样你可以更好地看到添加的行。
<html>
    <head>
        <script>
            myFunction = function() {
                for (var i = 1000; i > 0; i--) {
                    setTimeout(function() {
                        main.innerText += new Date();
                    }, 100);
                }
            };
        </script>
    </head>

    <body onload="myFunction()">
        <header>What it is, my doge?</header>
        <div id="main"></div>
    </body>
</html>

感谢您的回答。然而,这并不像我发布的想法那样优化,即在回调函数之外设置一个超时为0的window.setTimeout调用,这样会更快。 - Matthew James Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接