参考:https://www.cnblogs.com/caiyy/p/10406934.html
GUI和Web软件
1、GUI是通过 代码打包下载 安装到如手机上进行访问的过程
2、Web端的软件是通过 发布到server或cdn上,网页通过增量式访问获取服务的
浏览器多进程
包含一个主进程(包含网络渲染,页面管理),插件进程,GPU(3d)渲染进程(可选),页面渲染进程(js处理,页面渲染,事件处理)
渲染进程:
1、broswer主进程 进行浏览器的页面资源管理 创建和销毁其他进程,页面交互,将位图合并(布局渲染树)绘制到页面上
2、GPU进程,硬件加速 只有一个
3、插件进程 每个类型的插件对应一个进程
4、渲染进程 每个tab页一个进程,负责页面渲染 脚本执行 事件触发,任务队列轮询等
——JS引擎线程(V8引擎):负责js脚本解析,代码运行 与GUI互斥
——GUI渲染线程: 解析html和css,负责布局和绘制,与js引擎线程互斥,这是由于js可以操作dom,防止渲染前后不一致
——事件触发线程:归属于浏览器,用于控制事件循环,当事件被触发该线程会将对应的回调函数放到任务队列当中
——定时触发器线程:setTimeout setTnterval W3C中规定setTimeout低于4ms算4ms 回调函数放入任务队列
——异步http请求线程:监测状态变更时产生状态变更事件,放入任务队列
——任务队列轮询线程:轮询监听任务队列是否为空
webWorker和shareWorker
js是单线程的 当有大量运算会造成页面渲染卡顿,为了避免可以申请一个web worker。 js引擎向浏览器申请开一个子线程(不可造作dom),js引擎线程和worker线程通过特定的方式通信,计算出结果后通信给js引擎主线程。
webWorker是某个页面render下的一个线程,不会和其他页面的render进程共享
shareWorder是浏览器所有页面共享的,是一个单独的进程
GUI渲染
网页从白屏到内容显示的时间就是HTML 文档加载和解析的时间。也就是DOMContentLoaded 事件触发之前所经历的时间。
js脚本参数设置: https://blog.csdn.net/zyj0209/article/details/79698430
由js引擎来解析html生成dom树,css解析生成cssdom树,js脚本执行可以设置参数默认是
1、同步sync,当执行html的过程中遇到js脚本,会停止解析html,先去加载执行js脚本完毕后继续解析html
2、异步async,当执行html遇到js,会同时下载js脚本,如果js先加载完就停止解析html先执行js脚本,然后解析html或者是这个时候html已经加载完毕,那么直接执行js,不管是哪一种,DOMContentLoaded都会在html解析完触发
3、延后dsync,遇到js脚本会进行下载,脚本需要等待html解析完才会执行,DOMContentLoaded会在js脚本执行完了被触发
dom树和cssdom树结合为渲染树render-tree,从根节点递归调用计算每一个元素的大小,位置等,给出每个节点在屏幕上精确的坐标,这就是基于渲染树的布局渲染树,之后交给主进程进行渲染树的绘制。
资源阻塞机制
DOMContentLoaded和load分别标识着dom加载完成和dom&&css&&js加载完成
GUI线程中html和css解析是并行的,css不会阻塞html的解析但是会阻塞页面渲染,所有放在head中尽量早解析
js根据需要不同可以放在不同的位置,初始化放在head,操作dom放在body末尾或者使用load事件
回流和重绘
render树中的部分或全部因为元素的尺寸、布局、隐藏等改变需要重新构建称为回流,如
1、页面初始化、调整窗口,改变字体,内容变化、操作dom、操作css、激活css伪类、操作class属性、设置style属性、增加或者移除样式表
2、如何防止回流:减少逐项修改style最好一次定义在class中更新、避免循环操作dom、到要频繁的获取offset属性时,不重复读取,将复杂的元素绝对定位或者固定定位,使用GPU硬件加速创建一个新的复合图层
图层:dom中的每个节点对应一个简单图层,复合图层是对简单图层的合并,absolute,fixed布局会脱离文档流,但是还在当前的复合图层中,会影响重绘,但是回流不影响。
当使用硬件加速的时候会生成新的复合图层,互不影响,设置transform: translate3d(0,0,0) 或 translateZ(0),更多内容参考://www.greatytc.com/p/f8b1d6e598db
proload和prefetch
详情:https://www.cnblogs.com/xiaohuochai/p/9183874.html
proload提升资源的优先级,提升到跟设置了as属性的同一优先级,as属性设置资源的优先级,不设置会默认为异步请求的优先级,很低。不管资源是否需要都会被加载,并且有回调函数。设置方式<link rel="preload" href="..." as="..." onload="preloadFinished()">
prefetch是预先加载下一页可能用到的资源,不可混用。
事件循环机制
更多:https://juejin.im/post/5ec73026f265da76da29cb25
- 事件循环机制的核心是事件触发线程,js执行栈的过程中触发异步任务,或者是微任务,将异步任务交给相关的线程,将微任务放到微任务队列;
- 当执行栈执行完毕后去查看微任务,执行当次产生的微任务,这个时候如果有微任务添加进来会继续执行微任务直到js执行栈为空才去渲染;
- 同时异步任务完成后会将回调函数放到任务队列(宏任务)当中,当执行栈结束&&微任务结束,进入渲染阶段
1、判断是否需要重渲染,一般涉及到屏幕刷新率、页面性能、程序是否在后台执行,一般屏幕刷新率为60Hz,,如果页面性能过低,为了保证稳定的刷新率会选择30Hz,当在一次刷新帧内发生多次动画行为,并不会真实的渲染到页面,而会被浏览器收集起来一次执行;如果程序在后台执行会降低刷新率到4hz甚至更低;如果浏览器认为渲染不会引起页面视觉上的变化;如果帧动画回调函数为空(可以通过requestAnimationFrame函数触发);满足以上条件不进行渲染
2、如果判断需要渲染则进行渲染否则直接开始进行后续代码的执行,也是在这里进行resize scroll的触发,这里浏览器会保存一个,目标对象,等到这里派发事件到目标上的时候驱动目标对象的回调函数。触发帧动画回调(requAnimationFrame);执行IntersectionObserver回调;绘制页面
;判断宏任务和微任务队列为空,执行空闲周期算法判断是否要执行requestIdleCallback的回调函数 - 开启下一轮的事件循环,从宏任务队列获取新的任务放到执行栈执行
宏任务 macro-task:script整体代码、setTimeout、setInterval、setImmediate、I/O、UIrendering
微任务 micro-task:process、nextTick、Promises、Object.observe、MutationObserver
注:
- async await 最终的结果是返回了promise,在await后跟随的语句是同步的,下一行开始的语句是异步的 等同于then后面的微任务回调
- 对于任务队列中的任务,并不是只有一个,对于用户输入的任务如鼠标键盘事件将会优先于其他的task,浏览器在保证顺序的前提下将会分配给用户事件4/3的优先权
- requestAnimationFrame函数是在页面重新渲染之前的最后一步调用,很可能在宏任务之后不进行调用
- requestIdleCallback(fn, deadline)函数是浏览器提供的空闲调度算法,将一些计算量大而且又不紧急的任务放到空闲时间去执行,每次使用的时候要去调用timeRemaining()函数获取deadline来判断是否有空余时间可以使用,如果有更高优先级的任务出现则将在没有渲染任务的时候则会动态的将剩余时间设置为0;需要注意的是每次当浏览器是空闲的也会有deadline为50ms这是为了应对用户的交互操作发生时,确保用户在无感知的延迟下得到回应
例题:https://blog.csdn.net/weixin_34176694/article/details/91400057
浏览器渲染之前做了什么
更多:https://juejin.im/post/5e6394b4e51d4526e32c3cef
需要注意的是,每次渲染的的条件是js的执行栈为空,也就是触发了requestAnimationFrame()方法,所以在执行代码的时候,如
document.addElementListener('click', function(){console.log(1, promise.resolve().then(() =>{console.log(2)}))})
document.addElementListener('click', function(){console.log(3, promise.resolve().then(() =>{console.log(4)}))})
在代码中调用,会在js执行栈中加载一个脚本文件,第一个监听器触发后,脚本文件scripts还在所以这个时候不能去执行微任务,还是继续执行宏任务,执行完后退出js堆栈,这时才去清空微任务,结果:1=>3=>2=>4
如果是在页面上点击触发,没有初始的js脚本,则会在第一个监听触发后判断堆栈为空去执行微任务,结果为1=>2=>3=>4
这里有一个图,三个环,中间是事件循环,左边是其他的事件(宏任务、微任务),右边是浏览器渲染,在左边完成后,会按照顺序推到中间的js执行栈中,在js执行栈为空的时候触发requestAnimationFrame()去进行页面渲染,如下代码,微任务执行后继续添加了新的任务到js栈中,会循环执行
function loop(){promise.resolve().then(loop)}
loop()
setTimeout(() => {
console.log("sto")
requestAnimationFrame(() => console.log("rAF"))
})
setTimeout(() => {
console.log("sto")
requestAnimationFrame(() => console.log("rAF"))
})
如果用setTimeout循环来进行动画的循环处理,会比requestAnimationFrame()执行的次数多,这是因为浏览器一般为60Hz也就是一秒最多渲染60次,这里是由于人眼的感知所决定,60Hz已经是较为流畅的实现,当然与硬件显卡之类的设备也有关系,硬件决定上限,性能平衡决定结果;所以setTimeout最多可以达到浏览器的阈值,当然多余的调用就被浪费了,同时可能会因为在每一个动画帧调用次数不同而出现漂移,也可能因为在没有匹配到动画帧而丢失渲染,不可控,如上面的代码第二段,浏览器期待在定时器之间不穿插渲染,所以会将两次渲染合并到一起,结果是:sto sto rAF rAF
而requestAnimationFrame是在每一次渲染之前调用,更加科学的用于动画的处理,有些人会把requestAnimationFrame方法归类为和setTimeout一样都属于宏任务队列,但是实际上来说与宏任务和微任务无关它只和浏览器渲染相关,在浏览器刷新前执行