题目描述
function bs(){
console.log(1);
}
function fn(){
bs();
if(false){
function bs(){
console.log(2)
}
}
}
fn();
自己的思路
对于这一段代码的我的理解是:定义了全局bs、fn函数,而bs函数可忽视,下面调用了fn函数此时fn为独立的作用域,fn函数里面的bs函数我把bs函数看作为var bs=function(){},而它又放在一个if(false)里面,所以这导致它紧紧只是定义了bs变量而已并非赋值函数此时bs变量依然是undefined,上面bs()的调用就出现了bs is not a function
不知道我这样的理解对不对,希望大家指正或补充一下。
个人认为,解释这个问题就要弄明白
SC(作用域链)
,VO(变量对象)/ AO(活动对象)
。题主的第一个问题:由于函数域存在bs变量,因此覆盖了全局域的bs变量,所以不会输出全局域的bs变量。
题主的第二个问题:函数域的时候初始化了VO,所以bs是
underfined
(此时所有变量都是underfined
),但是要执行到if
的块级区域才会初始化该块级的AO,因此bs在该块级顶部才是function
。另外刚刚看到评论中提到严格模式,严格模式下是不允许使用未声明变量的,也就是说不能访问SC中的VO。因此访问的是全局变量。此时访问局部变量会报错,但是到
if
的块级区域下,能够访问AO,实现变量提升。2018-11-04 补充
在其他回答的评论中收到疑问,关于变量提升的问题,题主的疑问核心点是变量提升的背景下的提升情况的问题。
那要先说明代码背景下的变量提升问题:
首先JS一直是没有块级作用域的,其次ES6新增的块级作用域只针对let和const(修修补补用三年的感觉),然后没有写或者var都只有默认的<script>作用域和函数作用域,只有这种情况存在变量提升,也就是提升至这两个域各自的顶部。
问题一:为什么会变量提升?
由于JS是解释即执行的语言,而且是OOP语言,如果一边执行一边修改变量树(
SC作用域链
)是个非常低效的,因此JS在进入每一个上下文域之后执行代码之前会初始化该域下所有代码,找到var命名及省略命名、函数命名,并把变量加入变量树,意思是告诉后面的执行语句,你有什么变量可以使用。此时在域顶部访问变量会输出underfined
。也就是著名的变量提升。Scope = AO&VO + [[Scope]]
其中,AO始终在Scope
的最前端。此时回答题主关于变量提升的背景情况:控制台报错
is not function
而不是is not defined
说明变量确实提升了(忽略了if
块,直接提升至函数域顶部)。问题二:函数名和变量名同名变量的问题?
函数是各种语言的一等公民,JS也不例外,因此如果函数和变量同名,变量提升后在作用域顶部访问同名变量会输出函数定义,也就是说:不论是先命名变量还是先命名函数,都会被覆盖为函数定义。
至于题主的问题,bs却没有被定义为函数,因此要参考我的答案:此时变量仅为VO而不是AO,具体规定要看ECMAScript规范。
问题三:let和const和ES6块级作用域?
let和const会形成阮老师提出的暂时性死区问题,实际表现为:在基本作用域(<script>和函数空间)下,初始化变量树时,如果访问到同名let或者const变量定义,会删除变量树上已经命名的变量并且阻止后续var和函数定义、未写的直接定义变量加入变量树(此时会报错已定义)。
而let和const加入变量树的行为,则是在执行到该语句的时候,并且与当前上下文的块结构强相关(新的一级执行上下文内定义该变量),因此后面该块内才能够访问该变量,而一旦执行位置离开当前块结构后(新的一级执行上下文),变量树上该变量也因此被删除,并不影响外部变量定义(父块的
SC
)。问题四:function的规定和行为的差异?
ES5规定,不允许在块级区域定义函数;ES6规定,可以在块级区域定义函数,但是不存在变量提升,类似于let(可以理解为新增定义方式)。
但是!!重点!!
函数命名定义,浏览器从ES5开始就没有遵守规定,可以在块级内定义,因此ES6里面附录提到:为了向后兼容,不遵守就拉倒吧,就跟var一样吧,你开心就好。
因此,上面一大堆解释,全部是基于这个情况的。