ECMAScript 6 引入了 the let
statement。
我听说它被描述为一个局部变量,但我仍然不太确定它与 var 关键字的行为有何不同。
有什么区别?什么时候应该使用 let
而不是 var
?
let
包含在 6th edition draft 中,很可能会出现在最终规范中。
范围规则
主要区别在于范围规则。由 var
关键字声明的变量的作用域是直接函数体(因此是函数作用域),而 let
变量的作用域是由 { }
表示的直接 enclosure 块(因此是块作用域) .
函数 run() { var foo = "Foo";让酒吧=“酒吧”; console.log(foo, bar); // Foo Bar { var moo = "Mooo" let baz = "Bazz"; console.log(moo, baz); // Mooo Bazz } console.log(moo); // Mooo console.log(baz); // 参考错误 } run();
将 let
关键字引入语言的原因是函数范围令人困惑,并且是 JavaScript 中错误的主要来源之一。
看看 another Stack Overflow question 中的这个例子:
变量函数 = []; // 让我们创建 3 个函数 for (var i = 0; i < 3; i++) { // 并将它们存储在 funcs funcs[i] = function() { // 每个都应该记录它的值。 console.log("我的值:" + i); }; } for (var j = 0; j < 3; j++) { // 现在让我们运行每一个来查看 funcs[j](); }
每次调用 funcs[j]();
时都会将 My value: 3
输出到控制台,因为匿名函数绑定到同一个变量。
人们必须创建立即调用的函数来从循环中捕获正确的值,但这也很麻烦。
吊装
虽然用 var
关键字声明的变量是 hoisted(在代码运行之前用 undefined
初始化),这意味着它们甚至在声明之前就可以在其封闭范围内访问:
函数运行(){控制台.log(foo); // 未定义 var foo = "Foo";控制台.log(foo); // Foo } run();
let
变量在其定义被评估之前不会被初始化。在初始化之前访问它们会导致 ReferenceError
。从块的开始到处理初始化,该变量被称为处于“临时死区”。
功能 checkHoisting() { console.log(foo); // ReferenceError let foo = "Foo";控制台.log(foo); // Foo } checkHoisting();
创建全局对象属性
在顶层,let
与 var
不同,它不会在全局对象上创建属性:
var foo = "Foo"; // 全局作用域 let bar = "Bar"; // 不允许全局作用域 console.log(window.foo); // Foo console.log(window.bar); // 不明确的
重新声明
在严格模式下,var
将允许您在同一范围内重新声明相同的变量,而 let
会引发 SyntaxError。
'使用严格'; var foo = "foo1"; var foo = "foo2"; // 没问题,'foo1' 被 'foo2' 替换。让 bar = "bar1";让 bar = "bar2"; // SyntaxError: 标识符 'bar' 已经被声明
let
还可用于避免闭包问题。它绑定新值而不是保留旧引用,如下面的示例所示。
for(var i=1; i<6; i++) { $("#div" + i).click(function () { console.log(i); }); }
点击每个数字会登录到控制台:< /p>
上面的代码演示了一个经典的 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); }); }
点击每个数字会登录到控制台:< /p>
let
,但它会为所有按钮提示“6”。您是否有任何消息来源说明 let
的行为方式?
let
会非常有用。在循环中设置事件侦听器不再需要在每次迭代时立即调用函数表达式来确定本地范围 i
。
let 和 var 有什么区别?
使用 var 语句定义的变量在定义它的整个函数中都是已知的,从函数的开始。 (*)
使用 let 语句定义的变量仅在定义它的块中是已知的,从定义它的那一刻起。 (**)
要了解差异,请考虑以下代码:
// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here
function loop(arr) {
// i IS known here, but undefined
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
for( var i = 0; i < arr.length; i++ ) {
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
};
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
for( let j = 0; j < arr.length; j++ ) {
// i IS known here, and has a value
// j IS known here, and has a value
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
};
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
}
loop([1,2,3,4]);
for( var k = 0; k < arr.length; k++ ) {
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here
};
for( let l = 0; l < arr.length; l++ ) {
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS known here, and has a value
};
loop([1,2,3,4]);
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here
在这里,我们可以看到我们的变量 j
只在第一个 for 循环中知道,而在之前和之后都不知道。然而,我们的变量 i
在整个函数中是已知的。
另外,考虑到块范围的变量在声明之前是未知的,因为它们没有被提升。您也不允许在同一个块内重新声明同一个块范围的变量。这使得块范围的变量比全局或功能范围的变量更不容易出错,全局或功能范围的变量被提升并且在多个声明的情况下不会产生任何错误。
今天使用 let 安全吗?
有些人会争辩说,将来我们将只使用 let 语句,而 var 语句将变得过时。 JavaScript 大师 Kyle Simpson 写了 a very elaborate article on why he believes that won't be the case。
然而,今天绝对不是这样。事实上,我们实际上需要问自己,使用 let
语句是否安全。该问题的答案取决于您的环境:
如果您正在编写服务器端 JavaScript 代码 (Node.js),则可以安全地使用 let 语句。
如果您正在编写客户端 JavaScript 代码并使用基于浏览器的转译器(如 Traceur 或 babel-standalone),则可以安全地使用 let 语句,但是您的代码可能在性能方面并非最佳。
如果您正在编写客户端 JavaScript 代码并使用基于节点的转译器(如 traceur shell 脚本或 Babel),则可以安全地使用 let 语句。而且因为您的浏览器只会知道转译的代码,所以性能缺陷应该是有限的。
如果您正在编写客户端 JavaScript 代码并且不使用转译器,则需要考虑浏览器支持。还有一些浏览器根本不支持 let :
https://i.stack.imgur.com/J9kEC.png
如何跟踪浏览器支持
有关在您阅读此答案时哪些浏览器支持 let
语句的最新概述,请参阅 this Can I Use
page。
(*) 全局和函数范围的变量可以在声明之前初始化和使用,因为 JavaScript 变量是 hoisted。 这意味着声明总是位于范围的顶部。
(**) 块范围的变量不被提升
i
在功能块中随处可见!它以 undefined
开始(由于提升),直到您分配一个值! ps:let
也被提升(到它的包含块的顶部),但在第一次分配之前在块中引用时会给出 ReferenceError
。 (ps2:我是一个支持分号的人,但你真的不需要在 block 后面加分号)。话虽如此,感谢您添加有关支持的现实检查!
let
和 var
之间提升行为的区别!
let
和 const
建议仅在您真正需要它们的附加功能时使用,因为强制/检查这些额外的功能功能(如只写常量)导致(当前)引擎执行/检查/验证/设置的“更多工作”(以及范围树中的其他范围节点)。
这是一个带有一些示例的 explanation of the let
keyword。
let 的工作方式与 var 非常相似。主要区别在于 var 变量的作用域是整个封闭函数
Wikipedia 上的 This table 显示了哪些浏览器支持 Javascript 1.7。
请注意,只有 Mozilla 和 Chrome 浏览器支持它。 IE、Safari 和其他可能没有。
let
msdn.microsoft.com/en-us/library/ie/dn342892%28v=vs.85%29.aspx 的支持
let
支持目前所有最新的浏览器,除了 Opera、Blackberry 和QQ浏览器。
接受的答案缺少一点:
{
let a = 123;
};
console.log(a); // ReferenceError: a is not defined
for
循环初始化程序中进行了演示,大大缩小了 let
限制的应用范围。赞成。
让
块范围
使用 let
关键字声明的变量是块范围的,这意味着它们仅在声明它们的 block 中可用。
在顶层(函数之外)
在顶层,使用 let
声明的变量不会在全局对象上创建属性。
var globalVariable = 42;
let blockScopedVariable = 43;
console.log(globalVariable); // 42
console.log(blockScopedVariable); // 43
console.log(this.globalVariable); // 42
console.log(this.blockScopedVariable); // undefined
在函数内部
在函数内部(但在块外部),let
与 var
具有相同的范围。
(() => {
var functionScopedVariable = 42;
let blockScopedVariable = 43;
console.log(functionScopedVariable); // 42
console.log(blockScopedVariable); // 43
})();
console.log(functionScopedVariable); // ReferenceError: functionScopedVariable is not defined
console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
块内
在块内使用 let
声明的变量不能在该块外访问。
{
var globalVariable = 42;
let blockScopedVariable = 43;
console.log(globalVariable); // 42
console.log(blockScopedVariable); // 43
}
console.log(globalVariable); // 42
console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
循环内
循环中用 let
声明的变量只能在该循环内引用。
for (var i = 0; i < 3; i++) {
var j = i * 2;
}
console.log(i); // 3
console.log(j); // 4
for (let k = 0; k < 3; k++) {
let l = k * 2;
}
console.log(typeof k); // undefined
console.log(typeof l); // undefined
// Trying to do console.log(k) or console.log(l) here would throw a ReferenceError.
带闭包的循环
如果您在循环中使用 let
而不是 var
,则每次迭代都会获得一个新变量。这意味着您可以安全地在循环内使用闭包。
// Logs 3 thrice, not what we meant.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Logs 0, 1 and 2, as expected.
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0);
}
时间死区
由于 the temporal dead zone,使用 let
声明的变量在声明之前无法访问。尝试这样做会引发错误。
console.log(noTDZ); // undefined
var noTDZ = 43;
console.log(hasTDZ); // ReferenceError: hasTDZ is not defined
let hasTDZ = 42;
无需重新声明
您不能使用 let
多次声明同一个变量。您也不能使用与使用 var
声明的另一个变量具有相同标识符的 let
声明一个变量。
var a;
var a; // Works fine.
let b;
let b; // SyntaxError: Identifier 'b' has already been declared
var c;
let c; // SyntaxError: Identifier 'c' has already been declared
常量
const
与 let
非常相似——它是块范围的并且具有 TDZ。然而,有两件事是不同的。
无需重新分配
无法重新分配使用 const
声明的变量。
const a = 42;
a = 43; // TypeError: Assignment to constant variable.
请注意,这并不意味着该值是不可变的。它的属性仍然可以更改。
const obj = {};
obj.a = 42;
console.log(obj.a); // 42
如果您想拥有一个不可变对象,您应该使用 Object.freeze()
。
const obj = Object.freeze({a: 40});
obj.a = 42;
console.log(obj.a); // 40
console.log(obj.b); // undefined
需要初始化程序
使用 const
声明变量时,您始终必须指定一个值。
const a; // SyntaxError: Missing initializer in const declaration
在最基本的条件下,
for (let i = 0; i < 5; i++) {
// i accessible ✔️
}
// i not accessible ❌
for (var i = 0; i < 5; i++) {
// i accessible ✔️
}
// i accessible ✔️
⚡️沙盒玩↓
https://codesandbox.io/static/img/play-codesandbox.svg
https://i.stack.imgur.com/dqNYW.png
如您所见,var j
变量的值仍在 for 循环范围(块范围)之外,但 let i
变量在 for 循环范围之外未定义。
“使用严格”; console.log("var:"); for (var j = 0; j < 2; j++) { console.log(j); } 控制台.log(j); console.log("let:"); for (let i = 0; i < 2; i++) { console.log(i); } 控制台.log(i);
主要区别在于 scope 的不同,而 let 只能在它声明的 scope 内可用,就像在 for 循环中一样,var例如, 可以在循环外访问。来自 MDN 中的文档(示例也来自 MDN):
let 允许您将范围限制在使用它的块、语句或表达式中声明变量。这与 var 关键字不同,它在全局范围内定义一个变量,或者在整个函数的本地定义一个变量,而不考虑块范围。 let 声明的变量的作用域是定义它们的块,以及任何包含的子块。这样,let 的工作方式非常类似于 var。主要区别在于 var 变量的作用域是整个封闭函数:
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}`
在程序和函数的顶层,let 与 var 不同,它不会在全局对象上创建属性。例如:
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined
在块内使用时,让变量的范围限制在该块内。注意作用域在声明它的函数内部的 var 之间的区别。
var a = 1;
var b = 2;
if (a === 1) {
var a = 11; // the scope is global
let b = 22; // the scope is inside the if-block
console.log(a); // 11
console.log(b); // 22
}
console.log(a); // 11
console.log(b); // 2
也不要忘记它的 ECMA6 功能,所以它还没有完全支持,所以最好总是使用 Babel 等将它转换为 ECMA5 ......有关访问 babel website 的更多信息
有一些细微的差别 — let
作用域的行为更像是变量作用域在任何其他语言中的行为或多或少。
例如,它的范围是封闭块,它们在声明之前不存在,等等。
但是值得注意的是,let
只是较新的 Javascript 实现的一部分,并且具有不同程度的 browser support。
let
包含在 6th edition draft 中,并且很可能会出现在最终规范中。
let
。 Safari、IE 和 Chome 都没有。
let
不会提升,使用由块顶部定义的 let
定义的变量。如果您的 if
语句不仅仅是几行代码,您可能会忘记在定义该变量之前不能使用该变量。好点!!!
let
会将变量提升 到块的顶部。但是,在变量声明之前引用块中的变量会导致 ReferenceError(我的注释:而不是旧的 undefined
)。从块开始到处理声明,该变量处于“临时死区”中。” “switch 语句,因为只有一个底层块”也是如此。来源:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
变量不提升 let 不会提升到它们出现的块的整个范围。相比之下, var 可以提升如下。 { 控制台.log(cc); // 不明确的。提升 var cc = 23; } { 控制台.log(bb); // ReferenceError: bb 没有定义 let bb = 23;实际上,根据@Bergi, var 和 let 都被提升了。
let 的垃圾收集块范围与闭包和垃圾收集以回收内存有关。考虑一下,函数 process(data) { //... } var hugeData = { .. };过程(巨大数据); var btn = document.getElementById("mybutton"); btn.addEventListener("click", function click(evt){ //.... });点击处理程序回调根本不需要hugeData 变量。理论上,在 process(..) 运行之后,巨大的数据结构 hugeData 可能会被垃圾回收。然而,有可能一些 JS 引擎仍然必须保持这个巨大的结构,因为 click 函数在整个范围内都有一个闭包。但是,块作用域可以使这个庞大的数据结构被垃圾回收。 function process(data) { //... } { // 在这个块中声明的任何东西都可以被垃圾回收 let hugeData = { .. };过程(巨大数据); } var btn = document.getElementById("mybutton"); btn.addEventListener("click", function click(evt){ //.... });
let 循环 循环中的 let 可以将其重新绑定到循环的每次迭代,确保从上一次循环迭代结束时重新为其分配值。考虑一下, // 打印 '5' 5 次 for (var i = 0; i < 5; ++i) { setTimeout(function () { console.log(i); }, 1000);但是,用 let // print 1, 2, 3, 4, 5 替换 var。 now for (let i = 0; i < 5; ++i) { setTimeout(function () { console.log(i); }, 1000);因为让我们用这些名称创建一个新的词法环境 a) 初始化表达式 b) 每次迭代(之前评估增量表达式),更多细节在这里。
不同之处在于每个声明的变量的 scope。
在实践中,范围差异有许多有用的后果:
let 变量仅在其最近的封闭块 ({ ... }) 中可见。 let 变量只能在声明变量后出现的代码行中使用(即使它们被提升了!)。 let 变量不能被后续的 var 或 let 重新声明。全局 let 变量不会添加到全局窗口对象中。 let 变量很容易与闭包一起使用(它们不会导致竞争条件)。
let
施加的限制降低了变量的可见性并增加了提前发现意外名称冲突的可能性。这样可以更轻松地跟踪和推理变量,包括它们的reachability(帮助回收未使用的内存)。
因此,let
变量在用于大型程序或以新的和意想不到的方式组合独立开发的框架时不太可能导致问题。
如果您确定在循环中使用闭包 (#5) 或在代码中声明外部可见的全局变量 (#4) 时需要单绑定效果,var
可能仍然有用。如果 export
迁移出转译器空间并迁移到核心语言,则可能会替换使用 var
进行导出。
例子
<强> 1。不能在最近的封闭块之外使用: 此代码块将引发引用错误,因为 x
的第二次使用发生在用 let
声明的块之外:
{
let x = 1;
}
console.log(`x is ${x}`); // ReferenceError during parsing: "x is not defined".
相比之下,使用 var
的相同示例有效。
<强> 2。声明前不使用:
此代码块将在代码运行之前抛出 ReferenceError
,因为在声明之前使用了 x
:
{
x = x + 1; // ReferenceError during parsing: "x is not defined".
let x;
console.log(`x is ${x}`); // Never runs.
}
相反,具有 var
的相同示例在解析和运行时不会引发任何异常。
<强> 3。没有重新声明: 以下代码演示了使用 let
声明的变量以后可能不会重新声明:
let x = 1;
let x = 2; // SyntaxError: Identifier 'x' has already been declared
<强> 4。未附加到 window
的全局变量:
var button = "I cause accidents because my name is too common.";
let link = "Though my name is common, I am harder to access from other JS files.";
console.log(link); // OK
console.log(window.link); // undefined (GOOD!)
console.log(window.button); // OK
<强> 5。易于使用闭包: 用 var
声明的变量不适用于循环内的闭包。这是一个简单的循环,它输出变量 i
在不同时间点的值序列:
for (let i = 0; i < 5; i++) {
console.log(`i is ${i}`), 125/*ms*/);
}
具体来说,这会输出:
i is 0
i is 1
i is 2
i is 3
i is 4
在 JavaScript 中,我们经常在比创建它们的时间晚得多的时候使用变量。当我们通过传递给 setTimeout
的闭包来延迟输出来证明这一点时:
for (let i = 0; i < 5; i++) {
setTimeout(_ => console.log(`i is ${i}`), 125/*ms*/);
}
...只要我们坚持 let
,输出就保持不变。相反,如果我们使用 var i
代替:
for (var i = 0; i < 5; i++) {
setTimeout(_ => console.log(`i is ${i}`), 125/*ms*/);
}
...循环意外输出“i is 5”五次:
i is 5
i is 5
i is 5
i is 5
i is 5
var
而不是 let
,代码等效于: var i = 0; while (i < 5) { doSomethingLater(); i++; }
i
在闭包之外,并且在执行 doSomethingLater()
时,i
已经增加了 5 次,因此输出是 i is 5
五倍。通过使用 let
,变量 i
位于闭包内,因此每个异步调用都会获得自己的 i
副本,而不是使用使用 var
创建的“全局”副本。
for
语义的正常定义。一个更准确的转换,虽然更复杂,是经典的 for (var i = 0; i < 5; i++) { (function(j) { setTimeout(_ => console.log(
i is ${j}), 125/*ms*/); })(i); }
,它引入了一个“函数激活记录”来保存 i
的每个值,名称为 j
功能。
这是一个添加到其他人已经编写的内容的示例。假设您要创建一个函数数组 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
引用 adderFunctions
中每个匿名函数在循环结束时的 i
值 (1000)。这根本不是我们想要的:我们现在在内存中有一个包含 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。
let value = i;
。 for
语句创建一个词法块。
可能以下两个函数显示差异:
function varTest() {
var x = 31;
if (true) {
var x = 71; // Same variable!
console.log(x); // 71
}
console.log(x); // 71
}
function letTest() {
let x = 31;
if (true) {
let x = 71; // Different variable
console.log(x); // 71
}
console.log(x); // 31
}
ES6 引入了两个替代 var 的新关键字(let 和 const)。
当您需要块级减速时,您可以使用 let 和 const 而不是 var。
下表总结了 var、let 和 const 之间的区别
https://i.stack.imgur.com/GBn5a.jpg
var
的区别在于它们提升但不初始化为 undefined
值。如果它们不提升,它们将不会在封闭块中屏蔽同名变量:stackoverflow.com/q/63337235/2326961
函数 VS 块作用域:
var
和 let
的主要区别在于用 var
声明的变量是函数范围。而用 let
声明的函数是块作用域。例如:
function testVar () {
if(true) {
var foo = 'foo';
}
console.log(foo);
}
testVar();
// logs 'foo'
function testLet () {
if(true) {
let bar = 'bar';
}
console.log(bar);
}
testLet();
// reference error
// bar is scoped to the block of the if statement
带有 var
的变量:
当第一个函数 testVar
被调用时,用 var
声明的变量 foo 仍然可以在 if
语句之外访问。此变量 foo
将在 testVar
函数 范围内无处不在可用。
带有 let
的变量:
当第二个函数 testLet
被调用时,用 let
声明的变量 bar 只能在 if
语句中访问。因为用 let
声明的变量是块范围(其中块是大括号之间的代码,例如 if{}
、for{}
、function{}
)。
让变量不要被提升:
var
和 let
之间的另一个区别是使用 let
不被提升声明的变量。一个例子是说明这种行为的最佳方式:
let
不 的变量被提升:
console.log(letVar);
let letVar = 10;
// referenceError, the variable doesn't get hoisted
var
do 的变量被提升:
console.log(varVar);
var varVar = 10;
// logs undefined, the variable gets hoisted
全局让不附加到窗口:
在全局范围内使用 let
声明的变量(即不在函数中的代码)不会作为属性添加到全局 window
对象上。例如(此代码在全局范围内):
var bar = 5;
let foo = 10;
console.log(bar); // logs 5
console.log(foo); // logs 10
console.log(window.bar);
// logs 5, variable added to window object
console.log(window.foo);
// logs undefined, variable not added to window object
什么时候应该在 var 上使用 let?
尽可能使用 let
而不是 var
,因为它只是范围更具体。这减少了在处理大量变量时可能发生的潜在命名冲突。当您希望全局变量显式位于 window
对象上时,可以使用 var
(如果确实需要,请务必仔细考虑)。
let
很有趣,因为它允许我们执行以下操作:
(() => {
var count = 0;
for (let i = 0; i < 2; ++i) {
for (let i = 0; i < 2; ++i) {
for (let i = 0; i < 2; ++i) {
console.log(count++);
}
}
}
})();
这导致计数 [0, 7]。
然而
(() => {
var count = 0;
for (var i = 0; i < 2; ++i) {
for (var i = 0; i < 2; ++i) {
for (var i = 0; i < 2; ++i) {
console.log(count++);
}
}
}
})();
仅计数 [0, 1]。
看起来,至少在 Visual Studio 2015 和 TypeScript 1.5 中,“var”允许在一个块中多次声明相同的变量名,而“let”则不允许。
这不会产生编译错误:
var x = 1;
var x = 2;
这将:
let x = 1;
let x = 2;
这个解释摘自我在 Medium 上写的一篇文章:
提升是一种 JavaScript 机制,其中变量和函数声明由解析器移动到其作用域的顶部,解析器在 JavaScript 解释器开始实际代码执行之前将源代码读入中间表示。因此,实际上在哪里声明变量或函数并不重要,它们将被移动到其作用域的顶部,而不管它们的作用域是全局的还是局部的。这意味着 console.log (hi); var hi = "打个招呼";实际上被解释为 var hi = undefined;控制台.log(嗨); hi = "打个招呼";因此,正如我们刚才看到的,var 变量被提升到其作用域的顶部,并使用 undefined 的值进行初始化,这意味着我们可以在代码中实际声明它们之前实际分配它们的值,如下所示:hi = “打个招呼” console.log (hi); // 打个招呼 var hi;关于函数声明,我们可以在实际声明它们之前调用它们:sayHi(); // Hi function sayHi() { console.log('Hi'); };另一方面,函数表达式没有被提升,所以我们会得到以下错误:sayHi(); //输出:“TypeError: sayHi is not a function var sayHi = function() { console.log('Hi'); }; ES6 为 JavaScript 开发人员引入了 let 和 const 关键字。而 let 和 const 是块范围的,而不是作用域为 var 的函数在讨论它们的提升行为时不应该有所作为。我们将从最后开始,JavaScript 提升 let 和 const.console.log(hi); // 输出:在初始化之前无法访问 'hi' let hi = 'Hi'; 如上所示,let 不允许我们使用未声明的变量,因此解释器会显式输出一个引用错误,表明在初始化之前无法访问 hi 变量。如果我们更改也会出现同样的错误上面的 let to const console.log(hi); // 输出:在初始化之前无法访问 'hi' const hi = 'Hi'; 所以,底线,JavaScript 解析器搜索变量声明和函数并将它们提升到顶部在代码执行之前确定它们的范围,并在内存中为它们分配值,以防万一解释器在执行代码时会遇到它们,他会识别它们并能够使用它们分配的值执行代码。用 let 或 const 声明的变量在执行开始时保持未初始化,而用 var 声明的变量正在用 undefined 值初始化。我添加了这个视觉插图来帮助理解提升的变量和函数是如何保存在内存中的
var
是全局范围(可提升)变量。
let
和 const
是块作用域。
测试.js
{ 让 l = '让';常量 c = '常量'; var v = 'var'; v2 = 'var 2'; } console.log(v, this.v);控制台.log(v2, this.v2);控制台.log(l); // ReferenceError: l is not defined console.log(c); // ReferenceError: c 没有定义
使用let
时
let
关键字将变量声明附加到它所包含的任何块(通常是 { .. }
对)的范围内。换句话说,let
隐式劫持了任何块的范围以用于其变量声明。
let
变量不能在 window
对象中访问,因为它们不能被全局访问。
function a(){
{ // this is the Max Scope for let variable
let x = 12;
}
console.log(x);
}
a(); // Uncaught ReferenceError: x is not defined
使用var
时
var
并且 ES5 中的变量在函数中具有作用域,这意味着变量在函数内部有效,而不是在函数本身外部有效。
var
变量可以在 window
对象中访问,因为它们不能被全局访问。
function a(){ // this is the Max Scope for var variable
{
var x = 12;
}
console.log(x);
}
a(); // 12
如果您想了解更多,请继续阅读下面
关于范围的最著名的面试问题之一也可以满足以下 let
和 var
的确切用法;
使用 let
时
for (let i = 0; i < 10 ; i++) {
setTimeout(
function a() {
console.log(i); //print 0 to 9, that is literally AWW!!!
},
100 * i);
}
这是因为在使用 let
时,对于每个循环迭代,变量都是有作用域的并且有自己的副本。
使用 var
时
for (var i = 0; i < 10 ; i++) {
setTimeout(
function a() {
console.log(i); //print 10 times 10
},
100 * i);
}
这是因为在使用 var
时,对于每个循环迭代,变量都是有作用域的并且具有共享副本。
如果我正确阅读了规范,那么还可以利用 let
谢天谢地 来避免 self invoking functions 用于模拟仅限私有成员 - 一种流行的设计模式,它会降低代码可读性,使调试复杂化,没有增加真正的代码保护或其他好处——除了可能满足某人对语义的渴望,所以停止使用它。 /咆哮
var SomeConstructor;
{
let privateScope = {};
SomeConstructor = function SomeConstructor () {
this.someProperty = "foo";
privateScope.hiddenProperty = "bar";
}
SomeConstructor.prototype.showPublic = function () {
console.log(this.someProperty); // foo
}
SomeConstructor.prototype.showPrivate = function () {
console.log(privateScope.hiddenProperty); // bar
}
}
var myInstance = new SomeConstructor();
myInstance.showPublic();
myInstance.showPrivate();
console.log(privateScope.hiddenProperty); // error
请参阅“Emulating private interfaces”
let
提供? (我假设您的意思是具有“自我调用功能”的 IIFE。)
hiddenProperty
?您的“类”中的所有实例只有一个 hiddenProperty
。
let
的一些技巧:
1.
let statistics = [16, 170, 10];
let [age, height, grade] = statistics;
console.log(height)
2.
let x = 120,
y = 12;
[x, y] = [y, x];
console.log(`x: ${x} y: ${y}`);
3.
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined
let node = {
type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"
使用 let 的 getter 和 setter:
let jar = {
numberOfCookies: 10,
get cookies() {
return this.numberOfCookies;
},
set cookies(value) {
this.numberOfCookies = value;
}
};
console.log(jar.cookies)
jar.cookies = 7;
console.log(jar.cookies)
let { type, name, value } = node;
?您创建一个具有 3 个属性类型/名称/值的新对象,并使用来自节点的属性值初始化它们?
var
完美配合。
下面显示了 'let' 和 'var' 在范围内的不同之处:
let gfoo = 123;
if (true) {
let gfoo = 456;
}
console.log(gfoo); // 123
var hfoo = 123;
if (true) {
var hfoo = 456;
}
console.log(hfoo); // 456
由 let
定义的 gfoo
最初位于 全局范围 中,当我们在 if clause
内再次声明 gfoo
时,它的 范围已更改 并且当为该范围内的变量分配新值时,它不会影响全局范围。
而由 var
定义的 hfoo
最初位于 全局范围 中,但当我们在 if clause
中声明它时,它再次考虑全局范围 hfoo,尽管 var 已再次用于声明它。当我们重新分配它的值时,我们看到全局范围 hfoo 也受到了影响。这是主要区别。
我刚刚遇到一个用例,我必须使用 var
而不是 let
来引入新变量。这是一个案例:
我想用动态变量名创建一个新变量。
let variableName = 'a';
eval("let " + variableName + '= 10;');
console.log(a); // this doesn't work
var variableName = 'a';
eval("var " + variableName + '= 10;');
console.log(a); // this works
上面的代码不起作用,因为 eval
引入了一个新的代码块。使用 var
的声明将在此代码块之外声明一个变量,因为 var
在函数范围内声明了一个变量。
另一方面,let
在块范围内声明一个变量。因此,a
变量将仅在 eval
块中可见。
let
proposition 的重新声明是不允许的。
let 是 es6 的一部分。这些功能将以简单的方式解释差异。
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}
让 vs var。一切都与范围有关。
var 变量是全局的,基本上可以在任何地方访问,而 let 变量不是全局的,只存在直到右括号杀死它们。
请参阅下面的示例,并注意 Lion (let) 变量在两个 console.logs 中的行为方式有何不同;它超出了第二个 console.log 的范围。
var cat = "cat";
let dog = "dog";
var animals = () => {
var giraffe = "giraffe";
let lion = "lion";
console.log(cat); //will print 'cat'.
console.log(dog); //will print 'dog', because dog was declared outside this function (like var cat).
console.log(giraffe); //will print 'giraffe'.
console.log(lion); //will print 'lion', as lion is within scope.
}
console.log(giraffe); //will print 'giraffe', as giraffe is a global variable (var).
console.log(lion); //will print UNDEFINED, as lion is a 'let' variable and is now out of scope.
正如刚才提到的:
区别在于范围。 var 的范围是最近的功能块,而 let 的范围是最近的封闭块,它可以小于一个功能块。如果在任何块之外,两者都是全局的。让我们看一个例子:
示例 1:
在我的两个示例中,我都有一个函数 myfunc
。 myfunc
包含一个变量 myvar
等于 10。在我的第一个示例中,我检查 myvar
是否等于 10 (myvar==10
) 。如果是,我再次使用 var
关键字声明一个变量 myvar
(现在我有两个 myvar 变量)并为其分配一个新值(20)。在下一行中,我在控制台上打印它的值。在条件块之后,我再次在控制台上打印 myvar
的值。如果您查看 myfunc
的输出,myvar
的值等于 20。
https://i.stack.imgur.com/sWpnR.png
示例 2: 在我的第二个示例中,我没有在条件块中使用 var
关键字,而是使用 let
关键字声明 myvar
。现在,当我调用 myfunc
时,我得到两个不同的输出:myvar=20
和 myvar=10
。
所以区别很简单,即它的范围。
现在我认为使用 let
可以更好地为语句块定义变量范围:
function printnums()
{
// i is not accessible here
for(let i = 0; i <10; i+=)
{
console.log(i);
}
// i is not accessible here
// j is accessible here
for(var j = 0; j <10; j++)
{
console.log(j);
}
// j is accessible here
}
我认为人们会在这里开始使用 let ,以便他们在 JavaScript 中拥有类似的作用域,就像其他语言、Java、C# 等一样。
对 JavaScript 中的作用域没有清晰理解的人通常会更早地犯错误。
不支持使用 let
进行提升。
使用这种方法,JavaScript 中存在的错误将被消除。
请参阅 ES6 In Depth: let and const 以更好地理解它。
我想将这些关键字链接到执行上下文,因为执行上下文在所有这些中都很重要。执行上下文有两个阶段:创建阶段和执行阶段。此外,每个执行上下文都有一个变量环境和外部环境(它的词法环境)。
在执行上下文的创建阶段,var、let 和 const 仍将其变量存储在内存中,并且在给定执行上下文的变量环境中具有未定义的值。区别在于执行阶段。如果您在为其赋值之前使用使用 var 定义的变量的引用,它将只是未定义的。不会引发异常。
但是,在声明之前,您不能引用使用 let 或 const 声明的变量。如果您在声明之前尝试使用它,那么在执行上下文的执行阶段将引发异常。现在该变量仍将在内存中,由执行上下文的创建阶段提供,但引擎不允许您使用它:
function a(){
b;
let b;
}
a();
> Uncaught ReferenceError: b is not defined
使用 var 定义的变量,如果引擎在当前执行上下文的变量环境中找不到该变量,那么它将沿着作用域链(外部环境)检查该变量的外部环境变量环境。如果在那里找不到它,它将继续搜索范围链。 let 和 const 不是这种情况。
let 的第二个特点是它引入了块作用域。块由花括号定义。示例包括功能块、if 块、for 块等。当您在块内使用 let 声明变量时,该变量仅在块内可用。实际上,每次运行该块时,例如在 for 循环中,它都会在内存中创建一个新变量。
ES6 还引入了 const 关键字来声明变量。 const 也是块作用域。 let 和 const 的区别在于 const 变量需要使用初始化器来声明,否则会产生错误。
最后,当涉及到执行上下文时,用 var 定义的变量将附加到“this”对象。在全局执行上下文中,这将是浏览器中的窗口对象。这不是 let 或 const 的情况。
let
块表达式let (variable declaration) statement
是非标准的,将来会被删除,bugzilla.mozilla.org/show_bug.cgi?id=1023609。let
不与var
相同 --let
明确不会创建全局范围的引用:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…