image.png

  1. domLoading 表示开始解析第一批收到的 HTML 文档的字节
  2. domInteractive 表示完成全部 HTML 的解析并且 DOM 构建完毕
  3. domContentLoaded 表示 DOM 与 CSSOM 皆已准备就绪
  4. domComplete 表示所有的处理都已完成并且所有的附属资源都已经下载完毕
  5. loadEvent 作为网页加载的最后一步以便触发附加的应用逻辑

HTML页面的生命周期有以下三个重要事件:

  • DOMContentLoaded —— 浏览器已经完全加载了 HTML,DOM 树已经构建完毕,但是像是 <img> 和样式表等外部资源可能并没有下载完毕。
  • load —— 浏览器已经加载了所有的资源(图像,样式表等)。
  • beforeunload/unload —— 当用户离开页面的时候触发。

每个事件都有特定的用途

  • DOMContentLoaded —— DOM 加载完毕,所以 JS 可以访问所有 DOM 节点,初始化界面。
  • load —— 附加资源已经加载完毕,可以在此事件触发时获得图像的大小(如果没有被在 HTML/CSS 中指定)
  • beforeunload/unload —— 用户正在离开页面:可以询问用户是否保存了更改以及是否确定要离开页面。

DOMContentLoaded

  1. document.addEventListener("DOMContentLoaded", ready);

img的载入成功与否不会影响

  1. <script>
  2. function ready() {
  3. alert('DOM is ready');
  4. // image is not yet loaded (unless was cached), so the size is 0x0
  5. alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  6. }
  7. document.addEventListener("DOMContentLoaded", ready);
  8. </script>
  9. <img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

如果我们在整个页面加载完毕后设置 DOMContentLoaded 会发生什么呢?
啥也没有,DOMContentLoaded 不会被触发。

所以在vue的mounted里设置该事件的监听并不会发生作用;
DOMContentLoaded等价于 vue的mounted事件, DOM 加载完毕,所以 JS 可以访问所有 DOM 节点

DOMContentLoaded与JS

DOMContentLoaded 事件陷阱:
JS默认是会阻塞DOM和渲染树的构建的。HTML解析器在遇到脚本文件时,默认为停下来去获取脚本(不考虑资源预加载优化),然后执行,期间阻塞DOM构建。(UI 渲染线程与 JS 引擎是互斥的,当 JS 引擎执行时 UI 线程会被挂起)
DOMContentLoaded 会等待脚本的加载和执行。
两个例外是带 asyncdefer 的外部脚本

defer:声明为defer的脚本会延迟到DOM构建完成后(DOMInteractive事件),DOMContentLoaded和window.onload事件之前执行(但依然会被浏览器的预加载策略提前下载)
async:声明为异步的脚本会异步地下载和执行,会在window.onload事件之前执行,可能会在DOMContenLoaded事件前执行。

他们有两处不同:

async defer
顺序 带有 async 的脚本是优先执行先加载完的脚本,他们在页面中的顺序并不影响他们执行的顺序。 带有 defer 的脚本按照他们在页面中出现的顺序依次执行。执行顺序即文档顺序
DOMContentLoaded 带有 async 的脚本也许会在页面没有完全下载完之前就加载,这种情况会在脚本很小或本缓存,并且页面很大的情况下发生。
执行时机在load事件派发之前
带有 defer 的脚本会在页面加载和解析完毕后执行,刚好在 DOMContentLoaded 之前执行。
加载 都是并行加载(得益于浏览器的资源预加载)

所以 async 用在那些完全不依赖其他脚本的脚本上。

结论: async 和 defer都会影响DOMContentLoaded事件,使用defer也会阻碍DOMContentLoaded触发,使用async有可能会在DOMContentLoaded之前加载执行从而阻碍DOMContentLoaded事件,取决于html的加载完之前脚本是否请求到位(一般是脚本很小或本缓存,并且页面很大的情况下发生)。


defer 和把脚本放在 </body> 前真是没啥区别,只不过 defer 脚本位 于head中,更早被读到,加载更早,而且不担心会被其他的脚本推迟下载开始的时间。

区别:image.png

扩展:
为啥一般将css文件放在头部,js文件放在body里
===> js会影响页面加载吗,样式表会影响页面加载吗? 解析见这里

资源的优先级:当我们网页上有许多图片,脚本和样式资源时,它们的加载顺序是怎样的呢
===> 样式和脚本的优先级是比图片更高的,因为这两个都具有阻塞性,浏览器让它们尽快下载下来。第一个图片资源会和css一起下载,但浏览器发现后面还有许多图片就会优先去下载脚本文件。

  1. <body>
  2. <img src="/images/img1.png"/>
  3. <img src="/images/img2.png"/>
  4. <img src="/images/img3.png"/>
  5. <img src="/images/img4.png"/>
  6. <script type="text/javascript" src="/js/script1.js"></script>
  7. <script type="text/javascript" src="/js/script2.js"></script>
  8. <script type="text/javascript" src="/js/script3.js"></script>
  9. <p>conten after script1</p>
  10. </body>

