JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
单线程的优势
1. 降低处理复杂性,简化开发,例如不用考虑竞争机制等。
2. 作为用于预处理与用户互动的脚本语言,可以更加容易地处理状态同步的问题。
3. JS核心维护人员自身的理解与设计。
4. 越简单越容易推广,快速上手。
明显的劣势
- 并发处理能力,任务处于 I/O 等待状态,导致CPU处理资源的浪费。
单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU。
于是JavaScript语言将任务的执行模分成两种:同步任务(synchronous)和异步任务(asynchronous)。通过事件循环处理任务。
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
形成一个执行栈。
异步任务:不进入主线程、而进入任务队列(Task queue),只有任务通知主线程,某个任务可以执行了,对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
同步任务和异步任务同时存在时,一定先执行完同步任务再执行异步任务.
不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)。
以下事件属于宏任务:
setInterval()
setTimeout()
以下事件属于微任务
new Promise()
new MutaionObserver()
process.nextTick
当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。
同一次事件循环中,微任务永远在宏任务之前执行。
需要注意的是:new Promise是会进入到主线程中立刻执行,而promise.then则属于微任务