作者:帅哥大香蕉 | 来源:互联网 | 2023-10-12 13:58
本文示例源代码请戳github博客,提议人人着手敲敲代码。媒介浏览器衬着页面的历程从耗时的角度,浏览器要求、加载、衬着一个页面,时刻花在下面五件事变上:DNS查询TCP衔接HTTP
本文示例源代码请戳
github博客,提议人人着手敲敲代码。
媒介
浏览器衬着页面的历程
从耗时的角度,浏览器要求、加载、衬着一个页面,时刻花在下面五件事变上:
- DNS 查询
- TCP 衔接
- HTTP 要求即响应
- 服务器响应
- 客户端衬着
本文议论第五个部份,即浏览器对内容的衬着,这一部份(衬着树构建、规划及绘制),又能够分为下面五个步骤:
- 处置惩罚 HTML 标记并构建 DOM 树。
- 处置惩罚 CSS 标记并构建 CSSOM 树
- 将 DOM 与 CSSOM 兼并成一个衬着树。
- 依据衬着树来规划,以盘算每一个节点的多少信息。
- 将各个节点绘制到屏幕上。
须要邃晓,这五个步骤并不一定一次性递次完成。假如 DOM 或 CSSOM 被修正,以上历程须要反复实行,如许才盘算出哪些像素须要在屏幕上举行从新衬着。现实页面中,CSS 与 Javascript 往往会屡次修正 DOM 和 CSSOM。
1、浏览器的线程
在细致申明之前我们来看一下浏览器线程。这将有助于我们明白后续内容。
浏览器是多线程的,它们在内核制控下相互配合以坚持同步。一个浏览器最少完成三个常驻线程:Javascript 引擎线程,GUI 衬着线程,浏览器事宜触发线程。
- GUI 衬着线程:担任衬着浏览器界面 HTML 元素,当界面须要重绘(Repaint)或由于某种操纵激发回流(reflow)时,该线程就会实行。在 Javascript 引擎运转剧本时期,GUI 衬着线程都是处于挂起状况的,也就是说被”凝结”了。
- Javascript 引擎线程:重要担任处置惩罚 Javascript 剧本顺序。
- 定时器触发线程:浏览器定时计数器并非由 Javascript 引擎计数的, Javascript 引擎是单线程的, 假如处于壅塞线程状况就会影响记计时的正确, 因而浏览器经由过程零丁线程来计时并触发定时。
- 事宜触发线程:当一个事宜被触发时该线程会把事宜添加到待处置惩罚行列的队尾,守候 JS 引擎的处置惩罚。这些事宜包含当前实行的代码块如定时使命、浏览器内核的其他线程如鼠标点击、AJAX 异步要求等。由于 JS 的单线程关联一切这些事宜都得列队守候 JS 引擎处置惩罚。定时块任何和 ajax 要求等这些异步使命,事宜触发线程只是在抵达定时时刻或许是 ajax 要求胜利后,把回调函数放到事宜行列当中。
- 异步 HTTP 要求线程:在 XMLHttpRequest 在衔接后是经由过程浏览器新开一个线程要求, 将检测到状况变动时,假如设置有回调函数,异步线程就发生状况变动事宜放到 Javascript 引擎的处置惩罚行列中守候处置惩罚。在提议了一个异步要求时,http 要求线程则担任去要求服务器,有了响应今后,事宜触发线程再把回到函数放到事宜行列当中。
2、构建DOM树与CSSOM树
浏览器从收集或硬盘中取得HTML字节数据后会经由一个流程将字节剖析为DOM树:
- 编码: 先将HTML的原始字节数据转换为文件指定编码的字符。
- 令牌化: 然后浏览器会依据HTML范例来将字符串转换成种种令牌(如
、
如许的标签以及标签中的字符串和属性等都邑被转化为令牌,每一个令牌具有迥殊寄义和一组划定规矩)。令牌记录了标签的最先与终了,经由过程这个特征能够轻松推断一个标签是不是为子标签(假设有
与
两个标签,当
标签的令牌还未碰到它的终了令牌
就碰见了
标签令牌,那末
就是
的子标签)。 - 天生对象: 接下来每一个令牌都邑被转换成定义其属性和划定规矩的对象(这个对象就是节点对象)
- 构建终了: DOM树构建完成,全部对象鸠合就像是一棵树形构造。能够有人会迷惑为何DOM是一个树形构造,这是由于标签之间含有庞杂的父子关联,树形构造恰好能够诠释这个关联(CSSOS同理,层叠款式也含有父子关联。比方: div p {font-size: 18px},会先寻觅一切p标签并推断它的父标签是不是为div以后才会决议要不要采纳这个款式举行衬着)。
全部DOM树的构建历程实在就是: 字节 -> 字符 -> 令牌 -> 节点对象 -> 对象模子,
下面将经由过程一个示例HTML代码与配图更抽象地诠释这个历程。
Hello web performance students!
当上述HTML代码碰见标签时,浏览器会发送要求取得该标签中标记的CSS文件(运用内联CSS能够省略要求的步骤进步速率,但没有必要为了这点速率而丧失了模块化与可维护性),style.css中的内容以下:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
浏览器取得外部CSS文件的数据后,就会像构建DOM树一样最先构建CSSOM树,这个历程没有什么迥殊的差异。
3、构建衬着树
在构建了DOM树和CSSOM树以后,浏览器只是具有了两个相互自力的对象鸠合,DOM树形貌了文档的构造与内容,CSSOM树则形貌了对文档运用的款式划定规矩,想要衬着出页面,就须要将DOM树与CSSOM树连系在一同,这就是衬着树。
- 浏览器会先从DOM树的根节点最先遍历每一个可见节点(不可见的节点天然就没必要衬着到页面了,不可见的节点还包含被CSS设置了display: none属性的节点,值得注重的是visibility: hidden属性并不算是不可见属性,它的语义是隐蔽元素,但元素依旧占有着规划空间,所以它会被衬着成一个空框)
- 对每一个可见节点,找到其适配的CSS款式划定规矩并运用。
- 衬着树构建完成,每一个节点都是可见节点而且都含有其内容和对应划定规矩的款式。
4、规划与绘制
CSS采纳了一种叫做盒子模子的头脑模子来示意每一个节点与其他元素之间的间隔,盒子模子包含外边距(Margin),内边距(Padding),边框(Border),内容(Content)。页面中的每一个标签实在都是一个个盒子
规划阶段会从衬着树的根节点最先遍历,然后肯定每一个节点对象在页面上的确实大小与位置,规划阶段的输出是一个盒子模子,它会精确地捕捉每一个元素在屏幕内的确实位置与大小,一切相对的测量值也都邑被转换为屏幕内的相对像素值。
当Layout规划事宜完成后,浏览器会马上发出Paint Setup与Paint事宜,最先将衬着树绘制成像素,绘制所需的时刻跟CSS款式的庞杂度成正比,绘制完成后,用户就能够看到页面的终究显现结果了。
我们对一个网页发送要求并取得衬着后的页面能够也就经由了1~2秒,但浏览器实在已做了上述所讲的异常多的事情,总结一下浏览器症结衬着途径的全部历程:
- 处置惩罚HTML标记数据并天生DOM树。
- 处置惩罚CSS标记数据并天生CSSOM树。
- 将DOM树与CSSOM树兼并在一同天生衬着树。
- 遍历衬着树最先规划,盘算每一个节点的位置信息。
- 将每一个节点绘制到屏幕。
5、外部资本是怎样要求的
为了直观的视察浏览器加载和衬着的细节,当地用nodejs搭建一个简朴的HTTP Server。
index.js
const http = require('http');
const fs = require('fs');
const hostname = '127.0.0.1';
const port = 8080;
http.createServer((req, res) => {
if (req.url == '/a.js') {
fs.readFile('a.js', 'utf-8', function (err, data) {
res.writeHead(200, {'Content-Type': 'text/plain'});
setTimeout(function () {
res.write(data);
res.end()
}, 5000)
})
} else if (req.url == '/b.js') {
fs.readFile('b.js', 'utf-8', function (err, data) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(data);
res.end()
})
} else if (req.url == '/style.css') {
fs.readFile('style.css', 'utf-8', function (err, data) {
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(data);
res.end()
})
} else if (req.url == '/index.html') {
fs.readFile('index.html', 'utf-8', function (err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end()
})
}
}).listen(port, hostname, () => {
console.log('Server running at ' + hostname + ':' + port);
});
index.html
没有 defer 或 async,浏览器会马上加载并实行指定的剧本,也就是说不守候后续载入的文档元素,读到就加载并实行。
async 属性示意异步实行引入的 Javascript,与 defer 的区分在于,假如已加载好,就会最先实行——不管现在是 HTML 剖析阶段照样 DOMContentLoaded 触发以后。须要注重的是,这类体式格局加载的 Javascript 依旧会壅塞 load 事宜。换句话说,async-script 能够在 DOMContentLoaded 触发之前或以后实行,但一定在 load 触发之前实行。
defer 属性示意耽误实行引入的 Javascript,即这段 Javascript 加载时 HTML 并未住手剖析,这两个历程是并行的。全部 document 剖析终了且 defer-script 也加载完成以后(这两件事变的递次无关),会实行一切由 defer-script 加载的 Javascript 代码,然后触发 DOMContentLoaded 事宜。
defer 与比拟一般 script,有两点区分:
- 载入 Javascript 文件时不壅塞 HTML 的剖析,实行阶段被放到 HTML 标签剖析完成以后。
- 在加载多个JS剧本的时刻,async是无递次的加载,而defer是有递次的加载。
8、css文件的影响
服务端将style.css
的响应也设置耽误。
fs.readFile('style.css', 'utf-8', function (err, data) {
res.writeHead(200, {'Content-Type': 'text/css'});
setTimeout(function () {
res.write(data);
res.end()
}, 5000)
})
222222
3333333
能够看出来,css文件不会壅塞HTML剖析,然则会壅塞衬着,致使css文件未下载完成之前已剖析好html也没法先显现出来。
我们把css调解到尾部
222222
3333333
这是页面能够衬着了,然则没有款式。直到css加载完成
以上我们能够简朴总结。
- CSS 放在 head 中会壅塞页面的衬着(页面的衬着会比及 css 加载完成)
- CSS 壅塞 JS 的实行 (由于 GUI 线程和 JS 线程是互斥的,由于有能够 JS 会操纵 CSS)
- CSS 不壅塞外部剧本的加载(不壅塞 JS 的加载,但壅塞 JS 的实行,由于浏览器都邑有预先扫描器)
参考
浏览器衬着历程与机能优化
聊聊浏览器的衬着机制
你不知道的浏览器页面衬着机制