var e = 1
if (true) {
function e() {}
e = 2
}
console.log(e);
问题1:为什么打印结果是函数?
问题2:为什么e=2的赋值操作失效了?
虽然代码只有几行,但是里面涉及的知识点异常的多.
代码运行环境: 谷歌浏览器(其它浏览器可能有不同的行为表现)
第一个知识点:
JS代码预解析与变量提升和函数声明提升。在全局作用域和块级作用域下,函数声明提升与函数声明语句执行有不同的行为。
- 在全局作用域下的函数声明语句,预解析时函数名与函数值直接挂到GO对象上,值就是声明的函数,在执行代码时不会再执行函数声明语句。
- 在块级作用域下的函数声明语句,块作用域里代码执行前的预解析时,函数名与函数值挂到VO对象上,值是声明语句的函数,在执行代码时还会再执行函数声明语句。这里执行函数声明语句时只做一件事:就是把函数名此时的值赋值给GO对象里的同名变量。
- 块级作用域里的函数声明在整个程序代码执行前,会把函数名挂到GO对象上,不论嵌套有多深,值为undefined,而它的真正的值,取决于块级函数声明执行时抛出的值。
在执行代码前JS会对var声明的变量进行提升,除此之外还会对非函数作用域里的函数声明进行全局变量提升(对所谓块作用域进行全局变量提升是es5的行为,对es5来说没有块作用域,所以es6概念里的块作用域下的函数声明必须被提升到全局。但是如今es6引入了块作用域的概念,所以没有把块作用域下的函数声明值挂载到GO对象里,浏览器的这种怪异行为是为了兼容以前es5时代的代码)。(这一段话非常重要,是理解这段代码运行结果的基础)
预解析之后,在全局作用域里会出现一个变量e,且值为undefined。
这段代码中,var e 会在全局声明一次e,function e 也会在全局声明一次e(这里执行的是es5的行为,es5没有块作用域),都是相同变量名,且值都为undefined,可以理解为是等价的,没有谁覆盖谁。
关键在于后面的代码执行,会如何改变全局变量e的值?
代码执行到 if 语句里后:
最为关键的行为来了:块级作用域里的代码执行前,JS先对里面的函数声明进行提升,挂载到块级作用域里的VO对象里(这里执行的是es6的行为)。然后执行function e(){}这段代码,这段代码执行时候,会把自己的变量名e的值赋值给全局变量里的同名变量(这里是为了统一以上es5行为和es6行为导致的值的变化)。(这里非常重要,彻底改变了全局变量e的值)
执行 if 里的第二行代码:因为执行上下文的关系,e = 2的操作会先在块作用域里查到变量e,块作用域里有被函数声明的变量e,所以块作用域里的e被赋值成了2。
执行最后的打印代码:在全局里打印e,打印结果是全局e里的值,该值给赋值成了函数,所以打印出来是一个函数。
关于问题2:其实赋值操作没有失效,只是没有像我们想当然的那样以为会把函数值覆盖掉,或者说我们一直认为e=2操作的是全局变量,其实操作的是块作用域里的函数声明的变量e。
拓展知识点:
在预解析时,块级作用域下,函数声明会被提升到全局,且值为undefined。
在全局作用域下,函数声明的值为该函数。
主要是因为块级作用域的关系,把函数声明的行为割裂成了两种情况。而这个问题就是因为es6引入块级作用域引起的。
作用域里的声明提升在代码执行之前。
如果把以上代码修改一下,变成这样: 猜猜最后打印结果是什么?
var e = 1
if (true) {
e = 2
function e() {}
}
console.log(e);
e=2赋值操作放在函数声明之前。
前面的执行和上一段代码一样,关键在于块作用域里的执行顺序发生了变化,最后打印出来的结果上上一段代码完全不一样。
块级作用域里的代码执行前,作用域里的函数声明先会被挂载到VO对象里(拓展:全局作用域里,代码执行前函数声明先被挂载到GO对象里)
然后,e=2赋值操作会修改VO对象里函数名e的值,此时函数名e的值为2。
执行到函数声明语句,它会把VO对象里的函数名的值赋值给GO对象里的同名变量,此时GO对象里的e值就是2。
最后打印结果就是2,你猜对了吗?