image.png

DOMContentLoaded与CSS


外部样式表并不会阻塞 DOM 的解析,所以 DOMContentLoaded 并不会被它们影响。

CSS会阻塞渲染树的构建,不阻塞DOM构建,但是在CSSOM构建完成之前,页面不会开始渲染(一片空白),与js不一样,js虽然会阻塞后续DOM构建,但是前面已经就绪的内容会进行渲染

不过仍然有一个陷阱:CSS虽然不阻塞DOM构建,但是会阻塞后面js的执行,从而间接阻塞完整DOM的构建。即如果在样式后面有一个内联脚本,那么脚本必须等待样式先加载完。由于脚本被css阻塞,而脚本的阻塞会影响dom的构建,从而导致css的解析影响了页面的DOMContentLoaded事件。

(css为啥可以阻塞js? 因为js有可能会去获取 DOM 的样式,所以会等待样式表加载完毕
所以在有外部样式表的时候,JS 会一直被阻塞直到外部样式表下载完毕

**
一般情况下 样式表不会阻塞DOMContentLoaded事件,除非在样式表后引入了js。
但是出于页面的文件资源的最佳放置顺序考虑,一般css放在头部,js放在body前可知,大部分时候css的加载时长是会影响页面的加载。

针对性优化

  • 尽早加载css文件并生成CSSOM.
  • 不使用@import
  • 利用媒体查询,处理特定设备下的CSS,以减少不必要的阻塞.

window.onload

window 对象上的 onload 事件在所有文件包括样式表,图片和其他资源下载完毕后触发。
window.onload 会等待所有图片的加载

  1. <script>
  2. window.onload = function() {
  3. alert('Page loaded');
  4. // image is loaded at this time
  5. alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  6. };
  7. </script>
  8. <img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

window.onunload

用户离开页面的时候,window 对象上的 unload 事件会被触发,我们可以做一些不存在延迟的事情,比如关闭弹出的窗口,可是我们无法阻止用户转移到另一个页面上。

window.onbeforeunload

如果用户即将离开页面或者关闭窗口时,beforeunload 事件将会被触发以进行额外的确认。

readyState

确定页面上是否已经加载完毕

document.readyState属性给了我们加载的信息,有三个可能的值:

  • loading 加载 —— document仍在加载。
  • interactive 互动 —— 文档已经完成加载,文档已被解析,但是诸如图像,样式表和框架之类的子资源仍在加载。
  • complete —— 文档和所有子资源已完成加载。状态表示 load 事件即将被触发。
  1. function work() { /*...*/ }
  2. if (document.readyState == 'loading') {
  3. document.addEventListener('DOMContentLoaded', work);
  4. } else {
  5. work();
  6. }

检查 document.readyState 的状态,如果没有就绪可以选择挂载事件,如果已经就绪了就可以直接立即执行。

每当文档的加载状态改变的时候就有一个 readystatechange 事件被触发,所以我们可以打印所有的状态。

  1. <script>
  2. function log(text) { /* output the time and message */ }
  3. log('initial readyState:' + document.readyState);
  4. document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
  5. document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));
  6. window.onload = () => log('window onload');
  7. </script>
  8. <iframe src="iframe.html" onload="log('iframe onload')"></iframe>
  9. <img src="http://en.js.cx/clipart/train.gif" id="img">
  10. <script>
  11. img.onload = () => log('img onload');
  12. </script>
  1. [1] initial readyState:loading
  2. [2] readyState:interactive
  3. [2] DOMContentLoaded
  4. [3] iframe onload
  5. [4] readyState:complete
  6. [4] img onload
  7. [4] window onload

在所有资源加载完毕后(包括 iframeimg)变成 complete,我们可以看到completeimg.onloadwindow.onload 几乎同时发生,区别就是 window.onload 在所有其他的 load 事件之后执行。

总结

