JavaScript面试题
题目
话不多说,直接上题! 下面代码的输出结果是什么?
var value1 = 0;var value2 = 0;var value3 = 0;for( var i = 10; i > 0; 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);复制代码
思考一下你的答案... 10 9 8 7 6 5 4 3 2 1
答案:
0 10 55复制代码
可能除了部分老前端,大部分人第一次见到这个题目都会答错。因为这行为太诡异了,不符合我们大部分编程语言的逻辑思维。尤其是咋们常年做后端的朋友们(包括我,一个年轻的后端),看到这输出,什么鬼??哈哈,不要惊慌,这可以说是由JavaScript的缺陷引起的,没错,缺陷!下面我们来分析一下为什么会出现这个诡异的输出。
解析
首先我们需要说一说关于var的两个特性:变量提升和没有块作用域。
什么是变量提升
其实很简单,脚本运行的时候,变量的声明会统一提升到作用域的头部。也就是说,脚本一运行所有的全局变量就已经声明了(但还有没有赋值)。举个例子
var v1 = 111;console.log(v2) // 不会报错,输出undefined// do some thing ....// a lot of codevar v2 = 222;复制代码
上面的代码运行时其实是下面的样子:
var v1;var v2;v1 = 111;console.log(v2); //没有报错而输出undefined,已经很明了了// do some thing ....// a lot of codev2 = 222;复制代码
块级作用域
对于var来说,是没有块级作用域的(有方法作用域啊)。也就是说,你可以在块外使用块内声明的变量。举个例子:
var flage = 1;if( flage ){ var innerValue = 111;}console.log(innerValue);复制代码
上面的代码不会报错,而会输出111。因为innerValue实际上是一个全局变量。它等价于下面的代码:
var flage;var innerValue;flage = 1;if( flage ){ innerValue = 111;}console.log(innerValue);复制代码
分析题目
现在,再回过头来看题目。就很好理解了,它实际运行时是下面的样子:
var value1 var value2;var value3;var i;var i2;value1 = 0;value1 = 0;value1 = 0;for( i = 10; i > 0; i-- ){ i2 = i; (function(){ var i3 = i; setTimeout(function(){ value1 += i; value2 += i2; value3 += i3; }, 1); })();}setTimeout(function(){ console.log(value1, value2, value3);}, 100);复制代码
变量i和i2,实际上是全局变量。 最后一次执行for循环时,很显然此时i的值是1,i2最终被赋值为1。 代码继续运行,i变成0,不满足循环条件,退出循环,i最终为0。 对于i3来说,它是一个函数内部变量,最终形成了一个闭包。每一次运行匿名函数都是一个新的变量,他的值则很正常的依次被赋予10 9 8 7 .... 1。 循环结束。开始执行timeout,value1被加上十个i (0),最终为0。 value2被加上十个i2 (1), 最终为10。 value3依次加上各自私有个i3,最终为55。 所以输出是 0 10 55。
题外话
为了解决刚才所说的var两个“缺陷”,es6引入了let。let 不允许在声明之前使用,有块级作用域。将题目中var 换为let之后, 程序将输出"不诡异"的结果:55 55 55