这里写目录标题
let 和 const的特性
1.不会被提升
console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
console.log(b) // Uncaught ReferenceError: Cannot access 'b' before initialization
let a = 1
const b =2
2.不可重复声明
var a = 1
const a = 2 // Uncaught SyntaxError: Identifier 'a' has already been declared
var b = 1
let b = 2 // Uncaught SyntaxError: Identifier 'b' has already been declared
3.不绑定全局作用域
let a = 1;
const b = 2
console.log(window.a); // undefined
console.log(window.b); // undefined
拓展延申(开发中,要知道不绑定全局作用域就够了,有兴趣的可以研究下拓展部分)
问:既然不绑定在window上,那它们究竟绑定在哪呢?
要想知道答案,得先了解JS的词法环境,可以参考这篇文章,下面引用别人的一段话来稍作解释
ES6 开始,有别于全局环境记录,有一个单独的声明环境记录,它关联一个词法环境对象 (Lexical Environment Object) 来存储那些不是通过 var function 关键字进行的标识符声明,并且它们都是不可见的。
简而言之
var
和function
变量的声明和保存是在变量环境组件中的,即VariableEnvironment
let
和const
变量的声明和保存以及外部环境引用是在词法环境组件中的,即LexicalEnvironment
4.只在代码块内有效
{
var a = 0;
let b = 1;
}
console.log(a) // 0
console.log(b) // Uncaught ReferenceError: b is not defined
经典for循环
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[3](); // 打印结果:5
因为var
声明的变量,会被提升,上面的代码等价于下面的代码,变量i
在循环结束后被赋值为5
var arr = []
var i
for (i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[3](); // 打印结果:5
想要输出结果为3的话,ES6的let
为此问题提供了解决方法
var arr = []
for (let i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[3](); // 打印结果:3
那为什么let
它就能解决呢?
一个被提到的比较多的原因是,let
变量不提升,可就算不提升,最后i
还是变成5,输出也应该是5啊?!!
很多文章中都对这一问题有过解答
如果要追究这个问题,就要抛弃掉之前所讲的这些特性!这是因为 let 声明在循环内部的行为是标准中专门定义的,在早期的 let 实现中本就不包含这一行为,后来专门为这一问题加入这一行为
既然是专门定义的,就肯定有其特殊之处,那特殊在哪呢?
在 for 循环中使用 let 和 var,底层会使用不同的处理方式。
那么当使用 let 的时候底层到底是怎么做的呢?
简单的来说,就是在 for (let i = 0; i < 3; i++) 中,即圆括号之内建立一个隐藏的作用域
然后每次迭代循环时都创建一个新变量,并以之前迭代中同名变量的值将其初始化。
这样对于下面这样一段代码
var arr = []
for (let i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[3](); // 打印结果:3
就相当于:
// 伪代码
(let i = 0) {
arr[0] = function() {
console.log(i)
};
}
(let i = 1) {
arr[1] = function() {
console.log(i)
};
}
(let i = 2) {
arr[2] = function() {
console.log(i)
};
};
当执行函数的时候,根据词法作用域就可以找到正确的值,其实你也可以理解为 let 声明模仿了闭包的做法来简化循环过程。