块级作用域内函数和变量声明
为了方便后续引用理论依据进行说明,这里先给一些依据进行编号
- 允许在块级作用域内声明函数
- 函数声明类似于 var,即会提升到全局作用域或函数作用域的头部
- 同时,函数声明还会提升到所在的块级作用域的头部
- var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性
- let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性。
从 4、5 两点看出,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。在以前,顶层对象的属性赋值与全局变量的赋值,是同一件事
本文代码测试均在谷歌浏览器(版本: 80.0.3987.149)测试,支持 es6 语法,其余浏览器执行结果可能会有所不同,不代表所有浏览器的结果。防止代码相互影响,测试均是在新的空 tab 下进行。
块级作用域内的变量声明
测试代码和结果
一些猜测
-
在 es6 中,直接给之前没有声明的变量赋值,会沿着作用域链一直查找,一直到顶级对象,然后进行赋值
-
在顶级对象上都没找到属性时,会报错。
猜测实际运行代码以及结果
如果存在 var 声明,结果就和之前的不太一样了
猜测实际执行代码和测试结果
解析
-
因为 var 声明会被提前,所以相当于在代码顶部添加了一段声明
-
根据依据 4,var 声明的全局变量,其实就是顶层对象的属性。所以这里不管是打印 a 还是 window.a ,其实最后通过作用域链找到的都是 window.a
另外一个验证测试
如果整个过程发生在函数内呢?
这个结果还是比较符合预期的
-
通过 var 声明时,声明被提升到函数顶部,然后真实赋值时,通过作用域链找到函数内的变量 a,所以后续打印 a 为 1,而 window.a 为 undefined
-
没有 var 命令时,赋值操作通过作用域链找到的是全局作用域,所以赋值在了全局,然后不管打印 a 还是 window.a 最后找到的都是 window.a
块级作用域内的函数声明
测试代码和结果
猜测实际执行代码及结果
解析
- 为什么代码顶部多了一段声明
- 答:因为根据依据 2,function 声明会提升到全局作用域或函数作用域的头部
- 为什么块级作用域顶部多了一段函数的声明和赋值
- 答:根据依据 3,函数式声明是会提升到所在块级顶部,
- 为什么提升到块级顶部的声明是 let,而代码顶部是 var
- 答:因为实际上这个变量在块内是不能再次重复声明的,看图
这个时候代码块内的 var 声明居然报错了,而我们都知道,不管是 var 还是 function 的声明,不管重复几次,都是不会报错的!
- 为什么函数声明提升后,在原来的位置多了 window.a = a ?
- 答:理由来自这段代码执行结果
先重点看 function 声明位置执行前后的打印,window 上的变量 a 居然被改了,而块作用域内的 a 不变。就像执行了 window.a = a 一样。 联系依据 4,函数式声明,应该要声明到顶层对象上的,但是从第一个 console.log 我们发现,其实这个时候 window 上的 a 还只是 undefined,在真正执行了 function 声明这一行之后,window 上的 a 才被修改。
所以这里我觉得,函数声明在 es6 下的真正执行结果是这样
-
声明被提升到顶级作用域和块级作用域上
-
顶级作用域上只有声明,值为 undefined。而块作用域上不仅有声明,而且赋值为函数
-
等到执行到 function 声明所在位置时,顶级作用域上的变量才被赋值,但是注意,赋值的是当时的函数变量名,显然,这个函数变量名可以被重新赋值修改
块内有同名的函数和变量声明
一段测试代码
两个声明换个位置,测试结果并不一样
这段就不解释了,相信结合上面的说明应该能理解,就当作测试题吧!
总结
声明、赋值、取值,其实三个过程应该分开看了,特别是在 es6 之后,每个步骤都有它自己的一些特性。
-
let、const 不能重复声明;而 var、function 可以
-
var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性;而 let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性
-
取值操作是会有整个作用域链查找的过程。如果在顶级对象上都没找到,会报错;但要注意的是,如果是通过 "." 操作符来取值,则不会报错,没有值会返回 undefined
-
赋值操作也会有整个作用域链的查找过程,和取值不一样的是,如果在顶级对象上都没找到,则会直接在顶级对象上添加这个属性
-
var 和 function 在 es6 下依旧是有声明提升的,和原来一样,也是提升到最近的函数作用域或者全局作用域,且都只是声明,没有赋值。
-
function 声明提升特别之处在于,它还会提升到块级作用域顶部,而且这时候不仅声明,还会赋值。而在原来的位置,留下一段类似
window.name = attrs
这样的代码