0%

javascript事件循环

进程和线程

了解进程和线程,更好的理解js单线程。

官方术语

进程是cpu资源(包括内存等)分配的最小单元(是能拥有资源和独立运行的最小单元)。

线程是cpu调度的最小单元。

cpu资源包括什么??
这里的调度的意思是什么??

网络上看到很多次的比喻:

将进程比作一个工厂,工厂和工厂直接互不影响。工厂里面有很多工人,工人相当于线程,工厂里面的电力等资源工人都可以使用,一个或多个工人合作完成任务。

从上面可以得出:

  • 进程之间互不影响
  • 一个进程包含一个或多个线程
  • 同一个进程中的线程共享资源

浏览器的进程和线程

首先浏览器是多进程的,可以打开谷歌浏览器的任务管理器查看。基本上一个标签页就是一个进程,一个插件也是一个进程,但是多个空白标签页合并为一个进程,浏览器做了优化。多进程的好处是什么呢?当一个标签页或者插件崩溃了,并不会影响到其他的进程,如果是单进程的话,只要有一个崩溃的话会影响整个浏览器。多进程充分利用多核优势。

多进程为什么可以充分利用多核优势??

浏览器包含的进程

Browser进程:浏览器的主进程,只要一个,负责主控和协调。主要的作用跟整个浏览器相关的:

  • 浏览器的界面与用户的交互
  • 负责各个页面的管理,创建和销毁其他进程
  • 网络资源的管理和下载
  • 负责将Renderer进程内存里的Bitmap绘制成界面 o(╥﹏╥)o不懂啥意思

第三方插件进程:这个比较容易理解,一个插件一个进程,仅当插件启用时才会被创建。

GPU进程:最多一个,用于3D绘制。

什么是GPU??

Renderer进程:浏览器渲染进程,默认每个Tab页面一个进程,互不影响。 Renderer进程是多线程的,主要作用为页面渲染、脚本执行、事件处理等。

Renderer进程包含的线程

Renderer进程是前端关注的重点,主要包括GUI渲染线程、JS引擎线程、事件触发线程、定时器触发线程、ajax网络请求线程等。

GUI渲染线程主要负责:

  • HTML、CSS解析,构建DOM树和RenderObject树,布局和绘制
  • 当发生Repaint或者reflow时,该线程就是执行
  • GUI渲染线程和JS引擎线程是互斥的,这两个线程同时只有一个线程运行

JS引擎线程主要作用:

  • 负责执行javascript脚本
  • 等待任务队列的任务触发,一个Renderer进程只有一个JS线程在运行JS程序。
  • JS引擎线程和GUI渲染线程是互斥的

事件触发线程主要作用:

  • 用来控制事件循环
  • 当JS引擎执行setTimeout、发送Ajax,绑定事件响应等,会将对应的任务添加到该线程
  • 当对应的事件符合触发条件时,该线程会把事件添加到任务队列的末尾,等待JS引擎空闲时执行。

定时器触发线程:

  • setTimeout 和setInterval所在线程
  • 因为JS是单线程的,如果处于阻塞线程状态,会影响计时的准确性,所以另开线程。
  • 计时完毕后,会将事件添加到任务队列的末尾,等待JS引擎空闲时执行。

ajax网络请求线程:

  • 在XMLHttpRequest请求连接成功后,浏览器新开一个线程请求
  • 在状态请求状态变更时,会产生状态变更事件,将回调函数放在任务队列的末尾,等待JS引擎空闲时执行。

js为什么是单线程

其实这与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作。当然,我们可以为浏览器引入“锁”的机制来解决这些冲突,但这会大大提高复杂性,所以 JavaScript 从诞生开始就选择了单线程执行。

setTimeout 只是将事件插入到task queue,必须等到execution context stack(执行栈) 执行完成,
主线程才会去执行指定的回调函数。所以当程序耗时很长时,并不能保证setTimeout能够在指定时间执行。比如:

1
2
3
4
5
6
setTimeout(()=>{
console.log('start');
},0)
for (let i = 0; i < 100000; i++) {
console.log(i);
}

并不能保证在0 ms 之后打印 start并且setTimeout最短间隔不得低于 4ms,低于这个值会自动增加。
setTimeout(fn,0)的意思是让fn在主线程最早有空闲的时候执行。
vue中,数据的改变并不会马上触发Dom元素的更新,需要使用$nextTick或者setTimeout(fn,0),保证可以获取到更新后的DOM元素

nodejs中的event-loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
console.log(4);
process.nextTick(() => {
console.log(5);
process.nextTick(() => {
console.log(6);
});
});
setTimeout(() => {
console.log(7);
}, 0);
setImmediate(() => {
console.log(8);
}, 0);