使用“let”和“var”有什么区别?

javascript scope ecmascript-6 var let

ECMAScript 6 引入了 the let statement

我听说它被描述为一个局部变量,但我仍然不太确定它与 var 关键字的行为有何不同。

有什么区别?什么时候应该使用 let 而不是 var

ECMAScript 是标准,let 包含在 6th edition draft 中,很可能会出现在最终规范中。

有关 ES6 功能(包括 let)的最新支持矩阵,请参阅 kangax.github.io/es5-compat-table/es6。在编写 Firefox 时,Chrome 和 IE11 都支持它(虽然我相信 FF 的实现不是很标准)。

很长一段时间以来,我都不知道 for 循环中的 var 的作用域是它所包含的函数。我记得第一次弄清楚这一点并认为这很愚蠢。我确实看到了一些力量,尽管现在知道如何出于不同的原因使用这两者,以及在某些情况下,您可能实际上希望在 for 循环中使用 var 而不是将其限定为块。

随着 ES6 功能支持的改进,有关 ES6 采用的问题将焦点从功能支持转移到性能差异。因此,here's a site I found benchmarking performance differences between ES6 and ES5。请记住,随着引擎针对 ES6 代码进行优化,这可能会随着时间的推移而改变。

3
38 revs, 28 users 27%

范围规则

主要区别在于范围规则。由 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();

创建全局对象属性

在顶层,letvar 不同,它不会在全局对象上创建属性:

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' 已经被声明

请记住,您可以随时创建块。函数(){代码;{让inBlock = 5; } 代码; };

那么 let 语句的目的仅仅是在某个块中不需要时释放内存吗?

@NoBugs,是的,鼓励变量仅在需要的地方存在。

let 块表达式 let (variable declaration) statement 是非标准的,将来会被删除,bugzilla.mozilla.org/show_bug.cgi?id=1023609

顶级范围的 letvar 相同 -- let 明确不会创建全局范围的引用:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

N
Nash Bridges

let 还可用于避免闭包问题。它绑定新值而不是保留旧引用,如下面的示例所示。

for(var i=1; i<6; i++) { $("#div" + i).click(function () { console.log(i); }); }

点击每个数字会登录到控制台:< /p>

1
2
3
4
5

上面的代码演示了一个经典的 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>

1
2
3
4
5

这其实很酷。我希望“i”在循环体之外定义,包含在括号内,并且不会在“i”周围形成“闭包”。当然你的例子证明不是这样。我认为从语法的角度来看这有点令人困惑,但这种情况非常普遍,以这种方式支持它是有意义的。非常感谢您提出这个问题。

IE 11 支持 let,但它会为所有按钮提示“6”。您是否有任何消息来源说明 let 的行为方式?

看起来您的答案是正确的行为:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

事实上,这是 Javascript 中的一个常见缺陷,现在我明白为什么 let 会非常有用。在循环中设置事件侦听器不再需要在每次迭代时立即调用函数表达式来确定本地范围 i

使用“let”只是推迟了这个问题。所以每次迭代都会创建一个私有的独立块作用域,但是“i”变量仍然可能被块内的后续更改破坏,(授予迭代器变量通常不会在块内更改,但块内其他声明的 let 变量很可能be) 并且块内声明的任何函数在调用时都可能破坏块内声明的其他函数的“i”值,因为它们共享相同的私有块范围,因此对“i”的引用相同。

J
John Slegers

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 这意味着声明总是位于范围的顶部。

(**) 块范围的变量不被提升

关于答案 v4:i 在功能块中随处可见!它以 undefined 开始(由于提升),直到您分配一个值! ps:let 也被提升(到它的包含块的顶部),但在第一次分配之前在块中引用时会给出 ReferenceError。 (ps2:我是一个支持分号的人,但你真的不需要在 block 后面加分号)。话虽如此,感谢您添加有关支持的现实检查!

@GitaarLAB:根据 Mozilla Developer Network:“在 ECMAScript 2015 中,let 绑定不受变量提升的影响,这意味着 let 声明不会移动到当前执行上下文的顶部。” - 无论如何,我对我的答案进行了一些改进,以澄清 letvar 之间提升行为的区别!

您的答案改善了很多(我彻底检查了)。请注意,您在评论中引用的同一链接还说:“(let)变量从块开始到处理初始化都处于“临时死区”。这意味着“标识符”(指向“某物”的文本字符串“保留”)已经保留在相关范围内,否则它将成为根/主机/窗口范围的一部分。对我个人而言,“提升”只是将声明的“标识符”保留/链接到其相关范围;不包括它们的初始化/分配/可修改性!

还有..+1。您链接的 Kyle Simpson 文章是一篇优秀的阅读文章,谢谢! “时间死区”又名“TDZ”也很清楚。我想补充一件有趣的事情:我在 MDN 上读到过,letconst建议仅在您真正需要它们的附加功能时使用,因为强制/检查这些额外的功能功能(如只写常量)导致(当前)引擎执行/检查/验证/设置的“更多工作”(以及范围树中的其他范围节点)。

