欢迎关注 jsliang 的文档库 —— 一个穷尽一辈子更新的仓库,查看更多技术、理财、健身文章:github.com/LiangJunron…html
不折腾的前端,和咸鱼有什么区别前端
目录 |
---|
一 目录 |
二 前言 |
三 Event Loop |
四 浏览器 Event Loop |
4.1 示例 1 |
4.2 示例 2 |
4.3 示例 3 |
4.4 小结 |
五 Node.js Event Loop |
5.1 setTimeout & setImmediate |
5.2 process.nextTick() |
5.3 示例 1 |
5.4 示例 2 |
5.5 小结 |
六 总结 |
七 参考文献 |
返回目录node
Hello 小伙伴们早上好、中午好、下午好、晚上好、凌晨好~git
在平常工做中,你有没有碰到过这种疑惑:程序员
1 2 3
?for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// console:
// 3
// 3
// 3
复制代码
jsliang
?let name;
setTimeout(() => {
name = '梁峻荣';
console.log(name);
}, 1000);
if (name) {
name = 'jsliang';
console.log(name);
}
// console: '梁峻荣'
复制代码
孩子没娘,说来话长。es6
既然说来话长,jsliang 只能尝试长话短说了:github
这么一说,咱也好对文章进行划分了:web
OK,Let's go!面试
返回目录算法
答:
首先,咱们须要知道的是:JavaScript 是单线程的。
单线程意味着,全部任务都须要排队,前一个任务结束,才会执行后一个任务。
假设 jsliang 和 JavaScript 同样一次只能作一件事,那么大概就是以下图所示。
而这种 主线程从 “任务队列” 中读取执行事件,不断循环重复的过程,就被称为 事件循环(Event Loop)。
而后,若是前一个任务耗时很长,后一个任务就不得不一直等着,那么咱们确定要对这种状况作一些特殊处理,毕竟不少时候咱们并非彻底但愿它如此执行。
因此为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。
这样,在了解 浏览器 Event Loop 和 Node.js Event Loop 的状况下,咱们就能够了解它的执行过程。
经过自身的了解,来处理一些较为棘手的问题。
为了加深小伙伴们的印象,能够看下图:
jsliang 平常中,强制被加上了 “被豆豆妈打”(废话,豆豆那么可爱,你怎么能够打豆豆)。
固然,这个被打的顺序也不必定是在后面,可能打多两次后,“睡觉” 完以后就是 “被豆豆妈打” 了。
经过这个解释,小伙伴们应该知道为啥有 浏览器 Event Loop 和 Node.js Event Loop 了。
等等,你刚才说到了 浏览器 Event Loop 和 Node.js Event Loop,为何都是关于 JavaScript 的,在这两部分都不同呢?
你说了跟没说同样,为何会这样你没有解释啊!
好的,说得再仔细点:
libuv 是一个多平台支持库,主要用于异步 I/O。它最初是为 Node.js 开发的,如今 Luvit、Julia、pyuv 和其余的框架也使用它。Github - libuv 仓库
恍然大悟,的确是不同的啊!
因此,我们得将这两个 Event Loop 区分开来,它们是不同的东东哈~
最后,我们解疑开头的两个问题,为何会这样子,有没办法解决?
1 2 3
?for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// console:
// 3
// 3
// 3
复制代码
这道题是面试常备题,它是个颇有意思的问题,不只可让面试官跟你闲聊到 Event Loop,也能够闲聊下 var let const
。
为此,jsliang 特地录制了一个 GIF,但愿能帮助小伙伴进一步探索这个机制:
软件是 VS Code,调试方式是 Node.js
请仔细观看 GIF 图:
for
遍历的时候,它先执行了和 setTimeout
同级的 console
,而后往下执行,到 setTimeout
的时候,跳过了(放到某个位置)setTimeout
,依次打印了 0, 1, 2
。setTimeout
开始执行,可是这时候的 i
的值,通过前面的 i++
后,变成了 3
(for
停止循环后,i
已是 3
了)。因此,再依次打印了 3 3 3
。就是说,先走了正常的 for
,而后碰到 setTimeout
时,将 setTimeout
依次放到了异次元,最后走完 for
后,再将异次元中的的 setTimeout
放出,依次将数字给输出了。
这个执行机制,就是 Event Loop 的影响,恍然大悟有木有~
这个问题的精妙之处在于,它不只能够问你关于 Event Loop 的部分,还能够考察你对于 ES6 的 let
和 ES5 的 var
的区分,由于它有一个解决方式就是使用了 ES6 的 let
。
解决这个问题以前,不妨思考下下面的输出:
for (var i = 0; i < 3; i++) {
}
for (let j = 0; j < 3; j++) {
}
console.log(i);
console.log(j);
复制代码
若是小伙伴对 ES6 有些许了解,应该不难猜出:
3
ReferenceError: j is not defined
复制代码
是否是有些想法,那么我们再看下下面的解决方法,再进行总结:
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// console:
// 0
// 1
// 2
复制代码
是的,将 var i
改为了 let i
后,输出的结果依次是 0 1 2
了。
为何呢?简单回复就是:
let
在 for
中造成了独特的做用域块,当前的 i
只在本轮循环中有效,而后 setTimeout
会找到本轮最接近的 i
,从而做出了正确的输出。
而咱们经过 var
进行的定义,它会污染全局变量,因此在 for
外层,还能够看到 i
的值。
固然,讲到这里,你可能仍是不太清楚更细节的区分,亦或者面试官进一步问你 var let const
的区分了,你要怎么更好回答?
看看阮一峰大佬的 ES6 文档吧:es6.ruanyifeng.com/#docs/let
这里就不哆嗦了,有空我再将 ES6 这块内容整理到个人文档库中,欢迎持续关注 jsliang 的文档库:github.com/LiangJunron…
梁峻荣
?let name;
setTimeout(() => {
name = 'jsliang';
console.log(name);
}, 1000);
if (name) {
name = '梁峻荣';
console.log(name);
}
// console: 'jsliang'
复制代码
当你了解产生疑惑一的缘由后,疑惑二也就不破而解了。
咱们但愿的是 JavaScript 按照咱们须要的顺序写,结果它并无,就是由于受到了 Event Loop 的影响。
JavaScript 在碰到 setTimeout
的时候,会将它封印进异次元,只有等全部正常的语句(if
、for
……)执行完毕后,才会将它从异次元解封,输出最终结果。
咦,这就有意思了,浏览器的异次元和 Node.js 的异次元都是怎样的呢?咱们一块儿往下看。
在讲解浏览器的 Event Loop 前,咱们须要先了解一下 JavaScript 的运行机制:
而 JavaScript 的异步任务,还细分两种任务:
script
(总体代码)、setTimeout
、setInterval
、XMLHttpRequest.prototype.onload
、I/O
、UI 渲染Promise
、MutationObserver
这么讲是不太容易理解的,我们上图:
图较大,若是是公众号看的小伙伴,能够点击【阅读原文】看全图
好的,若是小伙伴们看不清楚,那么我们仍是经过代码来进行讲解,毕竟以上属于 jsliang 我的理解,是从 15 篇以上文章和本身观察代码运行总结出来的。
那么,上代码~
示例 1
// 位置 1
setTimeout(function () {
console.log('timeout1');
}, 1000);
// 位置 2
console.log('start');
// 位置 3
Promise.resolve().then(function () {
// 位置 5
console.log('promise1');
// 位置 6
Promise.resolve().then(function () {
console.log('promise2');
});
// 位置 7
setTimeout(function () {
// 位置 8
Promise.resolve().then(function () {
console.log('promise3');
});
// 位置 9
console.log('timeout2')
}, 0);
});
// 位置 4
console.log('done');
复制代码
提问:请指出上面代码的输出结果?
回答:
这是经典的面试题型,因此我们看到不用慌,先拿咱们上面的点,区分下分宏任务和微任务:
script
(总体代码)、setTimeout
、setInterval
、XMLHttpRequest.prototype.onload
、I/O
、UI 渲染Promise
、MutationObserver
OK,开始走流程:
若是你以为文字很差理解,请往下翻,有 GIF 图演示!!!
script
(总体代码),先看【位置 1】,属于宏任务 setTimeout
下的,因此作个标记,待会回来执行。script
(总体代码)下的无阻碍代码,直接执行便可。script
(总体代码)下的微任务,因此我们作个标记,走完文件全部代码后,优先执行微任务,再执行宏任务。script
(总体代码)下的无阻碍代码,直接执行便可。这样,第一波步骤,咱们输出的是【位置 2】的 start
和【位置 4】的 done
。
咱们接着走:
script
(总体代码)下的微任务,即【位置 3】
到这一步,咱们就走完了 script
(总体代码)及之下的全部微任务了。
这时候,咱们会说,【位置 1】和【位置 7】都被丢到任务队列了,是否是【位置 1】先走呢?
答案为:不是的。
一样的 setTimeout
,jsliang 在测试的时候,就发现它们的输出结果在各个环境都有本身的流程,有时候先走【位置 7】,再走【位置 1】;而有时候先走【位置 1】,再走【位置 7】。
固然,若是你指定是在 Chrome
的控制台输出一下上面的代码,那就是先【位置 7】,再【位置 1】~
因此答案是:
start
done
promise1
promise2
timeout2
promise3
timeout1
复制代码
你猜对没有?
没有能够看下 GIF 图加深印象:
在上面,jsliang 花费了许多口水,讲了一些繁杂冗余的步骤,因此下面这个示例,请小伙伴们先自行猜设,得出结论后再翻看答案和调试 GIF~
示例 2
console.log("script start");
setTimeout(function() {
console.log("setTimeout---0");
}, 0);
setTimeout(function() {
console.log("setTimeout---200");
setTimeout(function() {
console.log("inner-setTimeout---0");
});
Promise.resolve().then(function() {
console.log("promise5");
});
}, 200);
Promise.resolve()
.then(function() {
console.log("promise1");
})
.then(function() {
console.log("promise2");
});
Promise.resolve().then(function() {
console.log("promise3");
});
console.log("script end");
复制代码
script start
script end
promise1
promise3
promise2
setTimeout---0
setTimeout---200
promise5
inner-setTimeout---0
复制代码
最后再看一个示例:
示例 3
setTimeout(function() {
console.log(4);
}, 0);
const promise = new Promise(function executor(resolve) {
console.log(1);
for (var i = 0; i < 10000; i++) {
i == 9999 && resolve();
}
console.log(2);
}).then(function() {
console.log(5);
});
console.log(3);
复制代码
1
2
3
5
4
复制代码
若是不经常使用 Promise
的小伙伴,可能对此感到疑惑,为啥不是:3 1 2 5 4
?
手动滑稽,别问,问就是进一步探索 Promise
:
固然,还没将全部探索结果更新,若是有小伙伴催更会加快速度,欢迎留言或者私聊催更,哈哈~
这样,咱们就经过 3 个示例,大体了解了浏览器的 Event Loop。
固然,实际应用中的代码,何止这么简单,甚至有时候,面试官给你的面试题,也会让你瞠目结舌。
因此,这里我们废话两点:
if...else...
,再走 Promise
……可是,详细到每一个 point
都记下来,这里不推荐。大人,时代在进步,记住死的不如多在业务实践中尝试,取最新的知识。碰到问题不要慌,程序员,折腾就对了~
那么,下面我们吐槽下 Node.js 的 Event Loop。
说实话,看完 Node 官网和大佬们关于 Node.js 的 Event Loop 讲解,让我想起了 Vue、React、微信小程序 的【生命周期】,再联想到咱们的人生仿佛就像被写死的程序同样周期性、事件性运行,很是可恶,哈哈~
上面咱们讲解过:Node.js 的 Event Loop 是基于 libuv。libuv 已经对 Event Loop 做出了实现。
那么其机制是怎样子的呢?看图:
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
复制代码
关于这 6 个阶段,官网描述为:
setTimeout()
和 setInterval()
的调度回调函数。setImmediate()
调度的以外),其他状况 Node
将在适当的时候在此阻塞。setImmediate()
回调函数在这里执行。socket.on('close', ...)
。固然,这里 jsliang 并不想多此一举,将官网或者其余大佬的文章照搬过来讲是本身的,推荐小伙伴们阅读官网关于 Event Loop 的各个阶段的描述,以期在工做中有所使用:
Node.js 在不停的探索中,也会有所更新,因此正应了 jsliang 在浏览器 Event Loop 中的小结所说:不要限定死本身的知识点,与时俱进才是王道。
Node.js v9.5.0 Event Loop
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
复制代码
可是,迫于生活所需,有些时候,前端面试官仍是会跟你扯 setTimeout & setImmediate
和 process.nextTice()
。
n
毫秒后执行定时器里面的内容。setTimeout
和 setInterval
有些小弊端,因此设计了个 setImmediate
,该方法被设计为一旦在当前轮询阶段完成,就执行这个脚本。固然,光说无益,看代码:
index.js
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
复制代码
猜想下在 VS Code 中执行 node index.js
命令会发生什么?
结局 1
immediate
timeout
复制代码
结局 2
timeout
immediate
复制代码
事实上这两个结局都是会存在的,看似 happy ending,可是有的小伙伴可能内心闹翻天。
按照官网的解释:
setImmediate
老是被有限调用。const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
复制代码
虽然官方解释的很 巧妙,可是无论你懂不懂,反正我以为有点扯淡。
最后再来句官方总结:
setImmediate()
相对于 setTimeout
的主要优点是:若是 setImmediate()
是在 I/O 周期内被调度的,那么它将会在任何的定时器以前执行,跟这里存在多少个定时器无关。enm...后面若是我具体使用 Node.js 的时候,我再进一步观察吧,至于如今,我仍是先了解下便可。
nextTick
比较特殊,它存有本身的队列。
而且,它独立于 Event Loop,不管 Event Loop 处于何种阶段,都会在阶段结束的时候清空 nextTick
队列。
还有须要注意的是:process.nextTick()
优先于其余的微任务(microtask)执行。
固然,若是你对此有所兴趣,你能够进一步探索源码,或者观察大佬们探索源码:
没有使用就没有发言权,做为一个 Node.js 菜鸡,这里就不妄加评论分析了。
下面开始示例,咱们看下 Node.js 的 Event Loop 有何差别:
示例 1
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise1");
});
});
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(function() {
console.log("promise2");
});
});
复制代码
若是你还记得上面讲解的浏览器的 Event Loop,你可能会将答案直接写成:
浏览器 Event Loop 输出:
timer1
promise1
timer2
promise2
复制代码
是的你是对的,那就是浏览器的 Event Loop,到了 Node.js 这块,就有不一样变化了:
Node.js Event Loop 输出:
timer1
timer2
promise1
promise2
复制代码
尝试接受它!
而后大声默念:根据具体环境进行对应观察和得出结论。
下面我们再看一个示例:
示例 2
setTimeout(function () {
console.log(1);
});
console.log(2);
process.nextTick(() => {
console.log(3);
});
new Promise(function (resolve, rejected) {
console.log(4);
resolve()
}).then(res=>{
console.log(5);
})
setImmediate(function () {
console.log(6)
})
console.log('end');
复制代码
node index.js
2
4
end
3
5
1
6
复制代码
这里不打算解析,由于我怕初识 Event Loop 的小伙伴看完解释后懵逼,而后搞混淆了。
实话:我也不敢解析,由于我就是 Node.js 菜鸡
终上所述,咱们进行小结:
Node 端事件循环中的异步队列也是这两种:Macrotask(宏任务)队列和 Microtask(微任务)队列。
setTimeout
、setInterval
、setImmediate
、script
(总体代码)、 I/O 操做等。process.nextTick
、new Promise().then(回调)
等。OK,我们就探索了一遍 Node.js 的 Event Loop 啦,可是由于咱还成就不了 Node.js 工程师,因此咱就不对其进行详细探索,以避免和浏览器的 Event Loop 混淆了。
感兴趣的小伙伴能够自行探索咯~
若是你看到这里,你已经近乎懵逼,那么,仍是那个建议:
你不能彻底保证你的记忆力是 OK 的,因此你只须要知道有这个问题,而后在工做中实践解决便可。
enm...因此你看完了一篇水文,惟一的做用是让你面试的时候,能愉快地玩耍一些简单题目~
哈哈,Good luck.
若是你以为个人文章还不错,想持续关注或者加我微信好友,欢迎前往 github.com/LiangJunron… 进行 star 或者加微信。
感谢如下大佬们的文章,让我受益颇多。
并在他们创做的基础上,基于本身的想法,进行了整合。
不折腾的前端,和咸鱼有什么区别!
jsliang 会天天更新一道 LeetCode 题解,从而帮助小伙伴们夯实原生 JS 基础,了解与学习算法与数据结构。
浪子神剑 会天天更新面试题,以面试题为驱动来带动你们学习,坚持天天学习与思考,天天进步一点!
扫描上方二维码,关注 jsliang 的公众号(左)和 浪子神剑 的公众号(右),让咱们一块儿折腾!
jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/LiangJunron…上的做品创做。
本许可协议受权以外的使用权限能够从 creativecommons.org/licenses/by… 处得到。