likes
comments
collection
share

浏览器的底层运行机制

作者站长头像
站长
· 阅读数 94

首先,按照惯例,依旧是一波小小的复习

一、复习

1.进程和线程

  • 进程:指一个程序(或浏览器打开一个页面就是开辟一个进程)

  • 线程:程序中具体执行任务的一个进程中可能包含一到多个线程!!

    • 单线程:同时只能处理一件事,上一件事处理完,下一件事才能处理「同步编程」

    • 多线程:可同时处理多件事情,上一件事情即便没有处理完,下一件事也可以交给其他线程去处理「异步编程」

2.浏览器的渲染进程

浏览器打开一个页面就是开辟一个进程,每个进程中会包含多个线程「浏览器是多线程的」!!

  • GUI渲染线程:渲染解析HTML/CSS代码,并且绘制页面 =>W3C

  • JS引擎线程:渲染解析JS代码 =>ECMAScript「ECMA」

  • 定时器监听线程:设置定时器后,浏览器就会分配一个线程(小人)去监听定时器是否到时间

  • 事件监听线程:完成事件绑定后,也会分配一个线程去监听事件是否触发

  • HTTP网络线程:分配一个线程,去服务器获取相关的资源信息 「并发上限:同源下最多开辟5~7个」

  • ...

接下来正式开始!

二、浏览器底层渲染机制

当基于HTTP网络,从服务器端获取到页面源代码后,到浏览器把其绘制成页面,中间所做的事情,就是我们要研究的渲染机制!

1. 创建DOM TREE(DOM树)

浏览器的底层运行机制 GUI首先渲染所有的HTML结构,计算出结构之间的层级关系 触发DOMContentLoaded事件:window.addEventListener('DOMContentLoaded',function(){...})

2. 创建CSSOM TREE(样式树)

浏览器的底层运行机制

样式树:渲染解析CSS完毕后,GUI按照原先导入的顺序,依次解析CSS代码,记录了相应节点应该具备的样式「包括继承的样式、或者根据优先级重构的样式等等」

3. 生成RENDER TREE「渲染树」

把DOM TREE 和 CSSOM TREE 合并在一起生成RENDER TREE

渲染树中详细记录了DOM的层级结构和每个节点应该具备的样式!!

4. Layout布局

根据可视窗口大小,按照RENDER TREE,计算出每个节点在视口中的位置和排版等「含:位置、方向、大小、排列方式...」

5. 分层

规划出页面各个文档流(层面)中,所有节点的具体绘制步骤!!

6. Painting绘制

GUI根据上一步计算出来的步骤,开始绘制页面即可!!

触发load事件:window.onload=function(){...}

三、渲染页面流程的步骤

  • 打开页面,浏览器首先分配一个“HTTP线程”去服务器获取HTML资源;

  • 获取资源后,分配“GUI渲染线程”自上而下,逐行渲染解析HTML代码!

    • 渲染HTML/CSS代码:遵循W3C规则,GUI渲染线程去处理

    • 渲染JS代码:遵循ECMAScript(ECMA-262)规则,JS引擎线程去处理

    • 在渲染代码过程中,如果遇到 link/img/script[src=xxx]/audio/video 等,浏览器需要开辟“HTTP线程”,从服务器端获取到对应的资源文件(文件中的代码)

1.遇到<link>

  • 浏览器分配一个“HTTP线程”去服务器获取CSS样式

  • 同时GUI会继续向下渲染「异步」

  • 等待HTML渲染解析完毕「DOM TREE已经构建」,并且CSS样式“都已经”获取到了,再按照之前导入样式的顺序,逐一渲染解析CSS样式「创建CSSOM TREE」!

    • 目的:确保CSS优先级(层级)的准确,所以渲染顺序需要和之前编写的导入顺序一致

    • 如果某个资源一直没有响应,或者响应结果是获取失败,则忽略它的渲染!

1.1link三种 CSS 样式的渲染区别  

  • 渲染过程中遇到 则开辟新的HTTP线程去获取资源文件,GUI不受影响,继续向下渲染“异步” 【代码多的时候 用link】

  • 如果遇到的是这个标签,则无需获取资源,代码本身就有,此时GUI直接去渲染即可“同步” 【代码少的时候 最优】

  • 如果遇到的是@import ,也会开辟新的HTTP线程去获取资源,但是此时会阻碍GUI的渲染,只有当样式资源获取后,GUI才会渲染新拿到的样式代码 “同步” 【影响页面的渲染速度】

