我们常常说JS是单线程的,但是这个JS单线程在整个浏览器的协作体系中处于什么样的位置?从线程角度看待为何css前置,JS后置以及JS的异步通知机制是怎样的。或许你能从本文中窥到一丝端倪…
进程与线程的概念
进程:进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位);
线程:线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)。
tips:①、我们常说的单线程和多线程都是指的在一个进程内;②、进程与进程之间保持相互独立,并行不悖.
浏览器的多进程
浏览器包含哪些进程
- Browser进程:浏览器的主进程(负责协调、主控),只有一个;
- 第三方插件进程:每种类型的插件对应一个进程;
- GPU进程:最多一个,用于3D绘制等;
- 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为页面渲染,脚本执行,事件处理等;
tips:浏览器每打开一个网页就相当于新增一个进程。
浏览器内核的渲染进程(多线程)
浏览器内核的常驻线程
- GUI渲染线程
- JS引擎线程
- 事件触发线程
- 定时触发器线程
- 异步http请求线程
tips:GUI线程与JS引擎线程是互斥的,这也是我们常常将css前置,js后置的原因。
浏览器的渲染流程
浏览器器内核拿到内容后,渲染大概可以划分成以下几个步骤:
- 解析html建立dom树;
- 解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树);
- 布局render树(Layout/reflow),负责各元素尺寸、位置的计算;
- 绘制render树(paint),绘制页面像素信息;
- 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。
默认复合图层与复合图层
普通文档流就是一个默认复合图层。(定位虽然会脱离文档流,但依然是在默认的复合图层中)
开启硬件加速的原理其实就是让浏览器再创建一个复合图层(不同的复合图层之间是相互独立地渲染,故而新建一个复合图层单独进行动画等处理就会提升性能)。
复合图层中的任何一点发生变化,GPU都会重新绘制整个复合图层。定位虽然能脱离普通文档流,但是他的变化还是会导致他所在的复合图层重新渲染。
硬件加速
开启硬件加速的条件:
- 3D或透视变换
- 对元素的opcity作动画
- 视频、webGL、flash等
- 如果这个元素添加了硬件加速,并且index层级比较低,那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),会默认变为复合层渲染,如果处理不当会极大的影响性能
tips:①、动画时的复合图层是临时的,只在动画进行时创建;②、上面提到的第四点隐藏着一个巨坑,即如果一个元素启用了硬件加速,并且他的z-index值比较小,那么他的兄弟元素也会被放入硬件加速的复合层中。而硬件加速的复合层中的元素过多,会导致内存与GPU吃紧,页面也不会如预期那般加速。所以在使用硬件加速的时候应给该元素设置一个较大的z-index,明确告诉浏览器这一层是独立出去的。
JS事件环机制
首先明确三个线程:
JS引擎线程、事件触发线程、定时器触发线程。
- 主线程运行时会产生执行栈;
- 栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕);
- 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调;
- 如此循环。
一些细节:调用setTimeout后,是如何等待特定时间后才添加到事件队列中的?这个计时并不是由JS引擎来记录,而是定时器触发线程到点后将时间推入事件队列中。
setTimeout与setInterval
setTimeout与setInterval实现定时功能是有差别的。setInterval是由定时器触发线程精准定时推送到任务队列中,如果执行栈运行时间过长,就会导致任务积压(小于间隔时间连续执行)。setTimeout虽然会有超时存在,但好在不会对性能造成太大的压力。
macrotask和microtask
在ES6的Promise
与nodejs的process.nextTick
出现之前,是没有微任务队列一说的。下面先来唠一唠macrotask
。
JS一开始就是在执行宏任务队列中的代码,遇到定时任务等就会将这些回调推入下一个宏任务队列。而两个宏任务队列之间会对页面进行重渲染。
而微任务队列则会紧跟着当前宏任务队列后面执行(在页面重渲染之前,也是下一个宏任务队列执行之前)。
tips:
不管是宏任务队列还是微任务队列,里面保存的都只是要处理的事件。真正执行这些任务的是执行栈从事件队列中获取一个回调放入执行栈中执行。