一道JavaScript面试题

248 阅读3分钟

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 code
var v2 = 222;

上面的代码运行时其实是下面的样子:

var v1;
var v2;
v1 = 111;
console.log(v2); //没有报错而输出undefined,已经很明了了
// do some thing ....
// a lot of code
v2 = 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