前段时间有人在知乎上邀请我回答一个问题:为什么最后一个 a 是 1 不是 5?
题目如下:
console.log(a)
if (true) {
a = 1
function a() {}
a = 5
console.log(a)
}
console.log(a)
我的第一反应是:undefined,5,5。估计和题主想的一样
分析一波
假设没有 if(true),即如下代码:
console.log(a)
a = 1
function a() {}
a = 5
console.log(a)
console.log(a)
那么答案什么?
a()、5、5
这解释了两个特性
- 变量、函数提升且函数的权重大于变量
- 在 a 没有用 var 声明时,
a=XX
默认是用 var 来声明
变量、函数提升方面的知识点在于:
变量会提升,函数也会提升,并且函数提升的优先级大于变量,如下例:
console.log(a)
console.log(a())
var a = 1
function a() {
console.log(2)
}
console.log(a)
var a = 3
a = 4
console.log(a)
console.log(a())
a()、2、1、4、a is not a function
回过头来看这道题目
console.log(a)
if (true) {
a = 1
function a() {}
a = 5
console.log(a)
}
console.log(a)
if (ture) {} ,形成了作用域,锁住了这片变量,function a(){}
无法逃逸。换句话说,只有 {}
块级标识符在,function a() {}
就被所在块级作用域中,也就说在 if (ture) {}
这片块级作用域下,它不会提升到全局顶层,而是在 if(true){}
下,即代码执行时是这样:
console.log(a)
if (true) {
+function a() {};
a = 1;
-function a() {};
a = 5;
console.log(a);
}
console.log(a)
如果你在 a = 1
前打印 a,a 的值就是 function a(){}
所以这道题全局环境下,没有变量提升,写在第一行的 console.log(a)
因为找不到 a,所以值为 undefined
进入 if(true) {}
中,function a(){}
函数提升,且权重最高,所以赋值之前的块级作用域中的 a 为 function a() {}
,window.a
为 undefined
代码执行到 function a() {}
后,块级作用域中的 a 还是为 1,但是全局变量 a 被赋值为 1
执行到 a = 5
,传统赋值,影响的是块级作用域中的 a,而不会影响全局变量 a,所以打印的第二个 console.log(a)
为 5,第三个 console.log(a)
为 1
那么问题来了,为什么一执行 function a(){}
,全局变量 a 就被赋值为 1?
我陷入的沉思,后来在回答中发现云补断山回答了,说是
历史原因,为了兼容之前的 ES5 的语法,所以在规范规定了块级作用域内函数声明的一些行为,各个浏览器实现可能不一样
简单来说,在块级作用域内的函数函数声明,行为类似于 var ,都会在全局作用域声明一个同名变量(也就是 window 上挂一个同名的属性,默认值是 undefined),因为 ES6 遇到块级作用域,会基于块级作用域创建 environment record,存放当前块级作用域内的变量,所以这个函数声明会提升到块级作用域顶部(而非全局作用域顶部)
我们学的 JavaScript 是 ECMAScript,但是我们把代码运行在浏览器上时就要按照浏览器的标准,浏览器里会有一些私货在,最经典的是 __proto__
,倒逼 ECMA 采纳。话说回来,按照这位仁兄的意思
// 因为 function a() 声明过,所以全局有个 window.a
console.log(a)
if (true) {
// 声明归声明,但是函数提升提升与作用域相关,所以提升至此块级作用域顶部
a = 1
// 块级作用域中的 a 被赋值为 1
function a() {}
// 原地爆炸,执行函数后,全局 window.a 被赋值为块级作用域中的 a
a = 5
// 块级作用域中的 a 又被赋值为 5
console.log(a)
}
console.log(a)
最诡异的是执行 function a() {}
后,全局 window.a 被赋值且为块级作用域中的 a
这个事情没完!!
等等,我就说的玩玩的,如果工作中或面试中真遇到这类问题,我也许还是不会解。
太诡异了,这不是考题范围(块级作用域、函数提升、变量提升)
就这样先吧
本文参与了 SegmentFault 思否征文「如何“反杀”面试官?」,欢迎正在阅读的你也加入。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。