页面事件的生命周期:

  • DOMContentLoaded事件在DOM树构建完毕后被触发,我们可以在这个阶段使用 JS 去访问元素。
    • asyncdefer 的脚本可能还没有执行。
    • 图片及其他资源文件可能还在下载中。
  • load 事件在页面所有资源被加载完毕后触发,通常我们不会用到这个事件,因为我们不需要等那么久。
  • beforeunload 在用户即将离开页面时触发,它返回一个字符串,浏览器会向用户展示并询问这个字符串以确定是否离开。
  • unload 在用户已经离开时触发,我们在这个阶段仅可以做一些没有延迟的操作,由于种种限制,很少被使用。
  • document.readyState表征页面的加载状态,可以在readystatechange中追踪页面的变化状态:
    • loading —— 页面正在加载中。
    • interactive —— 页面解析完毕,时间上和 DOMContentLoaded 同时发生,不过顺序在它之前。
    • complete —— 页面上的资源都已加载完毕,时间上和 window.onload 同时发生,不过顺序在他之前。

扩展:vue的生命周期

横向对比一下vue的生命周期

父组件和子组件生命周期钩子函数执行顺序? data在什么阶段初始化完成? 在什么阶段才能访问操作DOM?

回答:如果想挂载一个loading 应该在哪两个生命周期里写
image.png

beforecreated:可以在这加个loading事件
created() : 可以访问到data的数据,但是this.$el还是不存在的;
在这结束loading,还做一些初始化,实现函数自执行
<———created to beforeMount 会触发 compile 虚拟树 到 $el上,但是$el此时还是为虚拟的节点———->
beforeMount(): data、el(虚拟)
mounted():
data、el都是真实的、 在这发起axios请求,拿回数据,配合路由钩子做一些事情
updated():
当组件内响应式数据发生变更

  1. 初始化组件时,仅执行了beforeCreate/Created/beforeMount/mounted四个钩子函数
  2. 当改变data中定义的变量(响应式变量)时,会执行beforeUpdate/updated钩子函数
  3. 当切换组件(当前组件未缓存)时,会执行beforeDestory/destroyed钩子函数
  4. 初始化和销毁时的生命钩子函数均只会执行一次,beforeUpdate/updated可多次执行

扩展:async与defer

image.png
常规script标签:浏览器解析 HTML 文档,解析到没有 defer 或 async 属性的 script 标签时,会阻塞 HTML 文档解析,并按照它们出现的顺序执行;
==> 所以:将需要 DOM 操作的 js 要放在 body 标签的最后,原因就是不阻塞 HTML 文档的解析,使页面尽可能快的在浏览器上渲染呈现。

async属性:加载和渲染后续文档元素的过程将和脚本的加载并行进行,脚本的加载完成后就马上执行,脚本执行时会阻塞 HTML 解析。文件独立,各async文件先加载完的先执行;
总结:下载时与html解析同步,不阻塞,但下载完成后立即执行脚本,会造成阻塞

默认情况下,动态脚本表现为“async”行为。

  1. let script = document.createElement('script');
  2. script.src = "/article/script-async-defer/long.js";
  3. document.body.append(script); // (*)

**
可以通过将 async 属性显示修改为 false 以将加载优先顺序修改为文档顺序
script.async = false;

defer属性:下载与解析并行,但是执行是在dom解析完成之后,DOMContentLoaded 事件触发之前完成。会按照出现顺序执行

扩展:资源onload&onerror

resource.onload 发生在资源loaded且执行之后
img、js都可以添加onload事件
注意:
大部分的资源都是在他们被添加到document事件后开始loading,但是img标签当他有src属性就开始loading

实战:
因为img标签当他们创建时候就被loaded,所以当我们将img插入页面时用户可能看不见img图像,因为此时img被创建了但是并没有获得资源;
为了用户体验,我们可以先加载图像,当引擎执行到将img插入页面时的代码时,可以利用浏览器的记忆功能读取我们预加载的图像资源,使得img可见;
可以实现预加载的原因:img是当遇见src就开始加载 而不是当loaded才加载,所以我们可以先加载 load的时候再用

即写一个img加载完的异步函数 (#图片异步加载)

扩展:页面文件资源的放置顺序

CSS不阻塞dom的生成。
CSS不阻塞js的加载,但是会阻塞js的执行。
js会阻塞dom的生成,也就是会阻塞页面的渲染,那么css也有可能会阻塞页面的渲染。
如果把CSS放在文档的最后面加载执行,CSS不会阻塞DOM的生成,也不会阻塞JS,但是浏览器在解析完DOM后,要花费额外时间来解析CSS,而不是在解析DOM的时候,并行解析CSS。
并且浏览器会先渲染出一个没有样式的页面,等CSS加载完后会再渲染成一个有样式的页面,页面会出现明显的闪动的现象。
以应该把CSS放在文档的头部,尽可能的提前加载CSS;把JS放在文档的尾部,这样JS也不会阻塞页面的渲染。CSS会和JS并行解析,CSS解析也尽可能的不去阻塞JS的执行,从而使页面尽快的渲染完成。

参考资料