事件循环
JS异步
1、JS是一门单线程语言, 运行在浏览器渲染主线程中,且渲染主线程只有一个(渲染主线程承担着诸多工作如:页面渲染,JS执行等)
2、如果同步执行可能会造成主线程阻塞,从而导致消息队列中其他任务无法得到执行,(影响: 1、浪费主线程时间; 2、页面无法及时更新造成页面卡顿)
3、具体做法; 当某些任务发生时(计时器、网络、事件监听)主线程将把任务分配给其他线程,自身结束任务执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务加装在消息队列末尾,等待主线程执行
JS为何阻碍渲染
js和页面渲染都在同一主线程上
任务优先级
1、任务无优先级,先进先出
2、消息队列存在优先级
2.1 每一个任务都有一个类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属不同队列
2.2线程只有一个,队列可以有多条
2.3在一次任务循序中,浏览器可根据实际情况从不同队列取出任务执行
2.4浏览器必须存在一个微队列,微队列最先执行,优先其他任务执行
2.5chrome
2.5.1延时队列 优先级(中)
2.5.2交互队列 (高)
2.5.3微队列 (最高)
2.6添加微队列方式
2.6.1 Promise
函数添加到微队列
Promise.resolve().then(函数)
2.6.2 MutationObserver
JS事件循环
1、事件循环又称为消息循环, 是浏览器渲染主线程的工作方式
2、在源码中会开启一个不会结束的for循环,每一次循环都会从消息队列中取出一个任务执行,其他线程只需要在是当时间将任务添加到队列末尾即可
3、过去只存在宏队列和微队列,目前已经无法满足浏览器环境
4、目前采取更加灵活的方式处理,每个任务有不同的类型,同类型的任务在同一个队列,不同任务在不同队列。不同任务队列存在不同优先级,在事件循环中,由浏览器自行决定优先队列,
5、浏览器必须要存在一个微队列,且微队列任务具有最高优先级,需优先执行
JS为什么不能精确计时
1、计算硬件无原子钟,无法精确计时
2、计时函数本身就存在偏差,JS计时器最终调用的是操作系统的函数
3、W3C标准,当嵌套层级超过5层会存在4毫秒的最少时间
4、事件循环造成计时函数只能在主线程空闲时运行
浏览器渲染原理
渲染(render)
html字符串 --> 像素信息
渲染时间点
1、网络线程收到HTML后产生一个渲染任务,并将其传递给渲染主线程的消息队列中,在事件循环机制作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程
2、渲染流水线: HTML字符串–>( 解析HTML–>样式计算–>布局–>分层–>绘制–>分块–>光栅化–>画) --> 像素信息 (每一个阶段都会有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入形成一套严密的生产线)
2.1解析html (Parse HTML)转换对象结构
DOM树
CSSOM树 css object model
为提高解析效率,浏览器在解析开始前,会启动一个预解析线程率先下载外部的CSS文件和JS文件,
在主线程解析到link位置时,若外部CSS文件还未下载解析完成,主线程也不会等待,会继续执行后续解析HTML,为此CSS代码不会阻碍HTML解析
在主线程解析到srcipt位置时, 会停止解析,转而等待JS下载好后,并将全局代码解析执行完成后再继续解析HTML,(原因:在JS代码执行过程中可能会修改DOM树)JS阻塞HTML解析的根本原因
第一步完成后会生成一个DOM树和CSSOM树
2.2样式计算 (Recalculate Style)计算宽高等样式
遍历dom树,依次为每个节点计算出它的最终样式(称为Computed Style)
在这过程中预设值会变更为绝对值(如red => rgb(255,0,0));相对单位变绝对单位如(em => px)
这步完成后会得到一个带样式的DOM树
补充点:css知识点
csss属性值的计算过程(层叠、基层)
视觉格式化模型(盒模型、包含块)
2.3布局 (Layout)
DOM树变更为Layout树(布局树)
DOM树和Layout树不一定是一一对应的 内容必须在行盒中(Layout树中元素需带有几何信息;盒子的类型由CSS来决定,不由HTML便签来决定,HTML只提供语义化;行号和块盒不能相邻,需要匿名行盒、匿名块盒去隔绝元素)
依次遍历DOM树的每一个节点,计算节点的几何信息(如节点宽高、相对包含块位置)
display: none 的节点无几何信息不会生成布局树;
伪元素选择器虽然在DOM树中不存在,但存在几何信息,也会生成在布局树中
补充知识点: 包含块
2.4分层 (Layer)
堆积上下文有关的属性都会影响分层结果 如z-index
will-change: transform 可以很大程度影响分层结果
好处:在某一层改变后,仅会对该层进行后续处理,从而提高效率
2.5绘制 (Paint)
主线程为每一个层单独产生绘制指令用于每一个层的内容该如何画出来
渲染主线程工作到此为止,剩余交给其他线程完成
2.6分块 (Tiling)
完成绘制后主线程将每个线程的绘制信息交给合成线程,剩余工作由多个合成线程完成
合成线程将每一层分成小的区域,从线程池中拿取多个线程完成分块工作
2.7 光栅化(Raster)
将每个块变成位图(像素点信息)
优先处理高级视口的块
此过程会通过GPU加速,提升运算速度
2.8画 (Draw)
合成线程拿到每个层、每个块的位图后,生成一个指引(quad)信息;指引会标识出每个位图应该画到屏幕的哪个位置,以及旋转,缩放等变形;变形发生在合成线程,与渲染线程无关(transform效率高的本质原因)
合成线程计算出每个位图在屏幕上的位置,交给GPU进行最终呈现
Why 合成线程需要交给GPU中转
合成线程和渲染主线程在渲染进程中,渲染进程是沙盒形式存在(安全机制),隔离了硬件, 而找GPU的过程称为系统调用,渲染进程不能直接操作硬件,需要GPU通知硬件
3、什么是reflow(重新布局)?
本质重新计算Layout树
当进行影响布局树的操作后,需要重新计算树,会引发layout
为避免连续多次操作导致布局树反复计算,看浏览器会合并这些操作,当JS代码全部完成后再统一进行计算,为此改动属性造成的reflow是异步的
当JS获取布局属性时,若reflow还是异步存在,则会拿取不到最新数据,为此浏览器决定在读取时,同步执行reflow
4、什么是repaint(重绘)?
本质就是重新更据分层信息计算绘制指令
当改变可见样式后,就会重新计算,引发repaint
由于元素的布局信息也属于可见样式,所有reflow一定会引起repaint
5、为什么transform效率高?
transform既不会影响布局也不会影响绘制指令,它影响的知识渲染流程中最后一个draw阶段
draw在合成线程中,所有transform的变化几乎不会影响主进程,反而渲染进程如何忙碌也不会影响transform变化
Vue执行
function observe(obj) {
for(const key in obj) {
let internalValue = obj[key];
let funcs = [];
Object.defineProperty(obj, key, {
get: function() {
// 依赖收集:记录哪个函数在用
// 将函数交给全局变量去执行
if(window.__func && !funcs.includes(window.__func)){
funcs.add(window.__func)
}
return internalValue;
}
set: function(val) {
internalValue = val;
// 派发更新,运行: 执行我的函数
for(var i = 0; i < funcs.length; i++){
funcs[i]();
}
}
})
}
}
function autorun(fn){
window.__func = fn;
fn();
window.__func = null;
}
Vue常用优化手段
1、使用key:v-for循环列表时 应给每个列表一个稳定且唯一的key值 下标不适合用于key值(唯一不稳定)
2、使用冻结对象:去除对象响应式
3、函数式组件:无data, 纯渲染, 不会为函数组件创建实例
4、使用计算数据: 可以缓存, 跟随依赖变化而变化
5、非实时绑定的表单项: v-model.lazy
6、保持对象引用稳定
7、v-show代替v-if
8、延迟装载: 利用requestAnimationFrame事件分批渲染内容
const refreshFrameCount = () => {
requestAnimationFrame(() => {
// 判断条件递归调用
// refreshFrameCount()
})
}
9、使用keep-alive
10、长列表优化
11、打包体积优化