var声明及变量提升(Hoisting)机制
- 在函数作用域或全局作用域中通过关键字var声明的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量,这就是hoisting机制
- 变量的声明会被提升至函数顶部,但是初始化操作依旧留在原处执行
块级声明
-
块级声明用于声明在指定块的作用域之外无法访问的变量,块级作用域(词法作用域)存在于:
- 函数内部
- 块中
-
let声明
let声明的语法和var相同,用let代替var,就可以把变量的作用域限制在当前代码块中,且let声明不会被提升
-
禁止重复声明
- 如果作用域中已经存在某个标识符,此时再使用let关键字声明就会抛出错误
- 同一作用域中不能用let重复定义已经存在的标识符,所以let会抛出错误
- 如果当前作用域内嵌另一个作用域,便可以在内嵌的作用域中用let声明同名变量,且不会抛出错误,此时内嵌作用域的变量会覆盖当前作用域的变量
-
const声明
- const关键字声明的是常量,其值一旦被设定后就不可更改,因此每个通过const声明的变量必须进行初始化
- const和let声明的都是块级标识符,所以常量也只是在当前代码块内有效,一旦执行到外块会被立即销毁。
- 常量声明同样也不会被提升至作用域顶部
- 与let相似,const也不支持重复声明
- 如果const声明的变量的值是对象, 则对象中的值可以修改
- const声明不允许修改绑定,但是允许修改值
-
临时死区(Temporal Dead Zone~TDZ)
与var不同,let和const声明的变量不会被提升到作用域顶部,如果在声明之前访问这些变量,即使是相对安全的typeof操作符也会触发引用错误
if(condition){ console.log(value)//引用错误 let value = 'blue' }由于console语句抛出错误,因此let定义并初始化变量value的语句不会执行,此时的value还位于临时死区中
js引擎在扫描代码发现变量声明时,要么将他们提升至作用域顶部(var),要么将他们放到临时死区中,访问TDZ中的变量会触发运行时错误,只有执行过变量声明语句后,变量才会从临时死区中移出,然后才可以正常访问
console.log(typeof value) // undefined if(condition){ let value = 'blue' }在let所在块级作用域内,let声明之前访问,会导致运行时错误
在let所在块级作用域外,不会导致运行时错误,undefined
循环中的块作用域绑定
-
循环中的函数
困境:var声明使得在循环中创建函数变的异常困难
var funcs = [] for(var i = 0;i<10;i++){ funcs.push(function(){ console.log(i) }) } funcs.forEach(function(func){ func() })此时会输出十次数字10,因为这时访问的都是循环中var声明的同一个变量i
解决:在循环中使用立即调用函数表达式(IIFE),用以强制生成计数变量的副本
var funcs = [] for(var i = 0;i<10;i++){ funcs.push(function(value){ return function(){ console.log(value) } }(i)) } funcs.forEach(function(func){ func() })此时会输出0~9
-
循环中的let声明
通过let声明解决上述问题
var funcs = [] for(let i = 0;i<10;i++){ funcs.push(function(){ console.log(i) }) } funcs.forEach(function(func){ func() })此时会输出0~9
let声明模仿上述IIFE所做的一切来简化循环过程,每次迭代循环都会创建一个新的变量,并以之前迭代中同名变量的值将其初始化,所以循环内部创建的每个函数都能得到属于他们自己的i的副本
-
循环中的const声明
循环中的const的行为与let行为非常类似,区别就是循环中使用const的时候,不可以对const声明的值进行修改
全局块作用域绑定
-
let和const与var的另一个区别是他们在全局作用域中的行为:
-
当var被用于全局作用域时,他会创建一个新的全局变量作为全局对象(浏览器环境中的window对象),如下
var RegExp = 'hello' console.log(window.RegExp) // hellovar声明的全局变量会覆盖已经存在的全局属性
-
全局作用域中使用let或者const,会在全局作用域下创建一个新的绑定,但是该绑定不会添加为全局对象的属性,换句话说,用let或const不会覆盖全局变量,只能 遮蔽
let RegExp = 'hello' console.log(window.RegExp) // ƒ RegExp() { [native code] } console.log(RegExp) // 'hello'这里let创建了一个绑定并遮蔽了全局的RegExp变量,但是不会破坏全局作用域
-
如果不想为全局对象创建属性,使用let和const要安全得多
-
块级绑定最佳实践的进化
默认使用const,变量确实需要改变时才使用let,可以在某种程度上实现代码的不可变,从而防止某些错误的产生