Event-Loop 详解

282 阅读3分钟

JavaScript是单线程

JavaScript是单线程的,也就是说同一时间只能做一件事。 这和它运行在浏览器有关,作为脚本语言,JavaScript只要用户操作DOM以及用户操作。 这决定了它只能是单线程,否则会带来很多复杂的同步问题。

HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程。
但是子线程完全受主线程控制,且不得操作DOM。
所以,这个新标准并没有改变JavaScript单线程的本质。

任务队列

单线程意味着所有的任务都要排队,前一个任务结束了才会执行后一个任务。如果有些任务耗时过长,那么就不得不一直等待下去。
在JavaScript中,耗时较长的操作主要是用户操作、网络请求、IO设备。往往这个时候CUP是处于闲置的,造成资源浪费。JavaScript设计者意识到网络请求和IO设备等耗时操作可以暂时不管他,先执行后面的任务,等耗时的操作有了结果回调再去之后再去执行相应操作。这样大大的提高了资源的利用和代码运行速度。
因此任务分为同步任务和异步任务。简单来说,同步任务是在主线程上的任务,该任务只有在前面任务执行完毕才可以执行。异步任务则是进入任务队列(task queue),当得知改任务可以执行时才会进入主线程执行。

同步异步执行顺序如下:
(1)所有同步任务都在主线程上执行,形成一个执行栈

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

事件和回调函数

任务队列就是事件队列。每当IO设备完成一项任务,就在任务队列添加一个事件。意味着相关的异步操作可以进入执行栈。
任务队列中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。
回调函数就是被主线程挂起的代码,异步任务必须制定回调函数,当主线程开始执行异步操作,就是执行相应的回调函数。
任务队列是先进先出的数据结构。同时主线程读取任务队列的规则是,主线程执行栈空是就去读取任务队列。

Event Loop

下图转引自Philip Roberts的演讲的PPT
有兴趣的同学看这里《Help, I'm stuck in an event-loop》