7

我在HEAD标签中有一些 JavaScript,它在页面上的最后一个脚本(当前已解析)之前动态插入异步加载脚本标签。这个动态包含的脚本标签包含需要在 DOM 可用之后,但在加载所有图像和脚本标签之前需要解析 DOM 的 JavaScript。重要的是 JavaScript 在加载所有 JS 之前开始执行,因为如果有一个挂起的脚本,这会导致糟糕的用户体验。这意味着我不能等待DOMContentLoaded事件触发。对于将动态包含脚本标记的 JavaScript 的第一个位放置在何处,我没有任何灵活性。

我的问题是,我是否可以立即开始解析 DOM,而无需等待DOMContentLoaded事件?如果没有,有没有办法让我在不等待DOMContentLoaded事件的情况下做到这一点?

4

4 回答 4

1

... HEAD 中的 JavaScript ... 在页面上的最后一个脚本之前动态插入一个异步加载脚本标签...

我假设加载程序脚本是 inline,这意味着突出显示的位实际上是指“当前”脚本元素,即加载程序。发生这种情况是因为只有加载器脚本标记之前的 html 已被解析和解释,因此插入的脚本标记实际上仍然在head页面底部而不是在页面底部。因此,目标脚本仅限于对前面的元素执行 DOM 操作,除非您将代码包装到 DOM 就绪回调中……这是您首先要避免的!

基本上你想加载所有 html 以便页面可见/可扫描,开始加载图像/样式表(发生在非阻塞线程中),然后加载任何 javascript。一种方法是将您的目标脚本放在页面底部,只需正确选择它们的顺序(首先是交互性,其次是增强功能,最后是第三方分析/社交媒体集成/其他任何超重的东西)并根据您的需要进行调整。从技术上讲,它仍然会阻止页面加载,但无论如何,页面底部只剩下脚本(并且由于它们位于底部,因此您可以在加载 DOM 后立即直接操作 DOM,减去一些 IE7 怪癖)。

我喜欢链接到一个相关的咆哮/概述,它提供了一些关于使用和滥用 DOM 就绪回调的体面示例和一些时间琐事,以及关于为什么出色的性能可能低于一个健全的依赖管理框架。后者的主题过于广泛,无法在一个答案中用尽,但是像requirejs 文档这样的东西应该可以让您大致了解该模式的工作原理。

也许要考虑的另一种模式是构建一个 SPA - 单页面应用程序,它利用对内容块的异步访问,而不是在完整页面之间进行“传统”导航。该模式具有被低估但相当显着的性能优势,因为不必在每个页面上解析和重新执行共享的 javascript,这也将解决您对第三方 js 性能的(有效)担忧。毕竟,一个好的缓存策略会在加载时间上创造奇迹,但糟糕的 javascript 代码或大量框架的执行开销仍然存在。

更新:想通了。考虑到您的特定场景(即无法控制标记本身,并且希望成为最后一个执行的脚本),您应该将异步脚本元素插入 DOM包装到 0mssetTimeout回调中:

setTimeout(function(){

    //the rest is how GA operates
    var targetScript = document.createElement('script');
    targetScript.type = 'text/javascript';
    targetScript.async = true;
    targetScript.src = 'target.js';

    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(targetScript, s);

}, 0);

由于环境的单线程特性,jssetTimeout回调基本上是在线程不再忙的时候就加入队列进行0ms延迟执行(这里有更详尽的解释)。所以浏览器甚至不知道需要加载,更不用说执行目标脚本,直到所有“更高优先级”代码完成!而且由于在添加脚本标记时 DOM 是可操作的,因此您不必在目标脚本本身中显式检查它(这对于从缓存“立即”加载它时很方便)。

于 2014-01-13T04:12:57.180 回答
1

以下技术的行为使解析 DOM 变得安全......

  1. 使用窗口加载或 DomContentLoaded 事件
  2. 在页面底部声明或注入您的脚本
  3. 在您的脚本标签上放置“异步”属性
  4. 或这样做:
<script>
    setTimeout(function(){
     // script declared inside here will execute after the DOM is parsed
    },0);
</script>

此外,这些不会阻止DOM 中的页面加载。

在您依赖的任何 DOM 下方声明脚本时,无需调用 DomContentLoaded 事件,除非您需要进行大小计算或定位,因为如果未指定宽度/高度,图像/视频将改变事物的大小。

以下是一些可行的方案。

DEPENDENT DOM IS ABOVE
<script src="jquery.js"></script>
<script>
    $('mydom').slideDown('fast');
</script>

或试试这个:

<script>
    // won't fart
    setTimeout(function(){ console.log(document.getElementById('mydom').innerHTML); },0);
</script>
DEPENDENT DOM IS BELOW or ABOVE (dont' matter)

这是我的小测试,让您看到 setTimeout 是我直到最近才注意到的那些奇怪的事情之一,所以很高兴看到它的工作示例。

http://jsfiddle.net/FFLL2/

于 2014-01-12T22:47:15.003 回答
0

是的,您应该等待用户代理告诉您 DOM 已加载。但是,这样做的方法不止一种。

onreadystatechange to interactiveDOMContentLoaded之间存在差异。

根据http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html ,用户代理在停止解析文档后所做的第一件事是将 文档准备就绪设置为“交互式”。

用户代理不等待脚本加载。

当文档就绪状态发生变化时,用户代理将在 Document 对象上触发 readystatechange。

因此,如果您担心的脚本是非内联的,您可能会使用 readystatechange。

谈论跨浏览器:这是规范。

于 2014-01-13T00:04:28.653 回答
0

我强烈建议您完整阅读以下文章,该文章详细探讨了脚本加载和实际 DOM 准备情况的奥秘,以及什么时候可以安全地做什么,同时考虑到浏览器的差异。

http://www.html5rocks.com/en/tutorials/speed/script-loading/

于 2014-01-13T05:05:03.157 回答