let && var
let
允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var
关键字不同的是,它声明的变量只能是全局或者整个函数块的。
一、 作用域
let
声明的变量只能在其声明的块或子块中使用,这个和var
很相似,二者之间最主要的区别在于var
声明的变量的作用域是整个封闭函数。
// var
function testVar() {
var a = 0;
if (true) {
var a = 1;
console.log(a); // 输出1
}
console.log(a); // 输出1
}
// let
function testLet() {
let a = 0;
if (true) {
let a = 1;
console.log(a); // 输出1 (不同的变量)
}
console.log(a); // 输出0
}
小结
在testVar
函数里面重复输出了1,var
声明的变量的作用域是整个封闭函数,所以到if里面被赋值为1
,最后都输出为1
。
在testLet
函数里面{}
,两个花括号相当于一个作用域,两个a
在不同的作用域里面,所以不受影响。(使用let
可以完全代替闭包)
二、简化内部函数代码
比如给许多函数添加事件
/*
* 使用let
*/
let lists = document.getElementsByTagName('li');
for (let i = 0; i < lists.length; i++) {
lists[i].onclick = function(ev) {
console.log(`点击了第${i}个元素`);
}
}
console.log(i); // 报错 Uncaught ReferenceError: i is not defined
运行这段代码点击每个元素都能输出正确的i值,而且console报错
var lists = document.getElementsByTagName('li');
for (var i = 0; i < lists.length; i++) {
lists[i].onclick = function(ev) {
console.log("点击了第"+i+"个元素"); // 点击了第6个元素
}
}
console.log(i); // 输出6
// 解决办法,可以使用自执行函数
var lists = document.getElementsByTagName('li');
for (var i = 0; i < lists.length; i++) {
(function(i){
lists[i].onclick = function(ev) {
console.log("点击了第"+i+"个元素"); // 正常输出
}
})(i);
}
console.log(i); // 输出6
上面的代码可以看出无论点击哪个元素都是输出“点击了第6个元素”
,而且最后console
也是6
。
小结
因为(匿名)内部函数的五个实例引用了变量i的五个不同实例。注意,如果你将let
替换为var
,则它将无法正常工作,因为所有内部函数都将返回相同的i:6
的最终值。此外,我们可以通过将创建新元素的代码移动到每个循环的作用域来保持循环更清晰。
三、 暂存死区的错误
不能再相同函数和相同作用域里面重新声明同一个变量
{
let a;
let a; //Uncaught SyntaxError: Identifier 'a' has already been declared
}
let
绑定不受变量提升的约束,也就是let
声明不会被提升到当前执行上下文的顶部,如果你在初始化之前引用它,也会报上面那个错误。
{
console.log(a); // Uncaught ReferenceError: a is not defined
let a;
let b;
}
上面介绍了es6
作用域的问题,表达式(2 + 55)
内的标识符“a”
会解析为if
块的a
,而不是覆盖值为2
的a
。if
块的a
已经生明,并未初始化,它仍处在暂存死区
function test(){
var a = 2;
if (true) {
let a = (a + 55); // ReferenceError
}
}
test();
小结
当在块中使用时,let
将变量的作用域限制为该块。注意var
的作用域在它被声明的函数内的区别。
总结
let
有块级作用域;- 没有变量提升;
- 暂存死区的错误;
- 不能重复声明;
do
表达式。
— 参考阮老师的 《ECMAScript 6 入门》