likes
comments
collection
share

JS块级作用域的隐藏细节!

作者站长头像
站长
· 阅读数 39

前言

最近有群友问我下面这段代码为什么会这样输出

{
    function foo (){}
    foo= 1;
    console. log (foo); //1
}
consolelog(foo) //function

对于上述的输出结果你会感到意外么?

这个问题我一看,顿时就笑出声了,因为至少有十个人问过我这个问题。所以我干脆写一篇文章来详细的回答下这个问题。

实际上,会有这种 "奇特" 的输出结果,与 JS 的块级作用域发展历史有关。

JS 块作用域

众所周知,在 ES6 之前,JS 中还是 var 满天飞的模式,没有传统编程语言块级作用于域这个重要的特性。终于,在 ES6 中加入了 let const 关键字来弥补这个缺失。至于 letconst 是如何实现块级作用域的,你可以看我这篇文章:

掘金: 块级作用域的本质

你也可以看我的 JS进阶教学视频:

b站:视频:JS块级作用域的本质


有了块级作用域后,JS的开发明显和谐了很多。

请看下面的代码:

if (true) {
    let a = 1;
    console.log(a);
}

console.log(a);

这里会报错a is not defined, 这是由于 a 变量使用 let 声明并且在代码块中。在代码块的外部是访问不到 a的。

更本质的讲,外部的执行上下文 与 代码块的执行上下文是独立的,当代码块执行完毕后,其执行上下文被释放,而外部代码的执行上问中不存在 a, 所以报错。

但是再看下面的代码:

if (true) {
    function a() {}
}

console.log(a);

没报错!甚至打印出了函数a

啊?这不对啊,怎么和前面讲的不一样捏?不要急,再看下面的这段代码:

console.log(a);

if (true) {
    function a() {}
}

依旧没报错!但是打印的 undefined

现在我来解释下这种 “反常的现象”

一门妥协的语言

let,const 关键字是 ES6 以后才有,但是 function 的声明一直存在。 众所周知,JS 在执行前有一个 预编译 环节:

在 ES6 之前,在预编译时,function 同 var 一样,如果声明在 {} 中,是会被预编译时加入执行上下文的。

如果你还不知道我说的预编译是怎么回事,推荐你看我的教程:执行上下文

换句话说:

a()
if(true){
    function a(){
        console.log("a")
    }
}

上述代码是可以正常执行的(ES5,无块级作用域)

请不要尝试,会报错,因为这段代码只在 ES5 以及之前的环境里可以生效,在 ES6 后会报错

那么在 ES6 出来之前的大量的远古代码中,可能会有人这么写。如果直接更新到 ES6 环境中,可能会导致很多代码大批量报错,而且不好查找。js 为了这种兼容性做了一定的妥协:

块级作用域中的 function 声明在外部也会被提升,但不会被初始化为函数,而是 undefined

console.log(a);

if (true) {
    function a() {}
}

所以你就理解了这里的输出为什么是 undefined

奇怪的同步

现在我们来解释下面这段代码:

if (true) {
    function a(){
    }
}

console.log(a);

JS有这样一个奇怪的特性:

当代码执行到上述代码中的第二行,也就是函数声明那一行时,会将 外部执行上下文中 a 与 块内执行上下文里的 a进行同步。 也就是说,在这一行时,外部执行上下文中的 a 被赋值为了 函数,所以外部打印的结果是 function

为了让你更加清晰,我们再来看一段代码:

if (true) {
    console.log(a);
    function a(){
    }
    a = 1;
    console.log(a);
}
console.log(a)

请你尝试思考上面输出的结果, 我来公布答案:

  • function
  • 1
  • function

开始解释:

进入块级作用域,生成该块的执行上下文,(我在执行上文教程中讲过这个流程),在预编译环节,function a 被放入执行上下文,所以执行第一个 console.log(a) 时,打印的是function. 执行到 第3 行,外部的 a,也就是全局的执行上下文中 a 被同步为 此时块级作用域中的 a,也就是 function, 执行到第 5 行,块级作用域的执行上下文中的 a 被改为 1 ,所以第6行打印 1 ,但对于外部的执行上下文并没有影响,所以第 8 行打印 function

总结

到此,我们就解释清楚了开始的问题。另外我还需要强调,本文所有的代码在 非严格模式 下,严格模式下没有兼容,会进入正常的逻辑:

"use strict"
console.log(a)
if(true){
    function a(){}
}

上述代码会报错!

转载自:https://juejin.cn/post/7247404852946288699
评论
请登录