有时做题时,不难发现有些题目在实际开发工作中可能并不会如此书写代码,但,它能考察你对基础知识的掌握程度。就好比本文要说的变量名与函数名重复的情况。讲解前先看题,如下:
这道题结果输出是多少呢?有小伙伴可能会说输出10,也有小伙伴可能会觉得是输出20,那么,究竟是多少呢?又是为什么呢?在讲解答案前,我们先做点知识点储备工作:
- 变量声明提升
在执行函数test
时,函数作用域内有非声明变量x = 20;
语句,有些小伙伴可能会认为在执行完函数test
后,函数内部非声明变量x
被隐式地创建为全局变量,且正好覆盖已在全局显示声明的变量x
的值,因此认为结果输出是20。这很大可能是认为输出为20的小伙伴们的解答。
在这里我们需要清楚声明变量和非声明变量的差异中的两点:
1.声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的。
2.声明变量在任何代码执行前创建,而非声明变量只有在执行赋值操作时才会被创建。
不是很清楚上述两点的小伙伴可以参考JavaScript语句和声明之var,里面有很详细的讲解栗子。
- 函数声明提升
还有部分小伙伴,可能会认为JavaScript引擎是由上而下执行代码,当执行函数test
时,执行到第4行函数内部return;
语句使得终止函数体内代码执行跳出函数体,觉得函数x
没任何作用。其实,我们要知道的是,函数声明也是会被提升的,也就是说函数x
的声明会被提升到其对应作用域的最顶层。
不懂变量提升和函数提升的小伙伴可以参看JavaScript:变量提升和函数提升。
简单来说,就是JS引擎在解析JavaScript代码之前会先将代码进行预编译,预编译期间会将所有的变量声明和函数声明提升到其对应的作用域的最顶层。且会先执行变量声明提升,再执行函数声明提升,也就是函数声明提升优先级比变量声明提升优先级高,要注意这个优先级高是说函数声明提升要晚,后执行。关于变量声明提升和函数声明提升优先级问题,在本文最后再作讨论验证。
有了以上知识点储备,可以隐式地将题目代码理解为:
var x; //变量x声明提升
function test(){ //函数test声明提升
function x() { //函数x声明提升
console.log(x);
}
x = 20;
return;
}
x = 10;
test();
console.log(x);
有了上述预解析后代码,小伙伴们是否觉得答案呼之欲出呢?如果仍然觉得结果输出为20,则大家忽略了文章关键点:变量名与函数名重名!
不过,在这之前大家还需要理解作用域链的知识点,这里就简单介绍下:
- 作用域链是多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
- 查找变量时,就是沿着作用域链来查找的
简单地说,就是嵌套的作用域产生的有内向外(由下向上)的作用域链,用于查找变量的。
那么,理解作用域链的小伙伴知道,在查找一个变量时,沿着作用域链由内向外查找,如果在当前作用域查找到对应变量,就直接返回,否则就往其上一级作用域进行查找;如果在上一级作用域内查找到就直接返回,否则就继续向上一级作用域查找;直到全局作用域,如果还未找到就抛出ReferenceError
异常。
接着,我们继续分析上述预解析后代码:
1.首先,声明变量x
,系统在栈内存中为变量x分配内存空间,初始化赋值为undefined
;
2.再声明函数test
,系统在栈内存中为函数名test
分配内存空间,值为堆内存中创建的函数对象的内存地址值;
3.执行赋值语句x = 10
,将变量x
赋值为10;
4.执行函数test
,函数test
内部也有函数声明且函数名为x
,紧接着函数x
定义下有同名变量x
赋值语句x = 20;
,这时就需要用到前面讲的作用域链知识点:
1).在执行函数test
时,函数体内部并未调用执行函数函数x
,故函数x
内的代码并不会被执行;
2).执行函数test
内x = 20;
时,按着作用域链由内向外查找变量x
,先在当前函数test
作用域内查找,当前函数作用域内已声明同名函数x
的函数对象(重名!),因此找到同名函数x
后,就不再查找;
3).赋值语句x = 20;
实际上是给函数名为x
的函数对象重新赋值为20。
5.经过前面分析可知,赋值语句x = 20;
是给函数test
作用域内的函数对象x
赋值为20,所以最后在全局作用域内执行console.log(x);
,访问的是全局变量x
,且全局变量x
值为10,故此最终输出结果为10。
到此为止,小伙伴们应该清楚为什么这道题输出结果是10,而不是20的整个分析过程了哈!
为了验证大家是否有真正明白其中涉及到的知识点,下面将题目稍作改动如下,输出结果又是多少呢?
var x = 10;
function test(){
x = 20;
return;
}
test();
console.log(x);
相信大家有了上述分析的基础,题目答案一目了然为20呢!那么,为什么呢?简单解析下就是在执行函数test
内x = 20;
时,沿着作用域链由内向外查找变量x
,在当前函数作用域内未找到变量x
,则会向上一级作用域查找,而上一级作用域就是全局作用域,在全局作用域内找到已声明的变量x
,故给全局变量x
赋值为20。
接着,再次将代码作改动如下:
function test(){
x = 20;
return;
}
test();
console.log(x);
那么,这个输出结果又是多少呢?相信大家会不约而同地回答20。对,没错,就是20。为啥呢?很简单,在函数test
内部有未声明变量x
进行赋值操作后,会被隐式地创建为全局变量,所以在全局作用域内执行console.log(x);
,输出结果毫无疑问当然就是20呢!
至此,这道重名问题的题也算卸下帷幕呢!接下来,就文章前面提到的变量声明提升和函数声明提升优先级问题来验证下。
function bar() {}
var bar;
console.log(bar,typeof bar); //ƒ bar() {} "function"
var bar;
function bar() {}
console.log(bar,typeof bar); //ƒ bar() {} "function"
运行结果均为ƒ bar() {} "function"
,这也就验证了前面说到的函数声明提升优先级比变量声明提升优先级高,而且,再次强调下,需要注意,这个优先级高是说函数声明提升要晚,后执行!!!好多人会误理解为优先级高就是先执行,导致网上好多解释都是云里雾里的。
如果你觉得这篇文章对你有帮助,请点赞支持一下哦!