Open ArthurWangCN opened 2 years ago
我们都知道JS是单线程的,所谓的"JS是单线程的"只是指JS的主运行线程只有一个,而不是整个运行环境都是单线程。JS的运行环境主要是浏览器,以大家都很熟悉的Chrome的内核为例,他不仅是多线程的,而且是多进程的:
GUI线程 GUI线程就是渲染页面的,他解析HTML和CSS,然后将他们构建成DOM树和渲染树就是这个线程负责的。
JS引擎线程 这个线程就是负责执行JS的主线程,前面说的”JS是单线程的“就是指的这个线程。大名鼎鼎的Chrome V8引擎就是在这个线程运行的。需要注意的是,这个线程跟GUI线程是互斥的。互斥的原因是JS也可以操作DOM,如果JS线程和GUI线程同时操作DOM,结果就混乱了,不知道到底渲染哪个结果。这带来的后果就是如果JS长时间运行,GUI线程就不能执行,整个页面就感觉卡死了。
定时器线程 setTimeout、setInterval
事件触发线程 定时器线程其实只是一个计时的作用,他并不会真正执行时间到了的回调,真正执行这个回调的还是JS主线程。所以当时间到了定时器线程会将这个回调事件给到事件触发线程,然后事件触发线程将它加到事件队列里面去。最终JS主线程从事件队列取出这个回调执行。事件触发线程不仅会将定时器事件放入任务队列,其他满足条件的事件也是他负责放进任务队列。
异步HTTP请求线程 这个线程负责处理异步的ajax请求,当请求完成后,他也会通知事件触发线程,然后事件触发线程将这个事件放入事件队列给主线程执行。
所以JS异步的实现靠的就是浏览器的多线程,当他遇到异步API时,就将这个任务交给对应的线程,当这个异步API满足回调条件时,对应的线程又通过事件触发线程将这个事件放入任务队列,然后主线程从任务队列取出事件继续执行。这个流程我们多次提到了任务队列,这其实就是Event Loop。
所谓Event Loop,就是事件循环,其实就是JS管理事件执行的一个流程,具体的管理办法由他具体的运行环境确定。目前JS的主要运行环境有两个,浏览器和Node.js。
事件循环就是一个循环,是各个异步线程用来通讯和协同执行的机制。各个线程为了交换消息,还有一个公用的数据区,这就是事件队列。各个异步线程执行完后,通过事件触发线程将回调事件放到事件队列,主线程每次干完手上的活儿就来看看这个队列有没有新活儿,有的话就取出来执行。
流程如下:
一个定时器的执行:
上述流程我们可以看出,如果主线程长时间被阻塞,定时器回调就没机会执行,即使执行了,那时间也不准了。
所以写代码时一定不要长时间占用主线程。
事件队列里面的事件还可以分两类:宏任务和微任务。微任务拥有更高的优先级,当事件循环遍历队列时,先检查微任务队列,如果里面有任务,就全部拿来执行,执行完之后再执行一个宏任务。执行每个宏任务之前都要检查下微任务队列是否有任务,如果有,优先执行微任务队列。
上图需要注意以下几点:
常见宏任务有: script (可以理解为外层同步代码)、setTimeout/setInterval、setImmediate(Node.js)、I/O、UI事件、postMessage
常见微任务有: Promise、process.nextTick(Node.js)、Object.observe、MutaionObserver
...
JS 作为浏览器脚本语言,为了避免复杂的同步问题(例如用户操作事件以及操作DOM),这就决定了被设计成单线程语言,而且也将会一直保持是单线程的。而在单线程中若是遇到了耗时的操作(IO,定时器,网络请求)将会一直等待,CPU利用率将会大打折扣,时间大量浪费。所以需要设计一种方案让一些耗时的操作放在一边等待,让后面的函数先执行,于是有了EventLoop的设计。
页面渲染事件,各种IO的完成事件等随时被添加到任务队列中,一直会保持先进先出的原则执行,我们不能准确地控制这些事件被添加到任务队列中的位置。但是这个时候突然有高优先级的任务需要尽快执行,那么若只有一种类型的任务就不合适了,所以引入了微任务队列。
微任务和宏任务的区分:
所谓Event Loop,就是事件循环,其实就是JS管理事件执行的一个流程,具体的管理办法由他具体的运行环境确定。目前JS的主要运行环境有两个,浏览器和Node.js。