浏览器工作原理学习笔记
单进程问题
- 不稳定,插件崩溃浏览器就崩溃
- 不流畅,脚本执行会让页面卡顿,内存泄漏也会导致浏览器变慢
- 不安全,恶意插件和恶意脚本容易获取系统权限作恶
多进程优点
- 进程隔离,插件或者页面崩溃不会导致其他页面崩溃
- 页面隔离,即使 js 阻塞了渲染进程,影响到的也只是当前的渲染页面,而并不会影响浏览器和其他页面,其他页面的脚本是运行在它们自己的渲染进程中的,浏览器也可以正常使用。
- 通过安全沙盒解决安全问题
五个常见进程
rel="noopener noreferrer"
浏览器渲染机制
- DOM:渲染引擎解析 html 文档,构建 DOM Tree
- Computed Style:解析对应的 css 样式信息,生成 document.styleSheets,计算 DOM 样式
- 布局(reflow/layout):计算布局信息,生成布局树 LayoutTree
- 分层(layer):对布局树进行分层。满足以下条件之一就会提升为单独图层:有层叠上下文属性,或者需要裁剪(超出/滚动)。
- 绘制(repaint/paint):遍历渲染树,绘制每个节点,把每一个元素对应的盒变成位图。
重排、重绘、合成
- 重排(reflow):当渲染树中的部分因为元素的尺寸、布局、隐藏等改变而需要重新构建。触发条件:页面渲染初始化(无法避免)、添加或删除 DOM、DOM 位置或尺寸的改变、浏览器窗口尺寸的变化、填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽度和高度的改变。
- 重绘(repaint):改变元素外观所触发的行为,浏览器会根据元素的新属性重新绘制,省去了布局和分层阶段。比如,仅修改 DOM 元素的字体颜色。
- 合成:既不改布局也不需要绘制,比如 css transform 动画,避开重排与重绘阶段,在非主线程上执行合成动画操作。
- display: none; 会发生 reflow,而 visibility: hidden; 只会触发 repaint
如何避免重排重绘
- 通过 class 批量修改样式
- 使用离线 DOM 或者 documentFragment
- 避免使用 table 布局
- 对类似 window resize 这样的事件做 debounce
- 对 dom 属性的读写分离
js 和 CSS 阻塞
- JS:会先下载并执行,再继续渲染,也就是说,下载和执行,都会阻塞 DOM 解析,因为 JS 可能存在 DOM 操作
- CSS:正常不会阻塞 DOM 解析,但是遇到 script 标签会触发渲染,会等到 CSS 下载完成再继续执行,这种情况下会阻塞 DOM 解析。
五个常驻线程
JS Runtime 是单线程,但是浏览器提供多线程环境
- GUI 渲染线程:负责解析html,CSS,构建DOM树,布局和绘制,与 JS 引擎线程互斥
- JS 引擎线程:负责处理 JS 脚本,执行会阻塞渲染线程
- 定时器触发线程:负责执行定时器一类函数的进程,如 setTimeout、setInterval。主线程执行代码遇到计时器时,会将计时器交给该线程处理,当计时完毕之后,定时器线程会将计时完毕后的事件加入到事件队列的尾部,等待 JS 引擎线程的执行
- 事件触发线程:主要负责将准备好执行的事件交给 JS 引擎线程执行,如计时器计时完毕后的事件,AJAX 请求成功返回并触发的回调函数和用户触发点击事件时,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程的执行
- 异步 HTTP 请求线程:当主线程依次执行代码时,遇到异步请求,会将函数交给改线程处理,当监听状态码变更时,如果有回调函数,会将回调函数加入到任务队列的尾部,等待 JS 引擎线程的执行
豌豆资源搜索网站https://55wd.com 广州vi设计公司http://www.maiqicn.com
Event Loop
主线程从任务队列读取事件这个过程,是循环不断的,称之为 Event Loop。
- 执行栈里清空了,才会从任务队列中取任务,浏览器不止一个任务队列(Task Queue 和 Microtask Queue)
- setTimeout 是浏览器行为,和 DOM 事件一样
- 宏观任务一个一个执行,微任务一批一批执行
异步任务有两种:
- 宏任务(Macrotask):宿主发起的任务,包括整体代码 script,setTimeout,setInterval、setImmediate、I/O操作、UI rendering
- 微任务(Microtask):JavaScript 引擎发起的任务,process.nextTick, Promises, Object.observe, MutationObserver
事件模型
DOM 事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。
这种传播分成三个阶段:
- 捕获阶段:事件从 window 对象自上而下向目标节点传播的阶段
- 目标阶段:真正的目标节点正在处理事件的阶段
- 冒泡阶段:事件从目标节点自下而上向 window 对象传播的阶段
在实际监听事件时,可以这样使用冒泡和捕获机制:默认使用冒泡模式;当开发组件时,遇到需要父元素控制子元素的行为,可以使用捕获机制。
豌豆资源搜索网站https://55wd.com 广州vi设计公司http://www.maiqicn.com
事件代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,通过 event.target 来判断。这种方法叫做事件的代理(delegation)。
优点:
- 减少内存消耗,提高性能
- 方便动态绑定事件