惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
J
Java Code Geeks
有赞技术团队
有赞技术团队
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
IT之家
IT之家
G
Google Developers Blog
月光博客
月光博客
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
SegmentFault 最新的问题
博客园 - 三生石上(FineUI控件)
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
腾讯CDC
V
Visual Studio Blog
博客园 - 【当耐特】
D
Docker
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Engineering at Meta
Engineering at Meta
L
LangChain Blog

轶哥博客

blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog blog
blog
2021-05-11 · via 轶哥博客

在某个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、青山几重、田热、北城之阙等网友提供的解答思路。