笔试题——JavaScript事件循环机制(event loop、macrotask、microtask)
栏目: JavaScript · 发布时间: 6年前
内容简介:今天做了一道笔试题觉得很有意义分享给大家,题目如下:JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的首先来看一个小小的demo
今天做了一道笔试题觉得很有意义分享给大家,题目如下:
setTimeout(()=>{ console.log('A'); },0); var obj={ func:function () { setTimeout(function () { console.log('B') },0); return new Promise(function (resolve) { console.log('C'); resolve(); }) } }; obj.func().then(function () { console.log('D') }); console.log('E');复制代码
JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的 异步 概念 。要完全理解异步,就需要了解 JS 的运行核心—— 事件循环(event loop)。
一、什么是事件队列?
首先来看一个小小的demo
console.log('start'); setTimeout(()=>{ console.log('A'); },1000); console.log('end'); //start //end //A复制代码
js执行之后,程序输出 'start' 和 'end',在大约1s之后输出了 'A' 。那我们就有疑问了?为什么A不在end之前执行呢?
这是因为 setTimeout 是一个异步的函数。意思也就是说当我们设置一个延迟函数的时候,当前脚本并不会阻塞,它只是会在浏览器的事件表中进行记录,程序会继续向下执行。当延迟的时间结束之后,事件表会将回调函数添加至 事件队列(task queue) 中,事件队列拿到了任务过后便将任务压入 执行栈(stack) 当中,执行栈执行任务,输出 'A'。
事件队列 是一个存储着待执行任务的队列,其中的任务严格按照 时间先后顺序 执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果不为空的话,事件队列便将第一个任务压入执行栈中运行。
那么我将这个例子做一个小小的改动看一看:
console.log('start'); setTimeout(()=>{ console.log('A'); },0); console.log('end'); //start //end //A复制代码
可以看出,我们将settimeout第二个参数设置为0后,'A' 也总是会在 'end' 之后输出。所以究竟发生了什么?这是因为 setTimeout 的回调函数只是会被添加至 事件队列 ,而不是立即执行。由于当前的任务没有执行结束,所以 setTimeout 任务不会执行,直到输出了 'end' 之后,当前任务执行完毕,执行栈为空,这时事件队列才会把 setTimeout 回调函数压入执行栈执行。
二、Promise的含义和基本用法?
所谓 Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
写一个小demo看一下Promise的运行机制:
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('resolved.'); }); console.log('Hi!'); // Promise // Hi! // resolved复制代码
上面代码中,Promise 新建后 立即执行 ,所以首先输出的是 Promise
。然后, then
方法指定的回调函数,将在当前脚本 所有同步任务执行完才会执行 ,所以 resolved
最后输出。
三、 Macrotasks和Microtasks
Macrotasks和Microtasks 都属于上述的异步任务中的一种,他们分别有如下API:
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promise, MutationObserver
setTimeout的macrotask, 和 Promise的microtask 有哪些不同,先来看下代码如下:
console.log(1); setTimeout(function(){ console.log(2); }, 0); Promise.resolve().then(function(){ console.log(3); }).then(function(){ console.log(4); }); //1 //3 //4 //2复制代码
如上代码可以看到, Promise 的函数代码的异步任务会 优先 于 setTimeout 的延时为0的任务先执行。
原因是任务队列分为 macrotasks 和 microtasks, 而promise中的then方法的函数会被推入到microtasks队列中,而setTimeout函数会被推入到macrotasks
任务队列中,在每一次事件循环中, macrotask 只会 提取一个执行 ,而 microtask 会 一直 提取,直到microsoft队列 为空 为止。
也就是说如果某个microtask任务被推入到执行中,那么当主线程任务执行完成后,会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止。
而事件循环每次只会入栈一个macrotask, 主线程 执行完成该任务后又会检查 microtasks 队列并完成里面的所有任务后再执行 macrotask 的任务。
四、分析本题目
setTimeout(()=>{ console.log('A'); },0); var obj={ func:function () { setTimeout(function () { console.log('B') },0); return new Promise(function (resolve) { console.log('C'); resolve(); }) } }; obj.func().then(function () { console.log('D') }); console.log('E');复制代码
1、首先 setTimeout A 被加入到 事件队列 中 ==> 此时 macrotasks 中有[‘A’];
2、obj.func()执行时,setTimeout B 被加入到 事件队列 中 ==> 此时 macrotasks 中有[‘A’,‘B’];
3、接着return一个Promise对象,Promise 新建后 立即执行 执行console.log('C'); 控制台 首次 打印‘C’;
4、然后, then
方法指定的回调函数,被加入到 microtasks 队列,将在当前脚本所有同步任务执行完才会执行。 ==> 此时 microtasks 中有[‘D’];
5、然后继续执行当前脚本的同步任务,故控制台第 二次 输出‘E’;
6、此时所有同步任务执行完毕,如上所述先检查 microtasks 队列 , 完成其中所有任务,故控制台第 三次 输出‘D’;
7、最后再执行 macrotask 的任务,并且按照入队列的时间顺序,控制台第 四次 输出‘A’,控制台第 五次 输出‘B’。
五、执行js代码
分析与实际符合,NICE!
参考文章: www.cnblogs.com/tugenhua070…
还有阮老师的promise介绍: es6.ruanyifeng.com/?search=pro…
文章本人原创,转载请评论;
前端菜鸟对JavaScript的理解还有很多不足,如有错误欢迎大家指出来;
喜欢的点个赞把!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
App研发录:架构设计、Crash分析和竞品技术分析
包建强 / 机械工业出版社 / 2015-10-21 / CNY 59.00
本书是作者多年App开发的经验总结,从App架构的角度,重点总结了Android应用开发中常见的实用技巧和疑难问题解决方法,为打造高质量App提供有价值的实践指导,迅速提升应用开发能力和解决疑难问题的能力。本书涉及的问题有:Android基础建设、网络底层框架设计、缓存、网络流量优化、制定编程规范、模块化拆分、Crash异常的捕获与分析、持续集成、代码混淆、App竞品技术分析、项目管理和团队建设等......一起来看看 《App研发录:架构设计、Crash分析和竞品技术分析》 这本书的介绍吧!