const 和 let 是ES6 新增的块级作用域变量申明方法,其中const 声明的是常量,他们同var 有哪些区别呢。
作用域链(Scope Chain)
当代码中引用一个变量时,JavaScript 引擎会从当前作用域开始向外层查找该变量,直到找到该变量或者到达全局作用域为止。这个查找过程就是作用域链。如果找到了该变量,就会使用它所在的作用域中的值;否则,就会抛出 ReferenceError 异常。
![Variable look up in scope chain](https://markbuild.com/wp-content/uploads/2023/04/scopeChain.jpg)
变量提升🏗 和 let const 的暂时性死区
JavaScript 引擎会在代码执行之前的编译过程中扫描整个作用域,并将所有的函数声明
和变量声明
“提升”🏗 到作用域的顶部。
对于var , let , const 的创建和初始化都是提升的,并将值初始化为 undefined。
function fn() {
console.log(a)
var a = 3
}
相当于
function fn() {
var a
console.log(a)
a = 3
}
另外,变量会先于函数提升。
![](https://markbuild.com/wp-content/uploads/2023/07/Variable-Function-Hoisting.jpg)
引用未定义的变量会报 is not defined 错误
![](https://markbuild.com/wp-content/uploads/2023/07/Variable-Function-Hoisting2.jpg)
但使用 let 或 const 声明的变量或常量存在“暂时性死区”(Temporal Dead Zone, TDZ),TDZ 是指从当前块级作用域开始到变量声明位置结束的区域,如果在这个区域访问该变量或常量会抛出一个引用错误(ReferenceError)。
如下图所示:
![](https://markbuild.com/wp-content/uploads/2023/04/javascript-hoisting.jpg)
但是v2 是let 创建并在TDZ 里引用,所以会报引用错误。
注意,以var f2 = function() {} 这种形式定义的函数,提升的是初始化为undefined 的 变量 f2
块级作用域(Block Scope)
ES5 只有全局作用域(Global Scope)和函数作用域(Function Scope),而 ES6 开始有块级作用域(Block Scope),用let const 申明的变量只能在块级作用域里有效。
块级作用域可以避免变量的污染和命名冲突。在块级作用域中声明的变量在块执行完后会被释放,不会污染全局作用域。
var a = 10
let a = 11 // caught SyntaxError: Identifier 'a' has already been declared
{
let a = 12
console.log(a) // 12
}
{
const a = 13
console.log(a) // 13
}
![](https://markbuild.com/wp-content/uploads/2023/04/script_scope.jpg)
块级作用域以括号{}分开,还有while(){}, if(){} ,try{} catch(e){},for 循环。
for 循环中每一次循环都是一个独立的作用域。
var a = []
for (let i = 0; i < 10; i++) {
a[i] = function () { console.log(i); }
}
a[6](); // output: 6 ,i 指向同一个内存地址
// 如果将let 改成 var
var a = []
for (var i = 0; i < 10; i++) {
a[i] = function () { console.log(i); };
}
a[6](); // output: 10 ,每个 i 指向不同的内存地址
// 可以值变参分割作用域
var a = []
for (var i = 0; i < 10; i++) {
(function(i) {
a[i] = function () { console.log(i); }
})(i)
}
a[6](); // output: 6 ,参数i = 变量i, 值拷贝
使用 let 和 const 声明的变量常量将被限制在当前块级作用域内,并且在这个块级作用域外部是不可访问的。
虽然块级作用域可以限制变量的作用域范围,但是它并不会为块中声明的变量分配一个新的执行上下文。在 JavaScript 中,只有函数会创建新的函数执行上下文(Function Execution Context),而块级作用域不会。
![](https://markbuild.com/wp-content/uploads/2023/04/debugger_block_scope.jpg)
![](https://markbuild.com/wp-content/uploads/2023/07/block_scope.jpg)
一个冷知识,就是 let 可以作变量
![](https://markbuild.com/wp-content/uploads/2023/06/let_as_variable_name.jpg)