问题由来
最近看面试题的时候,看到这样一道题,请问打印什么结果
var b = 10;
(function b() {
b = 20;
console.log(b);//function b() {...}
}())
console.log(b);//10
结果大家也看到了,function b里头的输出b的结果是本身,外层的console.log(b)的结果是10,为什么会出现这样的问题呢?
要理解这个问题,需要先知道执行上下文,还有IIFE的执行问题。
执行上下文,推荐javascript论代码执行上下文
立即执行函数,推荐看一下JavaScript的IIFE(即时执行方法)
好了,有了前面的知识准备,其实还是想不通这里是什么情况,百度了很久,查找了关于IIFE的执行过程,以及变量名和函数名重名的情况,找了半天没找到答案,然后综合了一些说法,最后推敲通了,别慌,这里我来进行解释。
function不出现在行首,就不会当做函数的声明
在前面我推荐那个文章里头有提到,只要不是function (){...}这样的方式,就不会被当做函数的声明,而是当做函数的表达式来处理的(这句话很重要)。
function b() {
b = 20;
console.log(b);//20
}
b();
console.log(b);//20
先来看上述代码,为什么全部打印的都是20呢,阅读了执行上下文可知,进入这片代码的时候,function b就已经在当前执行上下文的VO对象中了,然后开始执行function b,执行的时候,由于函数内部没有b变量,于是向上,找到了VO对象里头记录的function b,于是修改了b,因此function b内部改掉了b成了20,console.log还是向上找,找到了20,funciton b执行完之后,VO对象里头的b已经是20了,所以打印结果也是20。
可见整个修改的一个机制,就是function b由于function在行首,导致了全局的VO对象里头有function b而且一开始就有,但是如果function没有出现在行首,比如
(function b(){...})()
或者
!function b(){...}()
上面两种都是立即执行函数,前面说了,如果function不在首部,那就不是函数的声明,而是当做函数的表达式来处理的,但是我们好奇的是,如果不当做函数的声明,当做表达式,那就值得我们推敲了。
当做函数表达式处理是怎么样的
var b = 10;
var b = function() {
b = 20;
console.log(b);//20
}
b();
console.log(b);//20
可以看到结果都是20,function b内部,打印的b还是20,所以很明显,b改掉的是全局的b值,已经改成了20,很明显和我们前面的出来的结果是不一样的。
回到最开始的面试题,第二个console,打印的依然是10,说明全局的b并没有改掉,况且函数内部的b打印的结果是function b的值,所以知道,function b里头的b,一定就是function b的引用,自调用函数依然存在作用域链条,也会向上找,找到的b已经是自己,但是却改不掉,看看我们上面的例子,var b = function(){...},这里在改却改掉了,所以可以推测出结果,那就是用了const声明了b,但是存在一个问题,那就是前面已经声明了b,如果是这样一定会报错,况且最外层的b和内层的没有关系,可见const b是在一个独立的作用域内的,和外界无关,因此得出结论就是
var b = 10;
{
const b = function() {
b = 20;//这样写,会报错:// Uncaught TypeError: Assignment to constant variable.
console.log(b);
}
b()
}
console.log(b);
上面代码中,b那一句会报错,因为b是常量,他改的就是const这个b。因为我们是在模拟,所以直接这样显式的写出来,肯定就报错了,面试题当中,就是默默的失败,而不会报错(这就是他的机制吧)。
为了验证上面的代码正确,我们可以试一下用下面的严格模式来验证
var b = 10;
(function b() {
'use strict'
b = 20;
console.log(b)
})() // "Uncaught TypeError: Assignment to constant variable."
可见报错都是一样的,所以我们把机制已经阐述清楚了。
根据前文中提到的,只要不是function在行首,都不会去声明函数,成为自调用函数,也就是我们这里所说的机制了,中间他会形成自己的一块作用域,这种机制就成立了,欢迎交流~