既然今天要谈的是javascript的事件循环机制,要理解事件循环,首先要知道事件循环是什么。

我们先从一个例子来看一下javascript的执行顺序。

<script>
setTimeout(function() {
console.log('定时器开始了.');
},0) new Promise(function(resolve) {
console.log('马上执行for循环了');
for (let i = 0; i < 10000; i++) {
i == 99 && resolve();
}
}).then(function() {
console.log('执行then函数了');
}) console.log('代码执行结束');
//执行结果为:
//马上执行for循环了
//代码执行结束
//执行then函数了
//定时器开始了.
</script>

怎么样,是不是和自己在心里运行的结果差了一万八千里呢。如果是的话,请耐心看完后面的内容,让你彻底弄明白javascript的事件循环机制。

单线程的javascript

要想了解事件循环的我们就得从javascript的工作原理开始说起。

javascript语言的一大特点就是单线程,可是为什么javascript不做成多线程呢?

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

任务队列

我们说单线程就意味着所有的任务必须排队。就类似于银行只有一个窗口,前一个任务执行结束后,后一个任务才能执行。如果新执行的任务耗时很长,那么后一个任务就不得不一直等着。

这样就又出现了一个问题,在进行浏览器的操作时,我们常常会通过ajax向后台发送请求,然而js必须等到浏览器接收到响应内容后才会继续往下执行,如果这段时间是10s,那么页面必须停在这里10s。这不仅会影响用户体验,也会降低CPU的利用率,显然不是我们想要的。

于是,聪明的程序员小哥哥就把任务分成了两类

  • 同步任务
  • 异步任务

同步任务指的是:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是:不进入主线程、而进入其他线程的任务(比如处理事件的事件触发线程,处理HTTP请求的异步HTTP请求线程,处理定时器的定时器触发线程),当任务完成后,相应的线程会把对应的回调函数放置到任务队列中,一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

同步任务和异步任务的执行过程大致可以简化成如下的导图所示。

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

为了便于理解事件循环,我们来看一段代码。

<script>
console.log(1);
setTimeout(function task() {
    console.log('定时器执行了.');
  },1000);
  console.log(2); </script>
  • js代码从上往下依次执行,
  • 遇到console.log(1),执行并打印出来。.
  • 遇到异步任务setTimeout,task进入Event Table并注册,计时开始。
  • 遇到console.log(2),执行并打印出来。
  • 主线程执行完毕,开始查询任务队列有没有等待执行的回调函数。
  • 一秒钟到后,timeout计时事件完成,task进入Event Queue。
  • 主线程发现任务队列有等待执行的函数task,将task调进进入主线程执行。

我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process(这个不知道翻译成啥好,因为翻译成监听进程也不对),会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

macrotask 与 microtask

其实除了广义的同步任务和异步任务的划分,异步任务还可以细分为宏任务(macrotask) 与微任务( microtask)。不同的异步任务类型会进入不同的Event Queue。

宏任务: 需要多次事件循环才能执行完,事件队列中的每一个事件对应的回调函数都是一个宏任务,每次事件循环都会调入一个宏任务;

浏览器为了能够使得js内部宏任务与DOM任务有序的执行,会在一个宏任务执行结束后,在下一个宏执行开始前,对页面进行重新渲染 (task->渲染->task->…)。

例如鼠标点击会触发一个事件回调,需要执行一个宏任务,然后重新渲染页面;setTimeout的作用是等待给定的时间后为它的回调产生一个新的宏任务。

微任务: 微任务是一次性执行完的。微任务通常来说是需要在当前task执行结束后立即执行的任务,例如对一些动作做出反馈或者异步执行任务又不需要分配一个新的task,这样便可以提高一些性能。只要执行栈中没有其他的js代码正在执行了,而且当前调入的宏任务都执行完了,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。

    • 也就是说微任务的执行,在当前task任务后,下一个task之前,在渲染之前
    • 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
    • 也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

简单理解,宏任务在下一轮事件循环执行,微任务在本轮事件循环的所有任务结束后重新渲染前执行。

  • 宏任务主要包括了:setTimeout、setInterval、setImmediate、I/O、各种事件(比如鼠标单击事件)的回调函数
  • 优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval
  • 微任务主要包括了:process.nextTick、Promise、MutationObserver
  • 优先级:process.nextTick > Promise > MutationObserver

据whatwg规范介绍:

  • 一个事件循环(event loop)会有一个或多个任务队列(task queue)
  • 每一个 event loop 都有一个 macrotask queue
  • task queue == macrotask queue != microtask queue
  • 一个任务 task 可以放入 macrotask queue 也可以放入 microtask queue 中
  • 调用栈清空(只剩全局),然后执行所有的microtask。当所有可执行的microtask执行完毕之后。循环再次从macrotask开始,找到其中一个宏任务执行完毕,如果这个宏任务中可能包含宏任务或微任务,会将宏任务添加到事件队列中,然后再执行所有的microtask,这样一直循环下去。

宏任务、微任务执行流程图如下所示。

这时,我们再来看一下文章开头给的一段代码。

<script>
setTimeout(function() {
console.log('定时器开始了.');
},0) new Promise(function(resolve) {
console.log('马上执行for循环了');
for (let i = 0; i < 10000; i++) {
i == 99 && resolve();
}
}).then(function() {
console.log('执行then函数了');
}) console.log('代码执行结束');
//执行结果为:
//马上执行for循环了
//代码执行结束
//执行then函数了
//定时器开始了.
</script>

执行步骤如下所示。

  • 当页面首次加载时,<script>标签内的代码段作为一个宏任务进入主线程,依次向下执行。
  • 遇到setTimeout将回调函数注册后压入宏任务的事件队列。
  • 遇到new Promise立即执行,输出'马上执行for循环了'。将then函数压入到微任务队列。
  • 遇到console.log('代码执行结束'),执行代码。输出"代码执行结束了"。
  • 主线程执行完后,先检查微任务队列中有没有待执行的任务,发现then函数在微任务队列里,将其取出到主线程执行,输出"执行then函数了"。
  • 开始下一轮事件循环,从红任务队列中取出setTimeout事件的回调函数,执行。输出“定时器开始了”。
  • 结束。

总结

javascript是一门单线程的语言,事件循环是js异步编程的一种方法。也是js的执行机制。当浏览器中的网页刚刚载入的时候,<script>里的代码会作为第一个宏任务被压入栈执行,同步代码执行完后,如果有微任务就执行微任务,没有微任务就执行下一个宏任务。如此往复循环,直至所有任务都执行完毕。

参考文章

JavaScript 运行机制详解:再谈Event Loop

浏览器内的事件队列

这一次,彻底弄懂 JavaScript 执行机制