请注意,MDN 说 IE 确实正确解释了 let。它是哪一个? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

J
Jack Bashford

这是一个带有一些示例的 explanation of the let keyword

let 的工作方式与 var 非常相似。主要区别在于 var 变量的作用域是整个封闭函数

Wikipedia 上的 This table 显示了哪些浏览器支持 Javascript 1.7。

请注意,只有 Mozilla 和 Chrome 浏览器支持它。 IE、Safari 和其他可能没有。

链接文档中的关键文本似乎是,“let 的工作方式与 var 非常相似。主要区别在于 var 变量的范围是整个封闭函数”。

@olliej,实际上 Mozilla 领先于游戏。见第 19 页,共 ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

@TylerCrompton 这只是保留多年的一组词。当 mozilla 添加 let 时,它纯粹是一个 mozilla 扩展,没有相关规范。 ES6 应该为 let 语句定义行为,但这是在 mozilla 引入语法之后出现的。记住 moz 也有 E4X,它完全死了,只有 moz。

IE11 添加了对 let msdn.microsoft.com/en-us/library/ie/dn342892%28v=vs.85%29.aspx 的支持

现在let支持目前所有最新的浏览器,除了 Opera、Blackberry 和QQ浏览器。

W
William

接受的答案缺少一点:

{
  let a = 123;
};

console.log(a); // ReferenceError: a is not defined

接受的答案并未在其示例中解释这一点。接受的答案仅在 for 循环初始化程序中进行了演示,大大缩小了 let 限制的应用范围。赞成。

@stimpy77 它明确指出“let 的范围为最近的封闭块”;是否需要包括清单的每一种方式?

有很多例子,但没有一个能正确地证明这个问题..我可能已经对接受的答案和这个答案都投了赞成票?

这一贡献表明,“块”可以简单地是一组括在括号中的行;即它不需要与任何类型的控制流、循环等相关联。

A
Abdul Rehman

块范围

使用 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

在函数内部

在函数内部(但在块外部),letvar 具有相同的范围。

(() => {
  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

常量

constlet 非常相似——它是块范围的并且具有 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
H
Hasan Sefa Ozalp

在最基本的条件下,

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

v
vlio20

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);

A
Alireza

主要区别在于 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 的更多信息

我不知道最后一个例子是否准确。因为通过不是从函数而是直接命令行调用它,它仍然被认为是同一函数的一部分。因此,如果您从函数外部调用它,它的行为方式不应相同。

J
Jack Bashford

有一些细微的差别 — let 作用域的行为更像是变量作用域在任何其他语言中的行为或多或少。

例如,它的范围是封闭块,它们在声明之前不存在,等等。

但是值得注意的是,let 只是较新的 Javascript 实现的一部分,并且具有不同程度的 browser support

还值得注意的是,ECMAScript 是标准,let 包含在 6th edition draft 中,并且很可能会出现在最终规范中。

刚刚偶然发现这个问题,在 2012 年仍然只有 Mozilla 浏览器支持 let。 Safari、IE 和 Chome 都没有。

意外创建部分块范围的想法是一个好点,请注意,let 不会提升,使用由块顶部定义的 let 定义的变量。如果您的 if 语句不仅仅是几行代码,您可能会忘记在定义该变量之前不能使用该变量。好点!!!

这是 let 和 var 之间最重要的区别之一,它不在公认的答案中,哈哈。特别是考虑到由于提升和范围界定可能发生的众多错误。如果您不提及提升,我觉得 let 和 var 之间没有太多区别。

@EricB:是和否:“在 ECMAScript 2015 中,let 会将变量提升 到块的顶部。但是,在变量声明之前引用块中的变量会导致 ReferenceError(我的注释:而不是旧的 undefined)。从块开始到处理声明,该变量处于“临时死区”中。” “switch 语句,因为只有一个底层块”也是如此。来源:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

C
Community

变量不提升 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) 每次迭代(之前评估增量表达式),更多细节在这里。

是的,它们被吊起,但由于(鼓声)时间死区而表现得好像没有被吊起 - 一个非常戏剧性的标识符名称,在声明之前无法访问:-)

所以 let 被提升了,但是不可用?这与“未吊装”有何不同?

希望布赖恩或伯吉回来回答这个问题。是否提升了 let 的声明,但没有提升分配?谢谢!

有趣的是,当涉及到 let 时,它甚至被称为提升。我知道从技术上讲解析引擎正在预先捕获它,但出于所有意图和目的,程序员应该将它视为它不存在。另一方面,提升 var 对程序员有影响。

m
mormegil

不同之处在于每个声明的变量的 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

#5 不是由竞争条件引起的。通过使用 var 而不是 let,代码等效于: var i = 0; while (i < 5) { doSomethingLater(); i++; } i 在闭包之外,并且在执行 doSomethingLater() 时,i 已经增加了 5 次,因此输出是 i is 5 五倍。通过使用 let,变量 i 位于闭包内,因此每个异步调用都会获得自己的 i 副本,而不是使用使用 var 创建的“全局”副本。

