JavaScript中的事件循环详解

    JavaScript是一门单线程的非阻塞的脚本语言,单线程意味着在代码执行的任何时候,都只有一个主线程来处理所有的任务;非阻塞则是当代码需要进行一项异步任务时(无法立即返回结果,需要一定的时间才能返回的任务,如I/O事件),主线程会挂起这个任务,然后在异步任务返回结果的时候再根据一定的规则去执行相应的回调。

    但是在JS当中却无处不存在异步的概念,那JS如何实现的非阻塞呢?-事件循环

一、什么是事件队列?

console.log("start");

setTimeout(function () {
    console.log("setTimeout");
}, 1000);

console.log("end");

    我们可以看到,程序先输出start end,在1s后输出setTimeout。程序中的end并没有在1s后输出,而是立即输出,这是因为setTimeout是一个异步函数,并不会阻塞当前的程序,只是会在事件表中记录,程序向下执行,当延迟事件结束之后,事件表会将回调函数添加到事件队列中,事件队列中拿到任务后将任务压入函数执行栈,执行栈执行任务,输出setTimeout。

    而事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果不为空的话,事件队列便将第一个任务压入执行栈中运行。

二、事件循环又是什么?

下面用Philip Roberts的演讲中的一个栗子来说明事件循环机制究竟是怎么执行setTimeout的。

首先main函数的执行上下文入栈:

代码继续执行,当console.log(Hi),log(‘Hi’)入栈,console.log方法只是一个webkit内核支持的普通的方法,所以log(‘Hi’)方法立即被执行。此时输出’Hi’。

扫描二维码关注公众号,回复: 2216163 查看本文章

当遇到setTimeout的时候,执行引擎将其添加到栈,调用栈发现setTimeout是webapis中的api,所以将其出栈之后将延时的执行函数交给浏览器的timer模块处理。

timer模块处理延时执行函数,此时执行引擎继续执行将console.log(sjs)添加到栈,输出sjs;

当time模块的延时到了规定的时间后,将其放入任务队列中,此时调用栈的task已经全部执行完毕;

调用栈中的task执行完毕之后,执行引擎会接着看执行任务队列中是否有需要执行的回调函数。这里的cb函数被执行引擎添加到调用栈中,接着执行里面的代码,输出’there’。等到执行结束之后再出栈。

三、总结

由于 JS 是单线程的,同步执行任务会造成浏览器的阻塞,所以我们将 JS 分成一个又一个的任务,通过不停的循环来执行事件队列中的任务。这就使得当我们挂起某一个任务的时候可以去做一些其他的事情,而不需要等待这个任务执行完毕。所以事件循环的运行机制大致分为以下步骤:

  •  检查事件队列是否为空,如果为空,则继续检查;如不为空,则执行 2;
  •  取出事件队列的首部,压入执行栈;
  •     执行任务;
  •     检查执行栈,如果执行栈为空,则跳回第 1 步;如不为空,则继续检查;

   需要注意的是:

  •     所有的代码都要通过函数调用栈调用执行;
  •     当遇到DOM操作、ajax请求以及setTimeout方法等,会交给浏览器的其他模块处理;
  •     任务队列中存放的是回调函数;
  •     等到调用栈的task执行完后再回去执行任务队列的task;

猜你喜欢

转载自blog.csdn.net/bangbanggangan/article/details/81071141