1.2优化技巧

  • 如果CSS样式“代码比较少”,我们直接使用内嵌式即可(尤其是移动端,我们经常这么干);

  • 但是如果代码比较多,还用内嵌式则会导致HTML请求速度都很慢,此时我们使用外链式;除特殊必要,不建议使用导入式;

2.遇到 <style>

  • 内嵌样式:获取HTML的时候,就把嵌入的样式也都获取到了!不需要再开辟HTTP线程,去服务器获取资源了!
  • 为了保证CSS渲染的顺序和样式优先级,此时GUI也不会立即渲染解析CSS代码,还是要等待DOM树构建完毕、其它CSS资源都获取到,再按照顺序进行解析!!
  • <style>方式确保:在DOM TREE构建完毕后,可以立即拿到样式代码去渲染解析!

3.遇到 import 导入式

  • 也会开辟新的HTTP线程去服务器获取资源,但是其“会阻碍GUI继续向下渲染”,也就是GUI此时停止工作;当把资源从服务器获取后,GUI才能继续向下渲染

  • 这种模式是同步的

  • 真实项目中,尽可能不要使用@import「除非在less/sass/stylus等预编译语言中」

4.遇到 img/audio/video

  • 开辟新的HTTP线程去获取相关资源,GUI继续向下渲染

  • 当浏览器去绘制页面的时候,如果资源已经获取,此时再去渲染

  • 图片懒加载:加快页面第一次渲染的速度

    • 不让图片资源占用有并发上限的HTTP线程

    • 第一次渲染页面,不去渲染耗时间的图片

5.遇到 <script src="">

  • 默认是同步的:默认会阻碍GUI的渲染:首先开辟“HTTP线程”去服务器获取JS资源,此时GUI停止渲染,等待资源获取完毕,“JS引擎线程”把代码都渲染解析好了,GUI才会继续渲染!!

5.1存在的问题

  • 如果把<script>放在HEAD中导入,则JS代码内是无法获取DOM元素的{原因:其阻碍GUI渲染,导致JS代码执行的时候,DOM TREE还未构建}

  • 所以我们一般都是把其放在BODY末尾导入,一方面不让其阻碍DOM TREE的构建,另一方面也是要等待DOM TREE构建完毕,我们可以在JS中获取DOM元素!

  • 当然也可以监听事件「DOMContentLoaded && load」,在事件中获取DOM元素

5.2依然放在HEAD中,还想能拿到DOM元素,该如何处理?

  • 把获取元素的代码放在window.onload事件中「等待所有资源渲染完毕后触发」

  • 还可以给<script>设置 async 或者 defer 属性

  • 设置了async属性:

    • 获取资源是异步的:浏览器分配HTTP线程获取JS资源,此时不阻碍GUI,GUI会继续渲染

    • 代码执行是同步的:当JS资源获取后,立即执行JS代码,阻碍GUI正在做的事情!

    • 特点:多个<script>设置async,最后也都是谁先获取先执行谁,没有所谓的顺序概念,一般用于不需要相互依赖的JS中!!

  • 设置defer属性:

    • <link>非常的类似,获取和渲染代码都是异步的。

    • 开辟HTTP线程去获取JS资源,此时GUI继续渲染,等待GUI渲染完毕「DOM TREE生成」,而要获取的JS资源也都拿到了,再按之前导入JS的顺序,依次渲染解析JS

    • 特点:多个<script>设置defer,它要等待所有资源都获取后,按照导入的先后顺序,依次渲染解析JS!!

总结

真实项目中,要不然就是把<script>放在末尾,要不然就是设置async/defer;在不考虑多个JS之间执行的先后顺序「也就是相互之间没有依赖」,可以设置async,谁先获取就先执行谁!反之,需要有顺序和依赖,则设置defer!

四、总结优化技巧

学习底层机制,目的是进行“前端性能优化”,写出更高质量的代码!!

优化思想:CRP「关键渲染路径」

  • 减少使用@import,<script>放在末尾或者设置async/defer让其变为异步处理,不让他们阻碍GUI干活!!

  • 使用语义化标签、层级结构不要过深!

  • CSS选择器前缀不要过长 「选择器是从右到左渲染的」

  • 样式代码较少的情况下,使用内嵌式!代码较多,采用外链式;坚决不用导入式!

  • CSS合并压缩为1个、JS也合并压缩为1个,减少HTTP请求的数量;

  • 对于图片等资源,做延迟加载「等待页面彻底渲染完毕,在去获取渲染」;
  • 减少DOM的回流(重排)和重绘!

  • ...

转载自:https://juejin.cn/post/7174080156422635551
评论
请登录