let 和 var 的作用差不多,都是声明 变量 的。但是它们有非常明显的区别:
区别:
let 关键字声明的变量是 块级作用域。
所谓 块级作用域 就是在 { 到 } 内的,以及 for 循环,while 循环,if 语句内,当程序执行完这些 块级作用域 的时候,在该作用域的 定义的 let 变量 会被注销。在块级作用域外是无法 调用的。
var 关键字声明的变量是 函数作用域。
在函数内定义的 var 变量,在函数外无法调用。也会随着函数执行的结束而销毁。在函数内部是可以随意调用的。即使是函数的内部函数依然可以调用。
联系
块级作用域 是 函数作用域 的子集,因此适用于 var 的作用域限制同样适用于 let 。
let 也不允许在同一个 块级作用域 中冗余声明,这样会报错。
var name;
var name;
let age;
let age; // 报错 SyntaxError,标识符 age 已经声明过了。
当然,JavaScript引擎会记录用于变量声明的标识符 以及所在的的 块级作用域,因此嵌套使用标识符 不会报错,而这是因为同一个块中没有重复声明。
对声明冗余报错不会因为混用 tel 和 var 而受影响。着两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域是如何存在。
var name;
let name; // SyntaxError
lat age;
let age; // 报错 SyntaxError,标识符 age 已经声明过了。
1、暂时性死区
let 与 var 的另一个重要区别,就是 let 声明的变量不会再作用域中 被提升。
// name 会被提升
console.log(name); // undefined
var name = "matt";
// ger 不会被提升
console.log(age);
let age = 26; // ReferenceError:age 没有定义
再解析代码的时候,JavaScript引擎会注意出现在 块后面的 let 声明,只不过在此之前不能以任何方式来引用为声明的变量。
在 let 声明之前的执行瞬间被称为 “暂时性死区 ” (temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError 错误。
2、全局声明
与 var 关键字不同,使用 let 在 全局作用域 中声明的变量不会成为 window 对象的属性( var 声明的变量 则会 )。
var name = "Matt";
console.log(window.name); // 会打印 Matt
let age = 26;
console.log(window.age); // undefined
不过, let 声明仍然是在全局作用域 中发生的,相应变量会在页面的生命周期内存续。因此,为了避免 SyntaxError ( 语法错误),必须确保同一个页面不会声明同一个变量。
3、条件声明
在 var 声明的变量时,由于声明会被 提升,JavaScript引擎会自动将多余的相同操作符的声明在 作用域顶部合并为一个声明。
因为 let 的作用域是 块级,所以不能检查前面是否已经使用 let 声明过 同名 变量,同名时就不可能在没有声明的情况下声明它。
<script>
var name = 'Nicholas';
let age = 26;
</script>
<script>
// 假设 脚本 不晓得前面是否声明过同名的变量,那就假设它没有声明过。
var name = 'Matt"; // 这里没有问题,因为可以被作为一个 提升变量来处理,那么就不需要检查之前是否创建过同名的变量名。
let age = 36; // 如果 age 以前声明过,那么这里会 报错。
</script>
但是也不要妄想用 try…catch 语句或者 typeof 操作符 来动态生成 未定义的变量,因为 条件块中 let 声明的 作用域仅限于 该条件块内。
为此,对于 let 关键字 声明变量 不能依赖 条件声明模式。
4、for 循环中的 let 声明
在 let 出现之前,for 循环定义的 迭代变量的 作用域 会溢到 循环体 外部:
for ( var i = 0;i < 5 ; i++){
// 循环逻辑
}
consele.log(i); // 5
改成 let 之后,这个问题就消失了,因为 迭代变量 的作用域 仅限于 循环块 内部:
for ( let i = 0;i < 5 ; i++){
// 循环逻辑
}
consele.log(i); // 报错,提示 i 没有 定义 。(ReferenceError)
在使用 var 的时候,最常见的问题就是对 迭代变量的其他声明 和 修改。
for ( var i = 0;i < 5 ; i++){
setTimeout(() => {consele.log(i), 0) // 这是一个延时函数,在 0 毫秒 后 执行 逗哈号 前面的 函数。
}
// 你可以以为会输出 0、1、2、3、4
// 实际上会输出:5、5、5、5、5
之所以会这样,是因为在推出循环的时候,迭代变量的保存的是 导致 循环推出 的值 5。在之后执行的延时逻辑时,所有 i 都是同一个变量,因为输出的都是同一个最终值。
而在使用 let 声明迭代变量时,JavaScript引擎在后台为 每一个迭代循环 声明一个新的 迭代变量。
每个setTimeout 引用的都是不同的 变量实列,所以 consele.log(i) 会输出的是我们期望的值,也就是循环执行过程中的每个迭代变量的值。
for ( let i = 0;i < 5 ; i++){
setTimeout(() => {consele.log(i), 0) // 这是一个延时函数,在 0 毫秒 后 执行 逗号 前面的 函数。
}
// 输出 0、1、2、3、4
这种每次迭代声明一个独立变量实列的行为适用于 所有的 for 循环,包括 for - in 、for - of 循环。