具体文章解释可以参考这篇博文
JS单线程
JS单线程,同一个时间段只能做一件事。why?作为浏览器脚本,主要用途是和用户互动以及操作DOM。浏览器允许JS创建多个现成,但是子现成完全受主线程控制,且不得操作DOM。
单线程:前一个接后一个。如果排队是因为计算量大,CPU忙不过来倒也算了,但很多时候CPU是闲着的,因为IO很慢(比如AJAX操作网络读取数据),这时就必须等结果出来。
JS认识到,主线程可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头把挂起的任务继续执行。
于是,任务合成两种,同步任务和异步任务。
- 同步任务:在主线程进入任务队列的任务。只有前一个任务执行完才能执行下一个任务
- 异步任务:不进入主线程,进入任务队列的任务。只有“任务队列”通知主线程,某个异步任务可以执行力,该任务才会进入线程执行
注意:
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还有一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个时间
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些时间,那些对应的异步任务,于时结束等待状态,进入执行栈,开始执行
- 主线程不断重复上一步。只要主线程空了就会读取“任务队列”
任务队列是一个事件的队列,IO完成一项任务,就在“任务队列”中添加一个事件,表示相关的异步任务可以进入执行栈了。主线程读取任务,就是读取里面具体有哪些事件
任务队列中的事件,除了IO设备的事件以外,还包括了一些用户产生的事件,比如鼠标点击、页面滚动等。只要通过指定回调函数,这些事件发生时就会进入任务队列,等待主线程读取。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。主线程从任务队列中 读取事件是循环不断地,所以整个机制叫事件循环。
Node.js运行机制
- v8引擎解析脚本
- 解析后的代码,调用Node API
- libviv库负责Node API的执行。它等待不同的任务分配给不同的现成,形成一个事件循环,以异步的方式将任务的执行结果返回给V8引擎
- v8再将结果返回给用户
process.nextTick
process.nextTick在下一次event loop之前触发回调函数,也就是说它指定的任务总是发生在所有任务之前。比如现在有一个同步任务,一个异步任务,一个process.nextTick,那么执行顺序就是先执行同步任务,再执行process.nextTick,再执行异步任务。
如果现在执行栈中有三个异步任务,任务队列还有五个异步任务,如果我在此时process.nextTick,那么就会再该轮执行栈执行结束以后,再将process.nextTick的事件添加进执行栈,也就是说先执行执行栈里面的三个异步任务,再执行process.nextTick的任务,最后执行任务队列中的五个异步任务
process.nextTick总是在该轮循环结束之后,在下一轮循环开始之前,执行该任务。
process.nextTick方法可以在当前”执行栈”的尾部—-下一次Event Loop(主线程读取”任务队列”)之前—-触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。请看下面的例子(via StackOverflow)。
1 | process.nextTick(function A() { |
上面代码中,由于process.nextTick方法指定的回调函数,总是在当前”执行栈”的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前”执行栈”执行。
process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前”执行栈”一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取”事件队列”!
1 | process.nextTick(function foo() { |
几种异步方法比较
setImmediate和setTimeout封装在一个setImmediate里面。色图Immediate、总是将事件注册到下一轮EventLoop。setImmediate指定的回调函数总是排在setTimeout前面。
process.nextTick和setImmediate的一个重要区别就是:多个process.nextTick语句总是在前执行栈一次执行完,多个setImmediate可能则需要多次loop才能能执行完。由于process.nextTick指定的指定函数是在本次“事件循环”触发,而setImmediate指定的是下次事件循环触发,所以前者总是比后者遭,而且执行效率也高。
需要注意的是,setTimeout只是将事件插入了任务队列,必须等到当前代码(执行栈)执行完,主线程才回去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的事件执行
微任务和宏任务。先执行微任务再执行宏任务。
运行机制:遇到同步任务直接执行,遇到异步任务分为宏任务和微任务
- 宏任务:整体script、setTimeout、setInterval、I/O交互、setImmediate
- 微任务:promise、process.nextTick
- 有微则微,无微则宏。如果微任务列表里面有任务,会执行完毕后再执行宏任务