@DanielT .:我不认为将变量定义从循环初始化程序中提取出来的转换可以解释任何事情。这只是 for 语义的正常定义。一个更准确的转换,虽然更复杂,是经典的 for (var i = 0; i < 5; i++) { (function(j) { setTimeout(_ => console.log(i is ${j}), 125/*ms*/); })(i); },它引入了一个“函数激活记录”来保存 i 的每个值,名称为 j功能。

a
abroz

这是一个添加到其他人已经编写的内容的示例。假设您要创建一个函数数组 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

这一次,ifor 循环的每次迭代中都会反弹。现在,每个函数在创建函数时都保留 i 的值,并且 adderFunctions 的行为符合预期。

现在,通过图像混合这两种行为,您可能会明白为什么不建议在同一脚本中将较新的 letconst 与较旧的 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。

@aborz:第二个示例中的匿名函数语法也很酷。这正是我在 C# 中所习惯的。我今天学到了一些东西。

更正:从技术上讲,此处描述的箭头函数语法 => developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

实际上,您不需要 let value = i;for 语句创建一个词法块。

P
Peter Mortensen

可能以下两个函数显示差异:

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
}
T
TylerH

ES6 引入了两个替代 var 的新关键字(let 和 const)。

当您需要块级减速时,您可以使用 let 和 const 而不是 var。

下表总结了 var、let 和 const 之间的区别

https://i.stack.imgur.com/GBn5a.jpg

提升的列不正确。他们都提升变量。与 var 的区别在于它们提升但不初始化为 undefined 值。如果它们不提升,它们将不会在封闭块中屏蔽同名变量:stackoverflow.com/q/63337235/2326961

S
Suraj Rao

函数 VS 块作用域:

varlet 的主要区别在于用 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{})。

让变量不要被提升:

varlet 之间的另一个区别是使用 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(如果确实需要,请务必仔细考虑)。

P
Peter Mortensen

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]。

是的,它增加了比必要的更多的混乱,而且应该没有。

@Bekim Bacaj 这是一个人为的例子,说明了 let 和 var 之间的区别。也就是说,在循环结束时,由 let 声明的变量超出范围,而 var 仍然存在。由程序员根据他们的意图和先前的经验来决定他们选择将哪些构造合并到他们的代码中。这个例子的目的不是引起混淆,而是给读者一个起点,以创造性的方式使用 let 结构来熟悉它。

J
John Slegers

看起来,至少在 Visual Studio 2015 和 TypeScript 1.5 中,“var”允许在一个块中多次声明相同的变量名,而“let”则不允许。

这不会产生编译错误:

var x = 1;
var x = 2;

这将:

let x = 1;
let x = 2;
R
Ran Turner

这个解释摘自我在 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 值初始化。我添加了这个视觉插图来帮助理解提升的变量和函数是如何保存在内存中的

Stack Overflow 上需要对引用的内容进行正确的归属。这包括明确披露从属关系并在从其他位置复制内容时清楚地显示……即使您是作者。

M
Moslem Shahsavan

var 是全局范围(可提升)变量。

letconst 是块作用域。

测试.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 没有定义

A
Ankur Soni

使用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

如果您想了解更多,请继续阅读下面

关于范围的最著名的面试问题之一也可以满足以下 letvar 的确切用法;

使用 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 时,对于每个循环迭代,变量都是有作用域的并且具有共享副本。

D
Daniel Sokolowski

如果我正确阅读了规范,那么还可以利用 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

P
Peter Mortensen

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 个属性类型/名称/值的新对象,并使用来自节点的属性值初始化它们?

在示例 3 中,您正在重新声明导致异常的节点。所有这些示例也与 var 完美配合。

这不能回答问题;它可以从每个代码块正在做什么的解释中受益。

P
Piklu Dey

下面显示了 '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 也受到了影响。这是主要区别。

A
Ashish Kamble

我刚刚遇到一个用例,我必须使用 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 块中可见。

你什么时候必须创建一个动态变量名,并且以后必须访问它?创建一个对象并为其分配键和值要好得多。

事实上,这是因为 JavaScript let proposition 的重新声明是不允许的。

V
Vipul Jain

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
}
d
daCoda

让 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.
N
N Randhawa

正如刚才提到的:

区别在于范围。 var 的范围是最近的功能块,而 let 的范围是最近的封闭块,它可以小于一个功能块。如果在任何块之外,两者都是全局的。让我们看一个例子:

示例 1:

在我的两个示例中,我都有一个函数 myfuncmyfunc 包含一个变量 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=20myvar=10

所以区别很简单,即它的范围。

请不要发布代码图片,这被认为是对 SO 的不良做法,因为将来的用户将无法搜索它(以及可访问性问题)。同样,这个答案没有添加其他答案尚未解决的任何内容。

P
Peter Mortensen

现在我认为使用 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 以更好地理解它。

D
Daniel Viglione

我想将这些关键字链接到执行上下文,因为执行上下文在所有这些中都很重要。执行上下文有两个阶段:创建阶段和执行阶段。此外,每个执行上下文都有一个变量环境和外部环境(它的词法环境)。

在执行上下文的创建阶段,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 的情况。