震惊,这段代码居然有 4 种执行结果

951 阅读3分钟

题目

var a = 0;  
if (true) {  
    a = 1;  
    function a() {return 3}  
    a = 2;  
    console.log(a);  
}  
console.log(a);  

问题是问两个打印机处的 a 会是什么?

直觉上我们可能会答:2 和 2,可是在我们熟悉的 chrome 中,结果居然是 2 和 1。

其实还有人说过,在 safari 中,结果是 2 和 2。没想到,最懂我的还是 safari。

我自己额外还发现了两种不同情况,如果加上严格模式,结果是 2 和 0;在带有编译环境的情况下,执行结果会是 2 和 ƒ () { return 3; }

这段代码的问题

这段代码既然我们可能怎么答都不可能答对,不如贬低一下它。

  • 不应该使用 var
  • 不应该把函数声明为和原本变量名一样
  • 不应该在一个条件里,使用 function 去声明函数(应该使用函数表达式)

关于这里的第三点,我们可以从 MDN 上找到说法: “这种声明方式在不同的浏览器里可能有不同的效果”。

我们也就可以稍稍理解为什么 chrome 和 safari 的不同结果了。

调试技巧

其实我如果我们真的想弄明白执行结果,可以采用单步执行查看调用时产生的作用域变量就可以。

针对有编译环境的代码,我们可以直接查看其编译结果,这个相对容易,我们先说这一点。

编译环境:输出 2 和 function

我使用的环境是 vite,只引入了我们题目的代码,编译结果如下:

var a = 0;  
if (true) {  
    let a2 = function() {  
        return 3;  
    };  
    var a = a2; // 这里会导致最终 a 是个 函数  
    a2 = 1;  
    a2 = 2;  
    console.log(a2);  
}  
console.log(a);  

我们发现由于编译的介入,函数已经被转换成了表达式,而且命名也被替换了,这一没有必要单步执行,按照一行一行执行,结果是 2 和 function 的打印。

safari 下单步调试

我们直接在控制台下输入:

debugger  
var a = 0;  
if (true) {  
    a = 1;  
    function a() {return 3}  
    a = 2;  
    console.log(a);  
}  
console.log(a);  

safari 做了函数的提升,最早 a 是一个 function。

执行完 var a = 0, a 变成 0。

后续两次打印是 a 都是 2。

chrome 非严格模式

我们把同样的代码粘贴到 chrome 控制台运行。

刚进入 if 时,global 下的 a 是 0,block 下的 a 是 function。

执行到第一次输出时,global 下的 a 是 1,block 下的 a 是 2。这里不知道什么时候全局下的 a 变成 1 的,深究下去也没有意义,这也正是不符合我们直觉的地方。

所以最后的输出是 2 和 1。

chrome 严格模式 (safari 同样的执行结果,只看一下 chrome)

同样的代码,我们只加上严格模式:

'use strict';  
debugger;  
var a = 0;  
if (true) {  
    a = 1;  
    function a() {return 3}  
    a = 2;  
    console.log(a);  
}  
console.log(a);  

同样的方式,查看调用栈,严格模式下,global 下的 a 不会莫名地变成 1,依然保持 0。

所以输出 2 和 0。

总结

纠结输出什么其实没有意义,这道题在我的实验环境下就有 4 中输出。

给我们的唯一作用就是警示我们写出符合规范的代码。