概述
知乎上看到的题,自己再加了点东西,这年头前端面试题越来越繁杂了,曾经只是一道简单的闭包题而已。
第一版:循环
分析以下代码并给出运行结果。
1 | for(var i = 0; i < 5; i ++) { |
结果很明显。
1 | // 0 |
第二版:事件轮询和无块级作用域
1 | for(var i = 0; i < 5; i ++) { |
JavaScript中使用var关键字定义的变量不存在块级作用域,因此等到主线程的当前任务(循环)执行完之后,setTimeout中的函数中的i引用的是同一个i,住循环执行结束后的i。因此结果是。
1 | // 5 |
第三版:let/const和块级作用域
1 | for(let i = 0; i < 5; i ++) { |
ES6中引入了新的关键字let和const,使用这两个关键字创建的变量都是有块级作用域的。
1 | // 0 |
第四版:闭包
1 | for(var i = 0; i < 5; i ++) { |
每一个闭包都有一个自己得函数作用域。
1 | // 0 |
如果删除IIFE中的变量声明。
1 | for(var i = 0; i < 5; i ++) { |
则闭包内部没有对i持有引用,会按照作用域链回溯到外部的i,因此还是。
1 | // 5 |
如果将闭包写在setTimeout内部。
1 | for (var i = 0; i < 5; i++) { |
IIFE会立即调用,实际上代码等价于。
1 | for (var i = 0; i < 5; i++) { |
而且世界输出。
1 | // 0 |
第五版:Promise/setTimeout和Microtask/Macrotask
1 | setTimeout(function() { |
这里考到的知识点比较多,有ES6的Promise,还有JavaScript中的微任务(Microtask)和宏任务(Macrotask)。
我们知道JavaScript是单线程的,主线程一次只执行一个任务,当响应了一个事件的时候,该事件会把相应的回调函数作为任务交给主线程。但是实际上这些任务也有区别的。
像setTimeout这种函数创建的就是一个宏任务,而Promise创建的就是一个微任务,微任务的处理属于宏任务内部的事件轮询,而宏任务的处理属于主线程的事件轮询。
下面我们来详细分析上面的代码。
1 | // 创建了一个宏任务,等待时间为1毫秒(0毫秒无效),立刻排进主线程中并继续执行当前任务 |
因此结果是。
1 | // 2 |