浏览器的多线程中有的线程负责加载资源有的线程负责执行脚本有的线程负责渲染界面有的线程负责轮询、监听用户事件。
这些线程根据浏览器自身特点以及web标准等等有的会被浏览器特意的阻塞。两个很明显的阻塞就是脚本执行时对其他线程的阻塞和脚本加载时对其他线程的阻塞。
这两个阻塞发生在HTML页面初次解析时它们对性能的影响较大原因是
document对象绑定了一个事件DOMContentLoaded。这个事件会在DOM解析完成之后触发。这个事件触发之后而不是window.load事件会进入异步事件驱动阶段另一个线程控制。也就是说DOM解析工作不完成用户与页面的很多并不是所有事件交互就无法进行。这时候浏览器的忙指示那个页面上方的烦人的旋转的圆圈不会消失。
DOMContentLoaded什么时候触发
DOMContentLoaded事件本身不会等待CSS文件、图片、iframe加载完成。
DOMContentLoaded的触发时机是加载完页面解析完所有标签不包括执行CSS和JS但是JS的执行需要等待位于它前面的CSS加载如果是外联的话、执行完成因为JS可能会依赖位于它前面的CSS计算出来的样式。所以
如果页面中没有script标签DOMContentLoaded事件并没有等待CSS文件、图片加载完成。
如果页面中静态的写有script标签DOMContentLoaded事件需要等待JS执行完才触发。而且script标签中的JS需要等待位于其前面的CSS的加载完成。
注现代浏览器会并发的预加载CSS、JS、IMG例如当 HTML 解析器HTML Parser被脚本阻塞时解析器虽然会停止构建 DOM但仍会识别该脚本后面的资源并进行预加载。但是执行CSS和JS的顺序还是按原来的依赖顺序JS的执行要等待位于其前面的CSS和JS加载、执行完——先加载完成的资源如果其依赖还没加载、执行完就只能等着。
所以就造成外部资源阻塞渲染如CSS 与 JavaScript
默认情况下CSS 被视为阻塞渲染的资源这意味着浏览器将不会渲染任何已处理的内容直至 CSSOM 构建完毕。
JavaScript 不仅可以读取和修改 DOM 属性还可以读取和修改 CSSOM 属性。
默认情况下CSS 被视为阻塞渲染的资源存在阻塞的 CSS 资源时浏览器会延迟 JavaScript 的执行和 DOM 构建这意味着浏览器将不会渲染任何已处理的内容直至 CSSOM 构建完毕。
总结如下
css加载不会阻塞DOM树的解析
css加载会阻塞DOM树的渲染
css加载会阻塞后面js语句的执行
css会阻塞js同理css也会阻塞img解码、paint浏览器认为你的CSS没有加载完毕不确定图片的样式到底如何牵扯到重绘资源问题,js不会阻塞img的解码、paint估计chrome做了优化具体本人还不知希望客官补充。
css阻塞优化
还可以用媒体类型media type和媒体查询media query来解除对渲染的阻塞。
media=“print",会加载但不会阻塞media="(min-width:320px)",会在符合查询条件下阻塞适配css会执行
大css文件拆分成多个小css文件并发加载
因为渲染线程和js线程与资源进行加载的线程并不互斥不会互斥意味着资源的加载可以和UI渲染、重排事件响应或者JavaScript代码的执行的并发进行。
所以资源加载器线程会一直进行并发加载。
这里还有一个知识点下载的最大并行数指的是从一个主机上下载的最大并行数如果从多个主机下载资源这个数量会翻倍但是由于对DNS的解析也是一个性能优化的点故而一般策略是不应设置超过4个主机最好只设置2个主机。
但是操蛋的就是如果浏览器解析DOM时需要下载脚本资源那么下载这个资源的线程就是阻塞其他下载线程以及渲染线程导致渲染速度变慢。
但是假设该脚本下载的速度较慢而且多个脚本非并发下载并且假如多个<script>内脚本执行时间较长的话DOM解析工作还是会一直完不成。
故而我们需要无阻塞加载脚本的技术。
js阻塞优化
因为脚本执行和渲染DOM的并发可能会引发严重的冲突脚本可以修改DOM
所以JavaScript引擎和渲染引擎所在的两个线程被设计为互斥的
这就意味着在执行<script>中内容时浏览器会切换到JavaScript引擎所在的线程此时渲染引擎所在的线程会阻塞故其后元素的解析和渲染会暂停。这时候如果脚本执行时间太长的话不仅后面的元素会一直看不到对DOM的解析工作也会一直完不成。用户会陷入焦急的等待中。
为了防止javascript阻塞我们会
1、把<script>放到紧跟</body>之前的位置
这样就不会影响需要放到页面上的UI元素的解析了。这样的好处就是用户能即使看到页面上的UI元素而防止出现了浏览器白屏等现象。
2、动态脚本元素-不重要的js动态插入。
因为document.createElement("script")的async属性默认为true而document.head.appendChild代码之后由于没有触发渲染树的重绘切换回的渲染线程会将剩下的DOM解析并渲染完毕。同时新插入的<script>中的资源也会并发的下载。
var script=document.createElement("script");console.log(script.async);//true
同理用XHR对象下载代码并注入到页面也可以达到同样的效果
如果需要同步执行需要将async属性设置为fasle
3、h5时代script添加defer或asyn两个属性(html4.0中定义了deferhtml5.0中定义了async)
如果 script 标签中包含 defer那么这一块脚本将不会影响 HTML 文档的解析而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。即整个 document 解析完毕且 defer-script 也加载完成之后这两件事情的顺序无关会执行所有由 defer-script 加载的 JavaScript 代码然后触发 DOMContentLoaded 事件。defer不会改变script中代码执行顺序
如果 script 标签中包含 async则 HTML 文档构建不受影响不需要等待 async-script 执行。但是async-script 加载完成后就会立即执行如果页面还是没有解析完成就会停下来阻塞页面等此脚本执行完毕再继续解析。async-script 可能在 DOMContentLoaded 触发之前或之后执行但一定在 load 触发之前执行。而且多个 async-script 的执行顺序是不确定的。
document.readyState
说道DOMContentLoaded不得不提readystatechange通过document.readyState值来更进一步来判断文档状态
uninitiatedxml 对象被产生但没有任何文件被加载。
loadingdocument正在下载文件尚未开始解析。
loaded部分的文件已经加载且进行解析但对象模型尚未生效。
interactivedocument完成了解析但是资源还在下载对象模型是有效但只读的。
complete代表加载成功文档加载完成并且所有resource都加载完毕
通过下面代码验证在chrome上貌似只有 interactive和complete。
document.addEventListener("DOMContentLoaded",function () { console.log("DOMContentLoaded"+new Date())}); document.addEventListener("readystatechange",function () { console.log("B_____"+new Date()); console.log(document.readyState)// switch (document.readyState){// case "loading":// console.log("LOADING"+new Date());// break;// case "loaded":// console.log("loaded"+new Date());// break;// case "interactive":// console.log("interactive"+new Date());// break;// case "complete":// console.log("complete"+new Date());// break;// } }); console.time("A")
A: 5.89208984375ms
B_____Thu May 17 2018 10:23:36 GMT+0800 (CST)
interactive
DOMContentLoadedThu May 17 2018 10:23:36 GMT+0800 (CST)
B_____Thu May 17 2018 10:23:36 GMT+0800 (CST)
complete
但是今天看了你不知道的 DOMContentLoaded
这里又有疑问interactive DOMContentLoaded complete onload三个先后顺序是什么呢
DOMContentLoaded和interactive表示文档解析完成且资源未完全加载完成。区别呢?执行顺序呢
验证表明interactive 》DOMContentLoaded 》 complete 》 onload
但是DOMContentLoaded触发时候document.readyState一般是interactive也有可能complete。而当页面有大量的二进制文件页面加载的时长大于阻塞的时长的时候document.readyState=complete 可能反而在 onload 事件之后才能触发(这个我未完成验证出这种情况)
我觉得onreadystatechange这个不是很靠谱一般用DOMContentLoaded判断页面解析完全。希望哪位大牛提供这方面的补充感激不尽
在图片上,也有onload跟complete
document.getElementById('load').onclick = function() { var img = new Image(); if(img.complete) { console.log('dd'); } img.onload = function() { console.log('ff') } img.class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="images/1-logo.png"; }
这里顺带提下img加载相关 属性
onload表示加载好换言之没有加载好不会执行
onAbort图片加载的时候用户通过点击停止加载时出发
onerror如果图片不存在网络很不通畅也可能触发 onerror事件
complete图片显示出来以后为true,
共同学习,写下你的评论
评论加载中...
作者其他优质文章