JS的一大特点就是单线程,所谓单线程就是,同一时间内只能执行一个任务,执行完这个任务才能执行下一个任务。执行任务的这一条线程也可称为主线程。
为了不让CPU空下来去等待IO设备(如从后台获取数据),所以将任务分成了同步任务和异步任务。同步任务指在主线程上排队执行的任务,只有前一个任务执行完毕才能执行下一个任务。异步任务指不进入主线程的任务,而进入"任务队列"(task queue)的任务。只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
异步执行的运行机制如下:
- 所有同步任务在主线程上,形成一个执行栈(execution context stack),按顺序执行
- 除了主线程,还有一个"任务队列"(task queue)。当异步任务执行有了结果,就会放一个事件在任务队列里。
- 一旦执行栈的同步任务都执行完,系统就会去按顺序读取任务队列。将任务队列中的事件所对应的任务加入到主线程中,并执行该任务事件所对应的回调函数
主线程不断重复以上三步。
"任务队列"其实是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。这些事件除了IO设备的事件外,还可能是用户操作的一些事件,如鼠标点击、鼠标滑动、键盘输入等。"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但也存在一个特殊的情况,那就是定时器。
"任务队列"可以放置定时事件,即指定某些代码在多少时间之后执行。因为定时事件是放在任务队列中的,所以就算为定时事件设定的执行时间是0秒,也得等执行栈中的所有同步任务执行结束后,才在主线程中执行定时事件所对应的回调函数。
setTimeout(function(){console.log(1);}, 0);
console.log(2);
// 先输出2 再输出1
![](/qrcode.jpg)
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。并且执行栈中的代码(同步任务),总是在读取"任务队列"(异步任务)之前执行。