从一个经典的 for 循环问题开始
for (var i = 1; i <= 5; i++) {
setTimeout( function timer() {
console.log(i);
}, i*1000)
}
输出是:每隔1秒,输出一个6,共5次。
原理
这样的输出,是由 JavaScript 的单线程及异步机制决定的。
JavaScript 是单线程的,所有的任务排队,按顺序一一执行。
异步执行可以实现多任务并发。
相关必要概念引用
- 并行: 同一时刻内多任务同时进行,多线程实现;
- 并发,同一时间段内,多任务同时进行着,但是某一时刻,只有某一任务执行,单线程可实现;
- 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
- 异步任务,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
- 堆(heap):内存中某一未被阻止的区域,通常存储对象(引用类型);
- 栈(stack):后进先出的顺序存储数据结构,通常存储函数参数和基本类型值变量(按值访问);
- 队列(queue):先进先出顺序存储数据结构。
- 任务队列:一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,”任务队列”上第一位的事件就自动进入主线程。
- 除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做”定时器”(timer)功能,也就是定时执行的代码。如果有定时器,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
- 回调函数(callback): 那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
- 事件循环: 主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为事件循环(Event Loop)。
异步机制如图: