题 使用“let”和“var”在JavaScript中声明变量有什么区别?


ECMAScript 6介绍 let 声明。我听说它被描述为一个“本地”变量,但我仍然不太确定它的行为方式与它有什么不同 var 关键词。

有什么区别?什么时候应该 let 被用完了 var


3259
2018-04-17 20:09


起源


ECMAScript是标准和 let 包括在内 第6版草案 并且很可能在最终规范中。 - Richard Ayotte
看到 kangax.github.io/es5-compat-table/es6 获取ES6功能的最新支持矩阵(包括let)。在撰写Firefox时,Chrome和IE11都支持它(虽然我认为FF的实现不是很标准)。 - Nico Burns
在最长的时间里,我不知道for循环中的变量是否包含在它所包含的函数中。我记得第一次搞清楚它并认为它非常愚蠢。我确实看到了一些力量虽然现在知道如何使用这两个因为不同的原因以及在某些情况下你可能实际上想要在for循环中使用var而不是将其作用于块。 - Eric Bishard
随着ES6功能支持的改进,有关ES6采用的问题将重点从功能支持转移到性能差异。因此, 这是一个网站,我发现ES6和ES5之间的基准性能差异。请记住,随着引擎优化ES6代码,这可能会随着时间的推移而发生变化。 - timolawl
这是一个非常好的阅读 wesbos.com/javascript-scoping - onmyway133


答案:


区别在于范围界定。 var 范围限定到最近的功能块和 let 范围是最近的 封闭 块,可以小于功能块。如果在任何区域之外,两者都是全球

另外,用变量声明的变量 let 在它们的封闭区域中声明之前是不可访问的。如演示中所示,这将引发ReferenceError异常。

演示 

var html = '';

write('#### global ####\n');
write('globalVar: ' + globalVar); //undefined, but visible

try {
  write('globalLet: ' + globalLet); //undefined, *not* visible
} catch (exception) {
  write('globalLet: exception');
}

write('\nset variables');

var globalVar = 'globalVar';
let globalLet = 'globalLet';

write('\nglobalVar: ' + globalVar);
write('globalLet: ' + globalLet);

function functionScoped() {
  write('\n#### function ####');
  write('\nfunctionVar: ' + functionVar); //undefined, but visible

  try {
    write('functionLet: ' + functionLet); //undefined, *not* visible
  } catch (exception) {
    write('functionLet: exception');
  }

  write('\nset variables');

  var functionVar = 'functionVar';
  let functionLet = 'functionLet';

  write('\nfunctionVar: ' + functionVar);
  write('functionLet: ' + functionLet);
}

function blockScoped() {
  write('\n#### block ####');
  write('\nblockVar: ' + blockVar); //undefined, but visible

  try {
    write('blockLet: ' + blockLet); //undefined, *not* visible
  } catch (exception) {
    write('blockLet: exception');
  }

  for (var blockVar = 'blockVar', blockIndex = 0; blockIndex < 1; blockIndex++) {
    write('\nblockVar: ' + blockVar); // visible here and whole function
  };

  for (let blockLet = 'blockLet', letIndex = 0; letIndex < 1; letIndex++) {
    write('blockLet: ' + blockLet); // visible only here
  };

  write('\nblockVar: ' + blockVar);

  try {
    write('blockLet: ' + blockLet); //undefined, *not* visible
  } catch (exception) {
    write('blockLet: exception');
  }
}

function write(line) {
  html += (line ? line : '') + '<br />';
}

functionScoped();
blockScoped();

document.getElementById('results').innerHTML = html;
<pre id="results"></pre>

全球:

在功能块之外使用它们非常相似。

let me = 'go';  // globally scoped
var i = 'able'; // globally scoped

但是,全局变量定义了 let 不会作为全局属性添加 window 像那些定义的对象 var

console.log(window.me); // undefined
console.log(window.i); // 'able'

功能:

在功能块中使用时它们是相同的。

function ingWithinEstablishedParameters() {
    let terOfRecommendation = 'awesome worker!'; //function block scoped
    var sityCheerleading = 'go!'; //function block scoped
}

块:

这是区别。 let 只有在 for() 循环和 var 是整个功能可见的。

function allyIlliterate() {
    //tuce is *not* visible out here

    for( let tuce = 0; tuce < 5; tuce++ ) {
        //tuce is only visible in here (and in the for() parentheses)
        //and there is a separate tuce variable for each iteration of the loop
    }

    //tuce is *not* visible out here
}

function byE40() {
    //nish *is* visible out here

    for( var nish = 0; nish < 5; nish++ ) {
        //nish is visible to the whole function
    }

    //nish *is* visible out here
}

重声明:

假设严格的模式, var 将允许您在同一范围内重新声明相同的变量。另一方面, let 将不会:

'use strict';
let me = 'foo';
let me = 'bar'; // SyntaxError: Identifier 'me' has already been declared
'use strict';
var me = 'foo';
var me = 'bar'; // No problem, `me` is replaced.

4571
2018-05-27 10:16



那么let语句的目的是为了在某个块中不需要时释放内存吗? - NoBugs
@NoBugs,是的,鼓励变量仅在需要的地方存在。 - batman
let阻止表达 let (variable declaration) statement 是非标准的,将来会被删除, bugzilla.mozilla.org/show_bug.cgi?id=1023609。 - Gajus
他们是全球范围的差异: let 不要向全局变量添加属性 2ality.com/2015/02/es6-scoping.html#the_global_object - Yukulélé
@ThinkingStiff:请复习 这个元文章,并权衡所提出的技术问题。 - Robert Harvey♦


let 也可用于避免闭包问题。它绑定了新的价值,而不是保留旧的参考,如下面的例子所示。

DEMO

for(var i = 1; i < 6; i++) {
  document.getElementById('my-element' + i)
    .addEventListener('click', function() { alert(i) })
}

上面的代码演示了一个典型的JavaScript闭包问题参考 i 变量存储在单击处理程序闭包中,而不是实际值 i

每个单击处理程序都将引用同一个对象,因为只有一个计数器对象可以容纳6,因此每次单击时会得到6个。

一般的解决方法是将其包装在匿名函数中并传递 i 作为论点。现在也可以通过使用来避免这些问题 let 代替 var 如下面的代码所示。

DEMO (在Chrome和Firefox 50中测试过)

'use strict';

for(let i = 1; i < 6; i++) {
  document.getElementById('my-element' + i)
    .addEventListener('click', function() { alert(i) })
}

451
2018-04-17 20:11



这实际上很酷。我希望在循环体外部定义“i”包含在括号内,而不是在“i”周围形成“闭包”。当然,你的例子证明不是这样。从语法的角度来看,我认为这有点令人困惑,但这种情况非常普遍,以这种方式支持它是有意义的。非常感谢你提出这个问题。 - Karol Kolenda
IE 11支持 let,但它会警告所有按钮“6”。你有任何消息来源说怎么做 let 应该表现吗? - Jim Hunziker
看起来你的答案是正确的行为: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - Jim Hunziker
事实上,这是Javascript中常见的陷阱,现在我可以看到原因 let 会非常有用。在循环中设置事件侦听器不再需要立即调用的函数表达式来进行本地作用域 i 在每次迭代。 - Adrian Moisa
使用“let”只是推迟了这个问题。因此每次迭代都会创建一个私有的独立块作用域,但是“i”变量仍然可能被块中的后续更改破坏,(授予迭代器变量不是 平时 在块中更改,但块中的其他声明的let变量可能是)并且在块中声明的任何函数在被调用时可以破坏块中声明的其他函数的“i”值,因为它们 做 共享相同的私有块范围,因此对“i”的引用相同。 - gary


这是一个 解释 let 关键词 举一些例子。

让我的工作非常像var。主要区别在于var变量的范围是整个封闭函数

这张桌子 在Wikipedia上显示哪些浏览器支持Javascript 1.7。

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


131
2018-02-23 18:35



来自链接文档的文本的关键位似乎是“让我们非常像var。主要区别在于var变量的范围是整个封闭函数”。 - Michael Burr
虽然说IE不支持它在技术上是正确的,但是说它只是一个mozilla扩展更为正确。 - olliej
@olliej,实际上Mozilla就是领先于游戏。见第19页 ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf - Tyler Crompton
@TylerCrompton,这只是多年来保留的单词集。当mozilla添加时,让它纯粹是一个mozilla扩展,没有相关的规范。 ES6应该定义let语句的行为,但是在mozilla引入语法之后。记住moz也有E4X,它完全死了,只有moz。 - olliej
IE11增加了支持 let  msdn.microsoft.com/en-us/library/ie/dn342892%28v=vs.85%29.aspx - eloyesp


有什么区别 let 和 var

  • 使用a定义的变量 var 声明始终是已知的 功能 它从函数的开头定义。 (*)
  • 使用a定义的变量 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大师 凯尔辛普森 写 一篇非常精细的文章,说明为什么不是这样

然而,今天绝对不是这样。事实上,我们实际上需要问自己,使用它是否安全 let 声明。这个问题的答案取决于您的环境:

  • 如果您正在编写服务器端JavaScript代码(Node.js的),你可以安全地使用 let 声明。

  • 如果您正在编写客户端JavaScript代码并使用转换器(如 Traceur),你可以安全地使用 let 声明,但是你的代码在性能方面可能不是最优的。

  • 如果您正在编写客户端JavaScript代码而不使用转换器,则需要考虑浏览器支持。

今天,2018年6月8日,仍然有一些浏览器不支持 let

enter image description here


如何跟踪浏览器支持

有关哪些浏览器支持的最新概述 let 在您阅读此答案时的陈述,请参阅 这个 Can I Use 页


(*)全局和功能范围的变量可以在声明之前初始化和使用,因为JavaScript变量是 悬挂 这意味着声明始终位于范围的顶部。

(**)未提升块范围变量


116
2018-06-02 20:59



关于答案v4: i 在功能块中到处都知道!它始于 undefined (由于提升),直到你分配一个值! PS: let 也被悬挂(到它的包含块的顶部),但会给出一个 ReferenceError在第一次分配之前在块中引用时。 (ps2:我是一个支持分号的人,但你真的不需要一个分号后的分号)。话虽如此,感谢您添加关于支持的现实检查! - GitaarLAB
@GitaarLAB:根据 Mozilla开发者网络 :“在ECMAScript 2015中,让绑定不受变量提升的影响,这意味着让声明不会移动到当前执行上下文的顶部。” - 无论如何,我对我的回答做了一些改进,应该澄清两者之间提升行为的区别 let 和 var! - John Slegers
你的答案改进了很多(我彻底检查了)。请注意,您在评论中引用的相同链接也会说:“(let)变量位于”暂时死区“中 块开始 直到处理初始化。“这意味着'标识符'(文本字符串'保留'指向'某事') 已经 保留在相关范围内,否则它将成为root / host / window范围的一部分。就我个人而言,“吊装”仅仅意味着将声明的“标识符”保留/链接到相关范围;排除他们的初始化/分配/可修改性! - GitaarLAB
和.. + 1。您链接的Kyle Simpson文章是一个 优秀 读,谢谢你!关于“时间死区”又名“TDZ”也很清楚。我想补充一件有趣的事情:我在MDN上读过这篇文章 let 和 const 是 建议仅在您确实需要其他功能时使用,因为强制执行/检查这些额外功能(如只写const)会导致(当前)引擎执行/检查/验证/设置“更多工作”(以及范围树中的其他范围 - 节点) 。 - GitaarLAB


接受的答案缺少一点:

{
  let a = 123;
};

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

98
2018-04-17 21:38



接受的答案解释了这一点。 - TM.
接受的答案并未在其示例中解释这一点。接受的答案只在一个证明了它 for 循环初始化器,大大缩小了应用范围的局限性 let。 Upvoted。 - Jon Davis
@stimpy77它明确指出“let是作用于最近的封闭块”;是否需要包含所有表现形式的方式? - Dave Newton
有很多例子,没有一个能够正确地证明这个问题。我可能已经对已接受的答案和这个答案进行了投票? - Jon Davis
这个贡献表明“块”可以简单地用括号括起来的一组线;即它不需要与任何类型的控制流,循环等相关联。 - webelo


有一些微妙的差异 - let 范围界定的行为更像是变量范围,或多或少任何其他语言。

例如它适用于封闭块,它们在声明之前不存在,等等。

但值得注意的是 let 只是较新的Javascript实现的一部分,并具有不同程度的 浏览器支持


40
2018-03-06 10:41



值得注意的是ECMAScript是标准的 let 包括在内 第6版草案 并且很可能在最终规范中。 - Richard Ayotte
这是3年的差异:D - olliej
刚刚对这个问题进行了调查,并且在2012年仍然只有Mozilla浏览器才支持 let。 Safari,IE和Chome都没有。 - pseudosavant
在事故中意外创建局部挡块范围的想法是一个好点,请注意, let 不提升,使用由a定义的变量 let 在块的顶部定义。如果你有 if声明不仅仅是几行代码,您可能会忘记在定义之后才能使用该变量。好点!!! - Eric Bishard
@EricB:是和否:“在ECMAScript 2015中, let  将提升 变量到块的顶部。但是,在变量声明之前引用块中的变量会导致a 引发ReferenceError (我的笔记:而不是好老 undefined)。变量位于从块开始到处理声明的“临时死区”中。“切换语句也是如此,因为只有一个底层块”。来源: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - GitaarLAB


以下是两者之间差异的示例(支持刚启动的chrome): enter image description here

正如你所看到的那样 var j 变量仍然具有for循环范围之外的值(块范围),但是 let i 变量在for循环范围之外是未定义的。

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


39
2017-11-23 22:52



我在这看什么工具? - Barton
Chrome devtools - vlio20
作为Cinnamon桌面小程序的开发者,我没有接触过这样的闪亮工具。 - Barton


let

阻止范围

用变量声明的变量 let 关键字是块范围的,这意味着它们仅在  他们被宣布。

在顶层(在功能之外)

在顶层,使用声明的变量 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 in循环只能在该循环内引用。

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

时间死区

因为 时间死区,变量声明使用 let 在声明它们之前无法访问。尝试这样做会引发错误。

console.log(noTDZ); // undefined
var noTDZ = 43;
console.log(hasTDZ); // ReferenceError: hasTDZ is not defined
let hasTDZ = 42;

没有重新申报

您不能多次声明相同的变量 let。您也无法使用声明变量 let 与使用声明的另一个变量具有相同的标识符 var

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

const 非常相似 let-it是块范围的并且有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

const a; // SyntaxError: Missing initializer in const declaration

38
2018-01-17 15:11