ECMAScript 6引入了let
语句 。
我听说它被描述为“局部”变量,但是我仍然不太确定它的行为与var
关键字有何不同。
有什么区别? 什么时候应该let
在使用var
?
#1楼
范围规则
主要区别是作用域规则。 var
关键字声明的变量的作用域范围是立即函数主体(因此,函数作用域),而let
变量的作用域范围是由{ }
表示的直接封闭块(因此,块作用域)。
function run() {
var foo = "Foo";
let bar = "Bar";
console.log(foo, bar);
{
let baz = "Bazz";
console.log(baz);
}
console.log(baz); // ReferenceError
}
run();
将let
关键字引入该语言的原因是函数范围令人困惑,并且是JavaScript中错误的主要来源之一。
从另一个stackoverflow问题看这个示例:
var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
My value: 3
每次funcs[j]();
都输出到控制台My value: 3
funcs[j]();
由于匿名函数绑定到同一变量,因此被调用。
人们必须创建立即调用的函数以从循环中捕获正确的值,但这也很麻烦。
吊装
尽管使用var
关键字声明的变量被“提升”到块的顶部,这意味着即使在声明它们之前,也可以在其封闭范围内访问它们:
function run() {
console.log(foo); // undefined
var foo = "Foo";
console.log(foo); // Foo
}
run();
在评估其定义之前,不初始化let变量。 在初始化之前访问它们会导致ReferenceError
。 从块的开始到初始化处理之前,变量都处于“临时死区”。
function checkHoisting() {
console.log(foo); // ReferenceError
let foo = "Foo";
console.log(foo); // Foo
}
checkHoisting();
创建全局对象属性
在顶层, let
与var
不同,不会在全局对象上创建属性:
var foo = "Foo"; // globally scoped
let bar = "Bar"; // globally scoped
console.log(window.foo); // Foo
console.log(window.bar); // undefined
重新声明
在严格模式下, var
使您可以在同一范围内重新声明相同的变量,而let
引发SyntaxError。
'use strict';
var foo = "foo1";
var foo = "foo2"; // No problem, 'foo' is replaced.
let bar = "bar1";
let bar = "bar2"; // SyntaxError: Identifier 'bar' has already been declared
#2楼
这是一个示例,可以补充其他人已经写的内容。 假设您要创建一个函数数组adderFunctions
,其中每个函数都采用一个Number参数,并返回参数和该函数在数组中的索引的和。 尝试使用var
关键字通过循环生成adderFunctions
不会像某些人天真的期望那样起作用:
// An array of adder functions.
var adderFunctions = [];
for (var i = 0; i < 1000; i++) {
// We want the function at index i to add the index to its argument.
adderFunctions[i] = function(x) {
// What is i bound to here?
return x + i;
};
}
var add12 = adderFunctions[12];
// Uh oh. The function is bound to i in the outer scope, which is currently 1000.
console.log(add12(8) === 20); // => false
console.log(add12(8) === 1008); // => true
console.log(i); // => 1000
// It gets worse.
i = -8;
console.log(add12(8) === 0); // => true
上面的过程无法生成所需的函数数组,因为i
的范围超出了在其中创建每个函数的for
块的迭代范围。 相反,在循环结束时, i
在每个功能的关闭是指i
的值在环路(1000),用于在每一个匿名函数末尾adderFunctions
。 这根本不是我们想要的:我们现在在内存中拥有1000个不同功能的数组,它们的行为完全相同。 并且如果我们随后更新i
的值,突变将影响所有adderFunctions
。
但是,我们可以使用let
关键字再试一次:
// Let's try this again.
// NOTE: We're using another ES6 keyword, const, for values that won't
// be reassigned. const and let have similar scoping behavior.
const adderFunctions = [];
for (let i = 0; i < 1000; i++) {
// NOTE: We're using the newer arrow function syntax this time, but
// using the "function(x) { ..." syntax from the previous example
// here would not change the behavior shown.
adderFunctions[i] = x => x + i;
}
const add12 = adderFunctions[12];
// Yay! The behavior is as expected.
console.log(add12(8) === 20); // => true
// i's scope doesn't extend outside the for loop.
console.log(i); // => ReferenceError: i is not defined
这次, i
在for
循环的每次迭代中反弹。 现在,每个函数在创建函数时都会保留i
的值,并且adderFunctions
行为符合预期。
现在,对两种行为进行图像混合,您可能会明白为什么不建议在同一脚本中将较新的let
和const
与较旧的var
混合使用。 这样做可能会导致一些令人困惑的代码。
const doubleAdderFunctions = [];
for (var i = 0; i < 1000; i++) {
const j = i;
doubleAdderFunctions[i] = x => x + i + j;
}
const add18 = doubleAdderFunctions[9];
const add24 = doubleAdderFunctions[12];
// It's not fun debugging situations like this, especially when the
// code is more complex than in this example.
console.log(add18(24) === 42); // => false
console.log(add24(18) === 42); // => false
console.log(add18(24) === add24(18)); // => false
console.log(add18(24) === 2018); // => false
console.log(add24(18) === 2018); // => false
console.log(add18(24) === 1033); // => true
console.log(add24(18) === 1030); // => true
不要让这种情况发生在您身上。 使用短绒。
注意:这是一个教学示例,旨在演示循环中的
var
/let
行为以及具有易于理解的函数闭包。 这将是添加数字的糟糕方法。 但是在其他上下文中,在现实世界中可能会遇到在匿名函数闭包中捕获数据的通用技术。 YMMV。
#3楼
这是两者之间差异的示例(刚刚开始支持chrome):

如您所见, var j
变量的值仍在for循环范围(Block Scope)之外,但在for循环范围之外, let i
变量未定义。
"use strict"; console.log("var:"); for (var j = 0; j < 2; j++) { console.log(j); } console.log(j); console.log("let:"); for (let i = 0; i < 2; i++) { console.log(i); } console.log(i);
#4楼
let
也可以用来避免关闭问题。 它将绑定新的值,而不是保留旧的参考,如下面的示例所示。
for(var i=1; i<6; i++) { $("#div" + i).click(function () { console.log(i); }); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p>Clicking on each number will log to console:</p> <div id="div1">1</div> <div id="div2">2</div> <div id="div3">3</div> <div id="div4">4</div> <div id="div5">5</div>
上面的代码演示了经典的JavaScript关闭问题。 对i
变量的引用存储在单击处理程序闭包中,而不是i
的实际值中。
每个单击处理程序都将引用同一对象,因为只有一个计数器对象包含6个,因此每次单击将获得6个。
一个通用的解决方法是将其包装在一个匿名函数中,并将i
作为参数传递。 现在也可以通过使用let
代替var
来避免此类问题,如下面的代码所示。
(在Chrome和Firefox 50中测试)
for(let i=1; i<6; i++) { $("#div" + i).click(function () { console.log(i); }); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p>Clicking on each number will log to console:</p> <div id="div1">1</div> <div id="div2">2</div> <div id="div3">3</div> <div id="div4">4</div> <div id="div5">5</div>
#5楼
可接受的答案遗漏了一点:
{
let a = 123;
};
console.log(a); // ReferenceError: a is not defined