JS事件循环机制(event loop)

一 前言

相信所有学过 JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的异步概念 。在初期许多人会把异步理解成类似多线程的编程模式,其实他们中有着很大的差别,要完全理解异步,就需要了解 JS 的运行核心——事件循环(event loop)。

二 JS引擎的两大特点:单线程和非阻塞

单线程

JS引擎是基于单线程(Single-threaded)事件循环的概念构建的。同一时刻只运行一个代码块在执行,与之相反的是像JAVA和C++一样的语言,它们允许多个不同的代码块同时执行。对于基于线程的软件而言,当多个代码块同时访问并改变状态时,程序很难维护并保证状态不出错。

非阻塞

非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。非阻塞是通过事件循环机制实现的。
JS通常是非阻塞的,除了某些特殊情况,JS会停止代码执行:

  • alert, confirm, prompt(除了Opera)
  • “页面上的程序正忙”的系统对话框弹出

三 事件队列

首先,我们先来看一段简单的代码:

console.log("script start"); 

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

console.log("script end");

我们可以看到,首先,程序输出 ‘script start’ 和 ‘script end’,在大约1s之后输出了 ‘setTimeout’ 。该程序的 ‘script end’ 并没有等待1s之后输出,而是立即输出。这是因为 setTimeout 是一个异步的函数。意思也就是说当我们设置一个延迟函数的时候,当前脚本并不会阻塞,它只是会在浏览器的事件表中进行记录,程序会继续向下执行。当延迟的时间结束之后,事件表会将回调函数添加至事件队列(task queue)中,事件队列拿到了任务过后便将任务压入执行栈(stack)当中,执行栈执行任务,输出 ‘setTimeout’。

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

四 事件循环机制

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

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

五 总结

关于事件循环,我们需要记住以下几点:

(1)事件队列严格按照时间先后顺序将任务压入执行栈执行;
(2)当执行栈为空时,浏览器会一直不停的检查事件队列,如果不为空,则取出第一个任务;
(3)在每一个任务结束之后,浏览器会对页面进行渲染;

猜你喜欢

转载自blog.csdn.net/baidu_30668495/article/details/83088675