4

关于JavaScript作用域与函数提升的一道思考题

 2 years ago
source link: https://www.wyr.me/post/686
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
关于JavaScript作用域与函数提升的一道思考题 - 轶哥
关于JavaScript作用域与函数提升的一道思考题

在某个JavaScript前端群里面,网友深圳-resolve发的一道关于JavaScript作用域与函数提升的思考题引发了众位大佬的讨论。

console.log(a)
if (true) {
    a = 2
    function a () {}
    a = 3
    console.log('内部', a)
}
console.log('外部', a)

执行结果:

image.png

打印a在所有位置的值:

image.png

  1. 为什么第一个console没有报错?(为什么没有发生a is not defined报错?)
  2. 为什么外部a的值为2

我们先把这个复杂问题拆解成几个小问题,逐一解决。

  1. if语句里面的函数定义对全局有何影响?
  2. var声明的变量、无var声明的变量对函数定义分别有什么样的影响?

为什么第一个console没有报错?(为什么没有发生a is not defined报错?)

image.png

正常情况下打印一个未被定义的值是会直接报错的。但是如果存在函数定义,那么就会出现函数提升的情况。

这是所有JavaScript程序员都知道的常识

然而如果函数声明可能出现在一个 if 语句里,在不同的浏览器将会得到不同的结果。

image.png

详情参阅:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function

也就是说,在Chrome浏览器中,

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

a被提升,可以看作是:

var a = undefined
console.log(a)
if (true) {
    a = function () {}
}

因此第一个console没有报错。而且函数只在程序执行到定义位置的时候赋给已被提升的变量a。因此在Chrome浏览器中,if判断条件能影响后续函数a是否能够被执行。

image.png

image.png

同样的代码在Safari中,是这样的:

image.png

为什么外部a的值为2

根据上一步得到的结论,我们将a=2加入到单元测试里再进行测试。

image.png

我们发现位于函数体之前的赋值操作修改了a的全局作用域。

这是因为不使用var定义的变量,在块级作用域视为定义全局变量window.a

此时我们将a=3放置回去。

image.png

根据第一个问题的结论,和我们熟知的函数提升原则,以及函数声明的提升优于变量提升的规则,可以视作如下代码:


window.a = undefined
console.log(1, a)
if (true) {
    window.a = function () {}
    console.log(2, window.a) // 非严格模式的函数提升也导致 a 在全局作用域被提升,此时window.a是function
    window.a = 2 // 由于没有添加var声明变量a,因此此时全局变量 a 变为了 2
    console.log(3, window.a)
    // function a () {} 被提升到全局最前方了
    let a = window.a // 由于Chrome的策略,此时函数声明处将创建局部变量a
    a = 3 // 此时再对a进行赋值,修改的是块级作用域的a
    console.log(4.1, window.a)
    console.log(4.2, a)
}
console.log(5, a)

由于浏览器差异,同样的代码在Safari浏览器得到不一致的结果:

image.png

这类问题不适合作为面试题或者笔试题,虽然能考察对函数、变量的理解,但是涉及的特殊情形太多。

如何避免类似问题的产生?

使用严格模式 + ES6

使用严格模式 + ES6,在不同浏览器都能得到一致的结果。

Chrome:

image.png

Safari: image.png

使用let定义局部函数

在结构体内定义函数尽量使用let,避免函数被提升到外部。

"use strict";
if (true) {
    let a = () => {
        console.log('ok')
    }
    a();
}
a()

感谢Johnson木马啊、清明雨上、imakan、幻☆精灵、Kapok、青山几重、田热、北城之阙等网友提供的解答思路。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK