前端的纯技术就是对规范的认知
什么是 DomContentLoaded 事件?
首先想到的是查看 W3C 的 HTML5 规范 , DOMCONTENTLOGADO 事件在什么时候触发 :
Una vez que el agente del usuario deja de analizar el documento, el agente del usuario debe ejecutar los siguientes pasos:
1. Establezca la preparación del documento actual en "Interactive" y el punto de inserción a Undefined.
Avalte todos los nodos de la pila de elementos abiertos.
2. Si la lista de scripts que se ejecutará cuando el documento haya terminado de análisis no está vacía, ejecute estos subtrepadores:
2.1 Pinte el bucle de eventos hasta que la primera script en la lista de scripts que se ejecutará cuando el documento haya terminado de analizar tiene su conjunto de bandera "listo para ser ejecutado por el analizador" y el documento del analizador no tiene una hoja de estilo que bloquee los scripts.
2.2 Ejecute el primer script en la lista de scripts que se ejecutará cuando el documento haya terminado de análisis.
2.3 Eliminar el primer elemento de script de la lista de scripts que se ejecutará cuando el documento haya terminado de análisis (es decir, desplaza la primera entrada en la lista).
2.4 Si la lista de scripts que se ejecutará cuando el documento haya terminado el análisis aún no está vacía, repita estos subtrepadores nuevamente del substep 1.
3. Coloque una tarea para despedir un evento simple que burbujean llamado DomContentLoaded en el documento.
规范总是那么的晦涩 , 但至少有一点是可以明确了的 , 就是在 JS (不包括动态插入的 JS )执行完之后 , 才会触发 DomContentLoaded 事件。
接下来看看 MDN 上有关 DomContentLoaded 事件的文档 :
El evento DomContentLoaded se dispara cuando el documento ha sido completamente cargado y analizado, sin esperar las hojas de estilo, las imágenes y los subtramas para terminar de cargar
NOTA: Stylesheet Cargue la ejecución de la secuencia de comandos, por lo que si tiene un
<script>después de un<link rel="stylesheet" ...>, la página no finalizará el análisis y la carga domcontented no se disparará hasta que se cargue la hoja de estilo.
这么看来 : : : DomContentLoaded 事件本身不会等待 CSS 文件、图片、 iframe 加载完成。
: : 加载完页面 , 解析完所有标签(不包括执行 CSS 和 JS) , 并如规范中所说的设置interactive和执行每个静态的 Script 标签中的 JS , 然后触发。
而 JS 的执行 , 需要等待位于它前面的 CSS 加载(如果是外联的话)、执行完成 , 因为 JS 可能会依赖位于它前面的 CSS 计算出来的样式。
实践是检验真理的唯一标准
实验 1 : DomContentLoaded 事件不直接等待 CSS 文件、图片的加载完成
index.html:
<! DocType html> <html lang = "zh-cn"> <fead> <meta charset = "utf-8"> <title> </title> <link rel = "stylesheet" type = "text/css" href = "./ css/main.css" rel = "nofuces externos" Rel = "externo nofugua"> <Head "<head" <head "<Head" <Head "<Head" <img src = "./ img/chrome-girl.jpg"> </body> </html>
图一
如果页面中没有 Script 标签 , DomContentLoaded 事件并没有等待 CSS 文件、图片加载完成。
Chrome 开发者工具的 Timeline 面板可以帮我们记录下浏览器的一举一动。图一中红色小方框中的蓝线 , 表示 表示 事件 事件 它右边的红线和绿线分别表示 它右边的红线和绿线分别表示 事件和 事件和 事件和 鼠标 鼠标 鼠标 在这些线露出灰色方框下面的一小部分时就会出现带有说明文字的 在这些线露出灰色方框下面的一小部分时就会出现带有说明文字的 在这些线露出灰色方框下面的一小部分时就会出现带有说明文字的 在这些线露出灰色方框下面的一小部分时就会出现带有说明文字的 鼠标 鼠标 鼠标 鼠标 鼠标 鼠标 在这些线露出灰色方框下面的一小部分时就会出现带有说明文字的 consejos (这交互够反人类的对吧!)。
实验 2 : DomContentLoaded 事件需要等待 JS 执行完才触发
index.html:
<! DocType html> <html lang = "zh-cn"> <head> <meta charset = "utf-8"> <title> </title> <script type = "text/javascript"> console.timestamp ('en línea script antes del enlace en la cabeza'); Window.AdDeventListener ('DomContentLoaded', function () {console.timestamp ('DomContentLoaded Event');}); </script> <link rel = "stylesheet" type = "text/css" href = "./ css/main.css" rel = "externo nofollow" rel = "externo nofollow"> <script type = "text/javaScript"> console.timestamp ('script inline después del enlace en la cabeza'); </script> </head> <body> <p> content </p> <img src = "./ img/chrome-girl.jpg"> <script type = "text/javascript" src = "./ js/main.js"> </script> </body> </html>main.js:
console.timestamp ('script externo después del enlace en el cuerpo');
图二
如果页面中静态的写有 Script 标签 , DomContentLoaded 事件需要等待 JS 执行完才触发。
而 Script 标签中的 JS 需要等待位于其前面的 CSS 的加载完成。
console.timestamp () 可以向 Línea de tiempo 中添加一条记录 并对应上方的一条黄线。 并对应上方的一条黄线。
从图二中可以看出 , 在 在 css 之前的 js 立刻得到了执行 , 而在 而在 css 之后的 js , 需要等待 需要等待 css 加载完后才执行 , 比较明显的是 main.js 早就加载完了 , 但还是要等 main.css 加载完才能执行。而 domcontentent cargado 事件 则是在 js 执行完后才触发。滑动 执行完后才触发。滑动 执行完后才触发。滑动 面板中表示展示区域的滑块 如图三 如图三 放大后即可看到表示 放大后即可看到表示 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。 中添加的记录也可证明其触发时间。
图三
现代浏览器会并发的预加载 CSS, JS , 也就是一开始就并发的请求这些资源 但是 , 执行 执行 CSS 和 JS 的顺序还是按原来的依赖顺序( JS 的执行要等待位于其前面的 CSS 和 JS 加载、执行完)。先加载完成的资源 , 如果其依赖还没加载、执行完 就只能等着。 就只能等着。
实验 3 : IMG 何时开始解码、绘制?
: : IMG 的请求老早就发出了 , 但延迟了一段时间才开始解码。如图二、图三中的红框所示 截图中只框出了一部分表示解码的记录 截图中只框出了一部分表示解码的记录 而实际上这些表示解码的记录一直持续到 IMG 加载结束 , 如图四所示 , IMG 是一边加载一边解码的 :
图三
现代浏览器会并发的预加载 CSS, JS , 也就是一开始就并发的请求这些资源 但是 , 执行 执行 CSS 和 JS 的顺序还是按原来的依赖顺序( JS 的执行要等待位于其前面的 CSS 和 JS 加载、执行完)。先加载完成的资源 , 如果其依赖还没加载、执行完 就只能等着。 就只能等着。
实验 3 : IMG 何时开始解码、绘制?
: : IMG 的请求老早就发出了 , 但延迟了一段时间才开始解码。如图二、图三中的红框所示 截图中只框出了一部分表示解码的记录 截图中只框出了一部分表示解码的记录 而实际上这些表示解码的记录一直持续到 IMG 加载结束 , 如图四所示 , IMG 是一边加载一边解码的 :
图四
抱着 “猜想 - ― 验证” 的想法 , 我猜想这是因为 img 这个资源是否需要展现出来 , 需要等所有的 js 和 css 的执行完才知道 , 因为 main.js 可能会执行某些 dom 操作 , 比如删除这个 img 元素 , 或者修改其 src 属性 , 而 css 可能会将其display: none
图五
图六
图七
图五中没有 JS 和 CSS , IMG 的数据一接收到就马上开始解码了。
图六中没有 JS , 但 IMG 要等到 CSS 加载完才开始解码。
图七的代码跟图六的代码唯一的区别是 CSS 把 IMG 给display: none; , 这使得 img 虽然请求了 但根本没有进行解码。 但根本没有进行解码。
这说明 , img 是否需要解码、绘图( pintura )出来 , 确实需要等 css 加载、执行完才能知道。也就是说 , css 会阻塞 img 的展现!那么 js 呢?
图八
图八对应的代码 :
<! DocType html> <html lang = "zh-cn"> <head> <meta charset = "utf-8"> <title> </title> <script type = "text/javaScript"> console.timestamp ('en línea script en cabeza'); Window.AdDeventListener ('DomContentLoaded', function () {console.timestamp ('DomContentLoaded Event');}); </script> </head> <body> <p> content </p> <img src = "./ img/chrome-girl.jpg"> <script type = "text/javascript" src = "./ js/main.js"> </script> </body> </html>非常令人惊讶 , 在有 js 而没有 css 的页面中 , img 居然能够在收到数据后就立刻开始解码、绘图( pintura) , , , js 并没有阻塞 img 的展现!这跟我们以前理解的 js 会阻塞 img 资源的传统观念不太一样 , 看来 Chrome 对 img 的加载和展现做了新的优化。
我们常用的 jQuery 的 $ (documento) .Ready () 方法 , 就是对 DomContentLoaded 事件的监听(当然 , 其内部还会通过模拟 DomContentLoaded 事件和监听 事件和监听 事件来提供降级方案)。通常推荐在 事件来提供降级方案)。通常推荐在 事件触发的时候为 事件触发的时候为 dom 元素注册事件。所以尽快的让 domcontententdentent 事件触发 就意味着能够尽快让页面可交互.
减小 CSS 文件体积 , 把单个 CSS 文件分成几个文件以并行加载 , 减少 CSS 对 JS 的阻塞时间
次要的 JS 文件 , 通过动态插入 Script 标签来加载(动态插入的 Script 标签不阻塞 DomContentLoaded 事件的触发)
CSS 中使用的精灵图 , 可以利用对 IMG 的预加载 , 放在 HTML 中跟 CSS 文件一起加载
在做实验的过程中 , 感觉 感觉 cromo 开发者工具的 cronograma 面板非常强大 , 浏览器的一举一动都记录下来。以前我们前端开发要想理解、探索浏览器的内部行为 , 或者摸着石头过河的做黑盒测试 , 或者事倍功半的研究浏览器源码 , 唯一高效点的做法就是学习别人的研究经验 看老外的文章 , 但浏览器的发展日新月异(比如这次实验发现的 js 不阻塞 img 的展现) , 别人的经验始终不是最新、最适合的 关键是要结合自己的业务、需求场景 , 有针对性的做分析和优化。 有针对性的做分析和优化。
Ps.
以上测试环境为 Windows/Chrome , 并用 Fiddler 模拟慢速网络