javascript的执行机制—Event Loop的更多相关文章

  1. 一篇文章图文并茂地带你轻松学完 JavaScript 事件循环机制(event loop)

    JavaScript 事件循环机制 (event loop) 本篇文章已经默认你有了基础的 ES6 和 javascript语法 知识. 本篇文章比较细致,如果已经对同步异步,单线程等概念比较熟悉的读 ...

  2. JavaScript 的核心机制——event loop(最易懂版)

    前言 javascript从诞生之日起就是一门单线程的非阻塞的脚本语言. 非阻塞就是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如ajax事件)时,主线程会挂起(pen ...

  3. 再次聊一聊promise settimeout asycn awiat执行顺序---js执行机制 EVENT LOOP

    首先js是单线程 分为同步和异步,异步又分为(macrotask 宏任务 和 microtask微任务 ), 这图还是很清晰嘛,再来一张 总结一下,就是遇到同步先执行同步,异步的丢到一边依次排队,先排 ...

  4. JavaScript Concurrency model and Event Loop 并发模型和事件循环机制

    原文地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop JavaScript 有一个基于 event loop 的 ...

  5. javaScript的执行机制-同步任务-异步任务-微任务-宏任务

    一.概念理解 1.关于javascript javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变.所以一切javascr ...

  6. JavaScript并发模型与Event Loop (转载)

    并发模型可视化描述 model.svg 如上图所示,Javascript执行引擎的主线程运行的时候,产生堆(heap)和栈(stack),程序中代码依次进入栈中等待执行, 若执行时遇到异步方法,该异步 ...

  7. Js 运行机制 event loop

    Js - 运行机制 (Even Loop) Javascript 的单线程 - 引用思否的说法: JavaScript的一个语言特性(也是这门语言的核心)就是单线程.什么是单线程呢?简单地说就是同一时 ...

  8. js事件循环机制(Event Loop)

    javascript从诞生之日起就是一门  单线程的  非阻塞的  脚本语言,单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务,非阻塞靠的就是 event lo ...

  9. JavaScript的执行机制

    JavaScript是单线程的,任务的执行时自上而下的,这就有了一个问题,当遇到一个比较耗时的任务时,下面的代码就会被阻塞,这就意味着卡死.所以js是有异步的,它的实现主要是通过事件循环(event ...

随机推荐

  1. ArcGIS10.x Engine直连提示连接超时ORA-12170 来自:http://www.iarcgis.com/?p=1004

    导语 随着Esri大力宣传直连,用户也越来越由服务连接,改为直连,当然ArcGIS Engine开发用户也不例外. 环境 Oracle数据库,ArcGIS版本不限,不过由于9版本多以服务连接,以10版 ...

  2. 剑指Offer-编程详解-二维数组中的查找

    题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...

  3. C#调用SQlite常见问题汇总

    最近在做SQLite开发,开发环境是VS2010+ SQLite Ado.Net data Provider.这套Data Provider程序是基于System.Data.SQLite 1.0.66 ...

  4. nginx参考资料

    什么是负载均衡? 官网的入门文章中文版 love2上关注数比较高的nginx教程 什么是反向代理,什么又是正向代理? csdn上浅谈Nginx之反向代理与负载均衡 Nginx 作为 WebSocket ...

  5. CentOS随笔 - 3.CentOS7安装Oracle 11g xe

    前言 转帖请注明出处: http://www.cnblogs.com/Troy-Lv5/ 由于手上很多项目都是采用Oracle在进行开发, 所以安装Oracle成为必然. 当然有朋友会想为什么不安装1 ...

  6. python基础语法2

    一.顺序结构 顺序结构就是从上而下的一步一步的执行每行程序语句. 二.分支结构(if) 形式1: if 条件: pass 形式2: if 条件: pass else: pass 形式3: if 条件: ...

  7. c++计算器后续(4)

    自娱自乐: 大概是终于做到没做完的部分了,第三步助教学长的评论还没去改,感觉那个把读取文件放到Scan里面比较麻烦,其他大概还好.以上. 文件读写: 先是原来的残留问题,都是和fstream :: o ...

  8. Java虚拟机12:虚拟机性能监控与故障处理工具

    前言 定位系统问题的时候,知识.经验是基础,数据是依据,工具是运用知识处理数据的手段.这里说的数据包括:运行日志.异常堆栈.GC日志.线程快照.堆转储快照等.经常使用适当的虚拟机监控和分析的工具可以加 ...

  9. SVM中为何间隔边界的值为正负1

    在WB二面中,问到让讲一下SVM算法. 我回答的时候,直接答道线性分隔面将样本分为正负两类,取平行于线性切割面的两个面作为间隔边界,分别为:wx+b=1和wx+ b = -1. 面试官就问,为什么是正 ...

  10. Django的时区设置问题

    1.Django的时区问题 django默认的时区是UTC,平时是没有什么影响的,但是在需要将时间戳转换成本时区的时间或者是获取当前的本地的localtime的时候就出现了问题.之前程序在测试时是运行 ...