首先是网页内容,加载完输入到HTML解释器,解释后构成DOM树,这期间如果遇到JavaScript代码就交给JavaScript引擎去处理,如果网页中包含CSS,就交给CSS解释器;DOM树简历的时候,渲染引擎接收来自CSS解释器的样式信息,构建一个新的你日不会吐模型,该模型由布局模块计算模型内部各个元素的位置和大小信息
渲染流程有四个主要步骤:
renderer与DOM元素是相对应的,但并不是一一对应,有些DOM元素没有对应的renderer,而有些DOM元素却对应了好几个renderer,对应多个renderer的情况是普遍存在的,就是为了解决一个renderer描述不清楚如何显示出来的问题,譬如有下拉列表的select元素,我们就需要三个renderer:一个用于显示区域,一个用于下拉列表框,还有一个用于按钮。
另外,renderer与DOM元素的位置也可能是不一样的。那些添加了float或者position:absolute的元素,因为它们脱离了正常的文档流,构造Render树的时候会针对它们实际的位置进行构造。
上面确定了renderer的样式规则后,然后就是重要的显示元素布局了。当renderer构造出来并添加到Render树上之后,它并没有位置跟大小信息,为它确定这些信息的过程,接下来是布局(layout)。
浏览器进行页面布局基本过程是以浏览器可见区域为画布,左上角为(0,0)基础坐标,从左到右,从上到下从DOM的根节点开始画,首先确定显示元素的大小跟位置,此过程是通过浏览器计算出来的,用户CSS中定义的量未必就是浏览器实际采用的量。如果显示元素有子元素得先去确定子元素的显示信息。
布局阶段输出的结果称为box盒模型(width,height,margin,padding,border,left,top,…),盒模型精确表示了每一个元素的位置和大小,并且所有相对度量单位此时都转化为了绝对单位。
在绘制(painting)阶段,渲染引擎会遍历Render树,并调用renderer的 paint() 方法,将renderer的内容显示在屏幕上。绘制工作是使用UI后端组件完成的。
回流(reflow):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染。reflow 会从<html>这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
重绘(repaint):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
在浏览器拿到HTML、CSS、JS等外部资源到渲染出页面的过程,有一个重要的概念关键渲染路径(Critical Rendering Path)。
例如为了保障首屏内容的最快速显示,通常会提到一个渐进式页面渲染,但是为了渐进式页面渲染,就需要做资源的拆分,那么以什么粒度拆分、要不要拆分,不同页面、不同场景策略不同。
现代浏览器总是并行加载资源,例如,当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载。
CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。JavaScript 应尽量少影响 DOM 的构建。
defer 属性表示延迟执行引入 JavaScript,即 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,再触发DOMContentLoaded(初始的 HTML 文档被完全加载和解析完成之后触发,无需等待样式表图像和子框架的完成加载) 事件。
defer 不会改变 script 中代码的执行顺序,示例代码会按照 1、2、3 的顺序执行。所以,defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。
async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行,无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发(HTML解析完成事件)之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。
从上一段也能推出,多个 async-script 的执行顺序是不确定的,谁先加载完谁执行。值得注意的是,向 document 动态添加 script 标签时,async 属性默认是 true。
使用 document.createElement 创建的 script 默认是异步的
通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。
chrome 官方文档:https://developers.google.com/web/fundamentals/performance/?hl=en
翻译:https://x5.tencent.com/tbs/document/doc-chrome.html
setTimeout(callback)和setInterval(callback)无法保证callback函数的执行时机,很可能在帧结束的时候执行,从而导致丢帧。
requestAnimationFrame(callback)可以保证callback函数在每帧动画开始的时候执行。拓展阅读《频率史—从电源频率到音频采样频率与视频帧率:29.97/44.1》、《弄懂javascript的执行机制:事件轮询|微任务和宏任务》
JS代码运行在浏览器的主线程上,与此同时,浏览器的主线程还负责样式计算、布局、绘制的工作,如果JavaScript代码运行时间过长,就会阻塞其他渲染工作,很可能会导致丢帧。
前面提到每帧的渲染应该在16ms内完成,但在动画过程中,由于已经被占用了不少时间,所以JavaScript代码运行耗时应该控制在3-4毫秒。
如果真的有特别耗时且不操作DOM元素的纯计算工作,可以考虑放到Web Workers中执行。
添加或移除一个DOM元素、修改元素属性和样式类、应用动画效果等操作,都会引起DOM结构的改变,从而导致浏览器要repaint或者reflow。
布局,就是浏览器计算DOM元素的几何信息的过程:元素大小和在页面中的位置。每个元素都有一个显式或隐式的大小信息,决定于其CSS属性的设置、或是元素本身内容的大小、抑或是其父元素的大小。在Blink/WebKit内核的浏览器和IE中,这个过程称为布局。在基于Gecko的浏览器(比如Firefox)中,这个过程称为Reflow。
布局的时间消耗主要在于:
一份详细的能触发布局、绘制或渲染层合并的CSS属性清单:CSS Triggers
新的Flexbox比旧的Flexbox和基于浮动的布局模型更高效。
在任何情况下,不管是是否使用Flexbox,你都应该努力避免同时触发所有布局,特别在页面对性能敏感的时候(比如执行动画效果或页面滚动时)。
将一帧画面渲染到屏幕上的处理顺序如下所示:
大多数情况下,都不需要先修改然后再读取元素的样式属性值,使用上一帧的值就足够了。过早地同步执行样式计算和布局是潜在的页面性能的瓶颈之一
比强制同步布局更糟:连续快速的多次执行它。如:
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
FastDom是一个轻量的库,它提供一个公共接口,能让DOM的读/写操作捆绑在一起。
https://github.com/wilsonpage/fastdom
绘制并非总是在内存中的单层画面里完成的。实际上,浏览器在必要时将会把一帧画面绘制成多层画面,然后将这若干层画面合并成一张图片显示到屏幕上。
这种绘制方式的好处是,使用tranforms来实现移动效果的元素将会被正常绘制,同时不会触发对其他元素的绘制。这种处理方式和思想跟图像处理软件(比如Sketch/GIMP/Photoshop)是一致的,它们都是可以在图像中的某个单个图层上做操作,最后合并所有图层得到最终的图像。
有时候尽管把元素提升到了一个单独的渲染层,渲染工作依然是必须的。渲染过程中一个比较有挑战的问题是,浏览器会把两个相邻区域的渲染任务合并在一起进行,这将导致整个屏幕区域都会被绘制。比如,你的页面顶部有一个固定位置的header,而此时屏幕底部有某个区域正在发生绘制的话,整个屏幕都将会被绘制。
注意:在DPI较高的屏幕上,固定定位的元素会自动地被提升到一个它自有的渲染层中。但在DPI较低的设备上却并非如此,因为这个渲染层的提升会使得字体渲染方式由子像素变为灰阶(详细内容请参考:Text Rendering),我们需要手动实现渲染层的提升。
减少绘制区域通常需要对动画效果进行精密设计,以保证各自的绘制区域之间不会有太多重叠,或者想办法避免对页面中某些区域执行动画效果。
比如js 获取元素的offsetTop ffsetTop 比如getBoundingClientRect 消耗更少。
在css里面,重绘 backgroun 比如 box-shadow 消耗更好。
那些能性能更加耗资源,我也不知道,道友若知,请留言赐教,多谢。手工就 paint profiler 分析对比咯
从性能方面考虑,最理想的渲染流水线是没有布局和绘制环节的,只需要做渲染层的合并即可:
之前也参看:《关于css3之transform一些坑的总结-transform对普通元素的N多渲染》
具体参看《Debounce 和 Throttle 的原理及实现》
参考文章:
从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 https://www.cnblogs.com/cangqinglang/p/8963557.html
Chrome源码剖析、上--多线程模型、进程通信、进程模型https://www.cnblogs.com/v-July-v/archive/2011/04/02/2036008.html
Chrome源代码分析之进程和线程模型(三) https://blog.csdn.net/namelcx/article/details/6582730
http://dev.chromium.org/developers/design-documents/multi-process-architecture
chrome渲染机制浅析 https://www.jianshu.com/p/99e450fc04a5
浅析浏览器渲染原理 https://segmentfault.com/a/1190000012960187
javascript宏任务和微任务 https://www.cnblogs.com/fangdongdemao/p/10262209.html
浏览器与Node的事件循环(Event Loop)有何区别? https://blog.csdn.net/Fundebug/article/details/86487117
转载本站文章《浏览器层面优化前端性能(2):Reader引擎线程与模块分析优化点》,
请注明出处:https://www.zhoulujun.cn/html/webfront/browser/webkit/2020_0615_8464.html
在面试的时候,经常会遇到一道经典的面试题:
如何优化网页加载速度?
常规的回答中总会有一条:
把 css 文件放在页面顶部,把 js 文件放在页面底部。
那么,为什么要把 js 文件放在页面的最底部呢?
我们先来看下这段代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Hi</title>
<script>
console.log("Howdy ~");
</script>
<script src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script>
<script src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script>
</head>
<body>
Hello ~
</body>
</html>
他的执行顺序是:
script 加载逻辑
浏览器的解析规则是:如果遇到 script 标签,则暂停构建 DOM,转而开始执行 script 标签,如果是外部 script,那么浏览器还需要一直等待其「下载」并「执行」后,再继续解析后面的 HTML。
如果请求并执行「vue.global.js」需要 3 秒,「vue-router.global.js」需要 2 秒,那么页面中的 Hello ~,则至少需要 5 秒以上才会展示出来。
可以看到,script 标签会阻塞浏览器解析 HTML,如果把 script 都放在 head 中,在网络不佳的情况下,就会导致页面长期处于白屏状态。
在很久以前,一般都是将这些外联脚本,放在 body 标签的最后面,确保先解析展示 body 中的内容,然后再一个个请求执行这些外联脚本。
那有没有其他更优雅的解决方案呢?
答案是肯定的,现在 script 标签新增了 2 个属性:defer 和 async,就是为了解决此类问题,提升页面性能的。
先看一下 MDN 上的解释:
这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。
有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。
文档是直接总结了他的特性,我们先看看下面的代码,展开说说细节,加深一下理解。
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Hi</title>
<script>
console.log("Howdy ~");
</script>
<script defer src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script>
<script defer src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script>
</head>
<body>
Hello ~
</body>
</html>
他的执行顺序是:
script defer 加载逻辑
如果在 script 标签上设置了 defer 属性,那么在浏览器解析到这里时,会默默的在后台开始下载此脚本,并继续解析后面的 HTML,并不会阻塞解析操作。
等到 HTML 解析完成之后,浏览器会立即执行后台下载的脚本,脚本执行完成之后,才会触发 DOMContentLoaded 事件。
看起来还是蛮好理解的吧?咱们再来讨论 2 个小细节:
Q1: 如果 HTML 解析完成之后,设置了 defer 属性的脚本还没下载完成,会怎样?
A1: 浏览器会等脚本下载完成之后,再执行此脚本,执行完成之后,再触发 DOMContentLoaded 事件。
Q2: 如果有多个设置了 defer 属性的脚本,那浏览器会如何处理?
A2: 浏览器会并行的在后台下载这些脚本,等 HTML 解析完成,并且所有脚本下载完成之后,再按照他们在 HTML 中出现的相对顺序执行,等所有脚本执行完成之后,再触发 DOMContentLoaded 事件。
最佳实践:
建议所有的外联脚本都默认设置此属性,因为他不会阻塞 HTML 解析,可以并行下载 JavaScript 资源,还可以按照他们在 HTML 中的相对顺序执行,确保有依赖关系的脚本运行时,不会缺少依赖。
在 SPA 的应用中,可以考虑把所有的 script 标签加上 defer 属性,并且放到 body 的最后面。在现代浏览器中,可以并行下载提升速度,也可以确保在老浏览器中,不阻塞浏览器解析 HTML,起到降级的作用。
注意:
按照惯例,先看一下 MDN 上的解释:
对于普通脚本,如果存在 async 属性,那么普通脚本会被并行请求,并尽快解析和执行。
对于模块脚本,如果存在 async 属性,那么脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。
该属性能够消除解析阻塞的 Javascript。
解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本,之后才能继续解析。
感觉这段描述的已经蛮清晰了,不过咱们还是先看看下面的代码,展开说说细节,加深一下理解。
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Hi</title>
<script>
console.log("Howdy ~");
</script>
<script async src="https://google-analytics.com/analytics.js"></script>
<script async src="https://ads.google.cn/ad.js"></script>
</head>
<body>
Hello ~
</body>
</html>
他的执行顺序是:
script async 加载逻辑
浏览器在解析到带有 async 属性的 script 标签时,也不会阻塞页面,同样是在后台默默下载此脚本。当他下载完后,浏览器会暂停解析 HTML,立马执行此脚本。
看起来还是蛮好理解的吧?咱们再来讨论 2 个小细节:
Q1:如果设置了 async 属性的 script 下载完之后,浏览器还没解析完 HTML,会怎样?
A1:浏览器会暂停解析 HTML,立马执行此脚本,等执行完之后,再继续解析 HTML。
Q2:如果有多个 async 属性的 script 标签,那等他们下载完成之后,会按照代码顺序执行吗?
A2:不会。执行顺序是:谁先下载完成,谁先执行。async 的特点是「完全独立」,不依赖其他内容。
最佳实践:
当我们的项目,需要集成其他独立的第三方库时,可以使用此属性,他们不依赖我们,我们也不依赖于他们。 通过设置此属性,让浏览器异步下载并执行他,是个不错的优化方案。
注意:
另外:async 和 defer 之间最大的区别在于它们的执行时机。
你有没有想过,如果一个 script 标签同时设置 defer 和 async,浏览器会如何处理?
先说结论:从表现形式上来说,async 的优先级比 defer 高,也就是如果同时存在这 2 个属性,那么浏览器将会以 async 的特性去加载此脚本。
这主要分 2 种情况: 如果是「普通脚本」,浏览器会优先判断async属性是否存在,如果存在,则以async特性去加载此脚本,如果不存在,再去判断是否存在defer属性。
如果是「模块脚本」,浏览器会判断async属性是否存在:
最后,用一张图概括一下这两个属性的加载模式吧:
defer 和 async 的加载模式
本文首发于:https://github.com/mrlmx/blogs/issues/4 ,如果喜欢,记得去点个赞哦~ ❤️
站的加载速度是跳失率的关键因素,大多数买家对于网页加载速度缓慢的网站会感到不耐烦。要了解网站的速度并提高网页加载速度,你可以使用Google Page Speed Insights工具。
本文将为你介绍该工具,了解它如何能够帮助你优化网站。
Google Page Speed Insight(PSI)是一款旨在优化所有设备上的网页、提高网页加载速度的工具。
它可以为以下各项创建单独的诊断:
1、桌面设备
2、移动设备
该工具会对网页进行诊断,并提供页面总体速度的分数,然后该工具会告诉你哪些地方需要优化。
此外,该工具会对你的网页在0到100之间评定一个分数,使你确切地知道网页的运行状况。
以下是使用该工具提高网页加载速度的方法。
将网站的URL粘贴到Page Speed Insights并获得分析结果后,你需要查看红色和黄色警报,以及导致网站运行缓慢的原因。
下面是一些使你的网站运行缓慢的常见问题,以及如何修复这些问题的方法。
每个网站都含有一定数量的图像,以使网站更加人性化、更具吸引力或更加生动有趣。但是,图像通常可能是导致网站运行以及加载时间缓慢的原因。
如果Page Speed Insight提醒你有关图像尺寸的信息,你则需要执行以下操作:
· 查看上传到网站上的图像;
· 使用压缩工具或插件调整图像大小;
· 压缩要上传的图像,以避免再次获得低分。
图像经过压缩后,你会注意到得分和加载速度有了显着改善。
此外,考虑使用延迟加载图像,在实际需要显示图像的时候再进行加载,这也可以帮助你提高网页速度。
每次浏览器加载网页时,下载网页文件的过程都会在后台进行。浏览器需要下载正确显示网页所需的所有文件:
· HTML;
· 图片;
· CSS;
· JavaScript。
此过程会使整个过程变慢,并拉低分数。
但是,浏览器缓存可以帮助你减少或消除此问题:
· 它将文件存储在用户的浏览器中;
· 用户重新进入网站时就无需再次下载此类文件;
· 网站加载速度更快。
你可以使用缓存插件以优化网站、提高加载速度。
JavaScript文件也可能会拖慢网页加载速度,这是一个不容忽视的常见问题。
要阻止这种情况的发生,你需要先停止加载CSS和JavaScript资源。
方法如下:
· 内联脚本;
· 延迟加载不重要的脚本
这样,你可以确保提高加载速度,并显着降低跳失率。
Page Speed Insight工具可能会警告你的另一个问题是源代码占用的空间。
编码会占用一定的空间,如果能对其进行精简,则可以加快网页加载时间。
你可以通过删除或修复重复数据来进行精简,例如:
· 空白空间;
· 未使用的代码;
· 重复格式化;
· 评论;
· 等等。
需要精简的三个资源是:
· JS;
· CSS;
· HTML。
每个资源都有一个工具或插件,可以帮助你进行精简。确保使用它们,并尽快解决此问题。
此外,重定向过程也会对加载速度造成影响。页面加载时间要是过长,很可能是重定向导致其无法正常运转的原因。
当页面重定向到针对某些设备优化过的不同页面时,就会发生这种情况。
那么,如何解决这个问题?
有一种方法:确保构建适合所有设备的响应式设计。这样,你就无需重定向,并且能够提高网页的加载速度。
【限时免费】如何使用谷歌官方工具,解决营销路上遇到的道道难关?
(编译/雨果网 陈杰)
【特别声明】未经许可同意,任何个人或组织不得复制、转载、或以其他方式使用本网站内容。转载请联系:editor@cifnews.com
上雨果网搜索“跨境资料库”,领取欧美/东南亚各国市场商机、各大平台热销品报告、跨境电商营销白皮书!
*请认真填写需求信息,我们会在24小时内与您取得联系。