前言:这是一道很经典的Js面试题,涉及到闭包、变量作用域、setTimeout等知识,对于深入理解这些内容很有帮助
题目描述
//问题描述:请写出最终的输出值,并解释原因
var value1 = 0, value2 = 0, value3 = 0;
for ( var i = 1; i <= 3; i++) {
var i2 = i;
(function() {
var i3 = i;
setTimeout(function() {
value1 += i;
value2 += i2;
value3 += i3;
}, 1);
})();
}
setTimeout(function() {
console.log(value1, value2, value3);
}, 100);
//输出结果:value1=12; value2=9; value3=6
题目解释
首先,为了下面解释这道题,我们先来补充一些预备知识(大神级人物可跳过这部分)
一、基础知识补充与温习
1、闭包和作用域
官方解释:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c=a();
c();
方言版:当函数a的内部函数b被外部变量c引用时,就形成了闭包
闭包的作用:我们知道,js里定义在函数内部的是局部变量,外部是无法直接访问的,而它的内部函数可以访问,那么内部函数返回一个值,就相当于类似在外部也能访问局部变量。
闭包的特点:为了使b中能够访问i的值,i不会被内存回收,就实现了内存常驻。对于理解这道题很重要。
闭包的缺点:内部闭包函数可以访问外部函数的变量,所以外部函数的变量不能被释放,如果闭包嵌套过多,会导致内存占用大,出现内存溢出。
作用域和作用域链:关于作用域这里不做过多解释,js中根据作用域可分为全局变量和局部变量。而对于作用域链的简单理解,可以认为当一个函数创建之后,从它的执行环境(当前对象)一直到全局对象建立了一个链表,可用的变量都挂载在上面。而函数需要查找某个变量值时,变回按照从当前直到全局对象来进行查找。
2、事件轮询与setTimeout
简介:setTimeout是js中常见的一个函数,属于window下的方法(通常,大家会省略window)
语法:setTimeout(code,millisec) 参数一为代码,参数二为毫秒数
作用:设定一个时间, 时间到了之后, 就会执行一个指定的函数或表达式,且只执行一次。
好吧,setTimeout不是这部分核心,核心是解释js的单线程和事件轮询机制。
我们知道,js是单线程的,也就是说所有的任务要排队执行。
在js中有同步和异步执行——
同步执行:是指前一个任务执行完,然后下一个任务继续执行,都在主线程里。
异步执行:则是把事情放进“任务队列”(或叫事件队里),而不是在主线程中,它们通过事件轮询(Event Loop)和回调来实现调入主线程执行。
继续回到setTimeout,语法里面的code就是异步执行的部分。
关于setTimeout更详细的内容,可点击这里学习setTimeout那些事
关于事件轮询的学习,请点击这里理解事件轮询
二、对习题的解答
上面都是些基础知识,接下来进入正题。
首先,我们拿到题目,要注意到第一个setTimeout里面匿名函数,这部分其实是放在for循环之后才会执行的,因为它是一个异步执行的函数,被放到了事件队列里最后执行。而且,每次setTimeout里面的函数执行时可以近似理解为是一次实例化。
value1
在计算value1时,需要用到i,这里涉及到作用域链的知识,最内层的函数没有i的值,它会沿着链式结构一直向上查找,最终发现i是for循环执行之后的值。此时,i的循环完成,最后一次i++之后,i已经变成了4。这样,setTimeout执行3次实例化,每次i的值是不变的,最终值为value1=4+4+4=12。-
value2
类似于value1,在执行setTimeout里的函数时,需要找到i2的值,最终我们找到的是for循环到第三次时i2=i=3。(i2不会等于4,因为到最后一次i++之后,已经不会再进入循环体了)。所以类似上面,value2的值是3+3+3=9。
value3
这部分就涉及到闭包的理解了。在循环过程中,通过立即执行函数创建了闭包,每次i3都会被赋予当次循环时i的值并保存,i3的值依次为1,2,3,最终value3=1+2+3=6。
以上为个人解释,如有错误,还望各位指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。