DOM
树构建
HTML
解析器不是等文档加载完成才解析的,而是边加载边解析。
网络进程根据收到的content-type=text/html
判断是html
类型文件,为该请求创建一个渲染进程,渲染进程准备好后会在网络进程和渲染进程中建立一个共享数据的管道,网络进程接收到的字节流像水一样的倒进这个管道,渲染进程的html
解析器会动态接收字节流将其解析为DOM
。
字节流Bytes
——>分词器Tokens
——>生成节点Node
——>DOM
- 通过分词器将字节流转换成
Token
。分为Tag Token(StartTag、EndTag)
和文本Token
- 将
Token
解析为DOM
节点,添加到DOM
树。Html
中有一个Token
栈结构,第一步中生成的Token
都会进入这个栈中。
如果是StartTag
,Html
解析器会为Token
创建一个Dom
节点,然后加入到DOM
树中。
如果是文本Token
,会生成一个文本节点加入DOM
树中,不用压入栈。
如果是EndTag
,Html
会查看栈顶元素有没有它的StartTag
,有就从栈顶弹出,表示该元素解析完成。
HTML
解析器开始工作时,会默认创建了一个根为 document
的空 DOM
结构,同时会将一个 StartTag document
的 Token
压入栈底。然后经过分词器解析出来的第一个 StartTag html Token
会被压入到栈中,并创建一个 html
的 DOM
节点,添加到 document
上
首次加载白屏时间
如果在2段div
中插入script
,解析到<script
>会暂停DOM
解析。JS
脚本执行完成后,Html
解析器恢复继续执行。
渲染进程接收字节流时,也会开启一个预解析线程,遇到JS
或CSS
文件,预解析线程会提前下载这些数据。请求Html
到构建DOM
之间有一段空闲时间,构建DOM
树之后css
未下载完之前也会有一段空闲时间,可能会有白屏。
<html>
<head><link href="them.css" rel="stylesheet" /></head>
<body>
<div>segmentfault.com</div>
<script src="foo.js"></script>
<div>思否</div>
</body>
</html>
解析Html
、下载CSS
、下载JS
、生成CSSOM
、执行JS
、生成布局树、绘制页面。
主要空白:下载CSS
、下载JS
、执行JS
策略:1.内联JS
、css
来移除这2种文件下载。2.减少文件大小、webpack
移除注释,压缩JS
文件。3.JS
标defer
或async
。4.拆分css
文件
优化:
- 加载阶段:图片,音频,视频文件不会阻塞页面首次渲染。
JS
、html
、css
文件会阻塞。因为构建DOM
需要Html
和JS
,构建渲染树需要CSS
。
(1). 减少关键资源个数:关键资源个数越多,首次加载时间越长。1.js
、css
改成内联。2.JS
没有DOM
或CSSOM
操作,加defer
或async
、css
不是构建页面之前加载的加媒体取消阻止显现标志madio
。他们就变成非关键资源了。
(2). 降低资源大小:资源越小,下载时间越短。压缩css
,js
资源。移除注释。
(3). 降低RTT
数量:传输分包,小于14k
只用1个RTT
。RTT
次数越多,请求时间越长。减少关键资源个数或减少关键资源大小、CDN
减少RTT
时长。 - 交互阶段:
JS
脚本,渲染进程渲染帧的速度。交互阶段,帧的渲染速度决定了交互的流畅度。
(1). 减少JS
脚本执行时间:1.执行函数分解多个任务。2.web workers
主线程之外的一个线程,可以执行js
,无法操作DOM
、CSSOM
。不要霸占主线程太久。
(2). 避免强制同步布局:通过DOM
接口,添加或删除元素,需要重新计算样式和布局。同步布局:JS
强制将计算样式和布局操作提前到当前任务中。如加个元素,然后获取此元素高度,获取就要布局后才能获取。修改DOM
前查询相关值。
(3). 避免布局抖动:一次JS
执行中,多次强布局和抖动。如for
循环中,不断读取属性值,每次读都要进行计算样式和布局。不要在修改DOM
时再去查相关值。
(4). 合理利用css
合成动画,css
合成动画在合成线程上执行。不受主线程限制,尽量用css
合成动画。
(5). 避免频繁垃圾回收,垃圾回收时会占用主线程。优化存储结构,避免小颗粒对象的产生。
DOM
的缺陷:DOM
提供了一组JS
接口来遍历或修改节点。JS
操作DOM
会影响到整个渲染流水线。如document.body.appendChild(node)
,往body
节点添加一个元素,首先渲染引擎将node
添加到body
之上,然后触发样式计算、布局、绘制、栅格化,合成等任务。强制同步布局和布局抖动问题,会大大降低渲染效率。
渲染主线程:用户输入事件、合成任务、定时器、V8垃圾回收、网络加载、HTML解析,布局等、JS回调。
页面渲染
chromium
目前采取的任务高度。
加载阶段:默认——>用户交互——>合成页面——>空闲:尽可能看到页面,页面解析,JS
优先级最高
用户交互:用户交互——>合成页面——>默认——>空闲
空闲阶段:默认,用户交互——>空闲——>合成页面
一条完整的流水线:解析HTML
文件生成DOM
、解析CSS
生成CSSOM
、执行JS
、样式计算、构造布局树、准备绘制列表、光栅化、合成显示等一系列操作。
显卡中有一块叫前缓冲区的地方,显示器会每间隔1/60
秒就读取一次前缓冲区,如果浏览器要更新显示的图片,浏览器会将新生成图片提到显卡的后缓冲区,提完之后,GPU
将后、前缓冲区互换位置,就保证了显示器读取最新的图片。但显示器读取图片和浏览器生成图片不一定都同步的,不同步会造成掉帧、卡顿、不连贯问题
当显示器将一帧画面绘制完成后,并主任务读取下一帧之前,显示器会发一个垂直同步信号给GPU
,简称vsync
。当GPU
接收vsync
信号后,会将vsync
信号同步给浏览器进程,浏览器进程将其同步到对应渲染进程,准备绘新一帧。
当渲染进程接收到用户交互任务的,要进行合成操作,这时可以设置当执行交互的任务时,将合成操作的任务优先级调到最高。
处理完DOM
,计算好布局和绘制,就要将合成线程合成最终图了。合成线程在工作时,就可以把下个合成任务优先级调最低。将页面解析、定时器等任务优先级提升 ,如果合成操作非常快,那就有空闲时间了,可以执行如V8
垃圾回收,或通过window.requestIdleCallback()
设置的回调任务等。
为了解决一直执行高优先级任务,不执行低优先级任务的问题,还给每个队列设置了执行权重,连续执行了一定个数的高优先级任务,中间会执行一次低优先级任务。
虚拟DOM
- 将页面改变的内容应用到虚拟
DOM
上,而不是直接应用到DOM
上。 - 变化被应用到虚拟
DOM
上时,虚拟DOM
并不急着去渲染页面,而仅仅是调整虚拟DOM
的内部状态,这样操作虚拟DOM
的代价就变得非常轻了。 - 在虚拟
DOM
收集到足够的改变时,再把这些变化一次性应用到真实的DOM
上。
虚拟DOM
通过JSX
和基础数据创建虚拟DOM
,由虚拟DOM
创建出真实DOM
树,再触发渲染流水线输出屏幕。
如果发生改变,就根据新数据创建新的虚拟DOM
树,然后比较2棵树,找出变化,再把所有变化一次性重新更新真实DOM
树上,更新渲染流水线,生成新页面。react
之前的递归算法比较多的话会占主线程比较久,造成卡页面。Fiber
又叫协程,执行算法的过程中让出主线程,解决占用时间久的问题。双缓存,先将计算的中间结果放在另一个缓存区,等全部计算完再把结果应用到DOM
上,减少不必要更新,保证DOM
的稳定输出。
WebComponent
渲染引擎会将所有的css
内容解析为CSSOM
。生成布局树的时候,会在CSSOM
中为布局树元素查找样式,2个相同标签显示的效果会是一样的,渲染引擎不会为他们单独设置样式。css
会影响到DOM
中其它同样标签的样式。JS
也能在任何地方修改DOM
。是阻碍组件化的2个因素。
webComponent
是一套技术的组合,具体涉及到Custom elements
(自定义元素)、Shadow DOM
(影子DOM
)、HTML templates
(Html
模板)。webComponent
提供了对局部视图封装能力,可以让DOM
、CSSOM
、JS
运行在局部环境,不会影响到全局。
微服务框架qiankun
就是用的这个。
新建一个组件:1.定义模板。2.定义内部css
样式。3.定义JS
行为
<!DOCTYPE html>
<html>
<body>
<template id="template">
<style>
p{
background-color: brown;
color: cornsilk;
}
div{
width: 200px;
background-color: bisque;
}
</style>
<div>
<p>text1</p>
<p>text2</p>
</div>
<script>
function foo(){
console.log('inner log')
}
</script>
</template>
<script>
class ShadowDomTemplate extends HTMLElement{
constructor(){
super();
//获取组件模板
const content=document.querySelector('#template').content;
//创建影子DOM节点
const shadowDom=this.attachShadow({mode:'open'});
//将模板添加到影子DOM上
shadowDom.appendChild(content.cloneNode(true));
}
}
customElements.define('temp-component',ShadowDomTemplate);
</script>
<temp-component></temp-component>
</body>
</html>
渲染出:
<!DOCTYPE html>
<html>
<head></head>
<body>
<template id="template">
#document-fragment
</template>
<script></script>
<temp-component>
#shadow-root(open)
<style>...</style>
<div>...</div>
</temp-component>
</body>
</html>
影子DOM
元素对网页不可见。
影子DOM
的作用将模板中内容与全局DOM
和css
进行隔离,实现元素和样式私有化。可以把影子DOM
看成一个作用域,内部样式和元素不会影响全局。全局下要访问影子内样式元素,通过约定好的接口,互不影响。
只通过影子DOM
隔离css
和DOM
,但不会隔离JS
。JS
可以被访问,渲染引擎判断temp-component
下shadow-root
,如是影子DOM
会跳过,样式也只会从内部找。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。