www.ruanyifeng.com/blog/2014/1…javascript
www.zhihu.com/question/64…html
www.alloyteam.com/2016/05/jav…java
juejin.im/post/59e85e…canvas
首先明确一下两个概念多线程
代码写的怎么样,页面性能如何的直观感受是页面生成的快不快,这个与浏览器的渲染进程息息相关。下面先梳理一下异步
setInterval
与setTimeout
所在的线程setTimeout(fn,ms)
指定某个任务在主线程最先可得的空闲时间执行,ms
秒以后将fn
函数加入到队列中setTimeout
中低于4ms
的时间间隔算为4ms
。因此setTimeout
的延时设置为0
也不可能瞬发因为JavaScript是可操纵DOM的,若是在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程先后得到的元素数据就可能不一致了。 所以为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,GUI更新则会被保存在一个队列中等到JS引擎线程空闲时当即被执行。ide
页面的解析工做是在 Renderer 进程中进行的,Renderer 进程经过在主线程中持有的 Blink 实例边接收边解析 HTML 内容。每次从网络缓冲区中读取 8KB 之内的数据。浏览器自上而下逐行解析 HTML 内容,通过词法分析、语法分析,构建 DOM 树。当遇到外部 CSS 连接时,主线程调使用网络请求板块异步获取资源,不阻塞而继续构建 DOM 树。当 CSS 下载完毕后,主线程在合适的时机解析 CSS 内容,通过词法分析、语法分析,构建 CSSOM 树。浏览器结合 DOM 树和 CSSOM 树构建 Render 树,并计算布局属性,每一个 Node 的几何属性和在坐标系中的位置,最后进行绘制展示在屏幕上。当遇到外部 JS 连接时,主线程调使用网络请求板块异步获取资源,由于 JS 可能会修改 DOM 树和 CSSOM 树而形成回流和重绘,此时 DOM 树的构建是处于阻塞状态的。但主线程并不会挂起,浏览器会用一个轻量级的扫描器去发现后续须要下载的外部资源,提早发起网络请求,而脚本内部的资源不会识别,比方 document.write。当 JS 下载完毕后,浏览器调使用 V8 引擎在 Script Streamer 线程中解析、编译 JS 内容,并在主线程中执行。
当 DOM 树构建完毕后,还需通过好几回转换,它们有多种中间表示。首先计算布局、绘图样式,转换为 RenderObject 树(也叫 Render 树)。再转换为 RenderLayer 树,当 RenderObject 拥有同一个坐标系(比方 canvas、absolute)时,它们会合并为一个 RenderLayer,这一步由 CPU 负责合成。接着转换为 GraphicsLayer 树,当 RenderLayer 知足合成层条件(比方 transform,熟知的硬件加速)时,会有本身的 GraphicsLayer,不然与父节点合并,这一步一样由 CPU 负责合成。最后,每一个 GraphicsLayer 都有一个 GraphicsContext 对象,负责将层绘制成位图做为纹理上传给 GPU,由 GPU 负责合成多个纹理,最终显示在屏幕上。 另外,为了提高渲染性能效率,浏览器会有专使用的 Compositor 线程来负责层合成,同时负责解决部分交互事件(比方滚动、触摸),直接响应 UI 升级而不阻塞主线程。主线程把 RenderLayer 树同步给 Compositor 线程,由它开启多个 Rasterizer 线程,进行光栅化解决,在可视区域以瓦片为单位把顶点数据转换为片元,最后交付给 GPU 进行最终合成渲染。
首先要将JavaScript分红同步任务和异步任务,其次是引入宏任务(macro-task)和微任务(micro-task)
下面用一副导图来描述一下同步任务与异步任务的执行
task->渲染->task->...
)console.log(1);
setTimeout(function(){
console.log(2);
},1000);
console.log(3);
var p = new Promise(function(resolve,reject){
setTimeout(function(){
console.log(4);
},500);
resolve();
}).then(function(){
console.log(5);
});
console.log(6);
p.then(function(){
console.log(7);
});
console.log(8)
// 1 3 6 8 5 7 4 2
复制代码
第一次宏任务循环: 执行的整段代码看作一个宏任务
console.log(1)
打印1setTimeout()
交给定时器处理线程处理,1秒延时后将任务加入宏任务队列 记为 s1console.log(3)
打印3Promise
,执行里面的回调函数setTimeout()
交给定时器处理线程处理,0.5秒延时后将任务加入宏任务队列 记为s2then
中的回调函数加入此次的微任务队列 then1
Promise
后,执行console.log(6)
,打印6p.then
,把回调函数加入微任务队列 then2
console.log(8)
打印 8 此时打印的是 1 3 6 8
宏任务队列:s1
,s2
微任务队列:then1
,then2
第一次宏任务执行结束以后执行此次产生的微任务,依次执行then1
,then2
,依次打印 5
和8
第二次宏任务开始 这里须要注意的是s1
和s2
触发计时事件,是在Event Table
中注册,等待计时完毕以后把回调函数加入Event Queue
中,因此 s2
的0.5
秒比s1
的1
秒要先完成,这样在Event Queue
中先放入s2
,再放入s1
第二次宏任务执行s2
,打印4
,没有微任务 第三次宏任务执行s1
,打印2
,没有微任务
一些定时事件、点击事件、网络加载都是交给浏览器的API处理的,这些地方会建立宏任务,并且并非直接加入到队列中,而是有一个EventTable来记录这些事件,等到条件知足以后再将回调的函数注册到Event Queue中,每次宏任务从 Event Queue中获取下一个宏任务