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


4537

ECMAScript 6引入了该let声明

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

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


104
ECMAScript是标准,let并包含在第6版草案中,并且很可能会出现在最终规范中。
理查德·阿约特

5
有关ES6功能(包括let)的最新支持列表,请参见kangax.github.io/es5-compat-table/es6。在编写Firefox时,Chrome和IE11都支持它(尽管我相信FF的实现还不是很标准)。
Nico Burns 2014年

22
在最长的时间里,我不知道for循环中的var的作用域是它所包装的函数。我记得第一次搞清楚这一点,并认为它非常愚蠢。尽管现在知道由于不同的原因如何使用两者,以及在某些情况下您实际上可能想在for循环中使用var而不将其作用于块的范围,但我确实看到了一些功能。
埃里克·比沙德

1
这是一本很好的文章wesbos.com/javascript-scoping
onmyway133

1
解释
得很

Answers:


6094

范围规则

主要区别在于范围规则。用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]();由于匿名函数绑定到同一变量,因此调用时。

人们必须创建立即调用的函数以从循环中捕获正确的值,但这也很麻烦。

吊装

虽然用var关键字声明的变量被提升undefined在运行代码之前用初始化),这意味着即使在声明它们之前,也可以在其封闭范围内访问它们:

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

22
请记住,您可以随时创建块。function(){代码; {让inBlock = 5; }代码;};
平均乔

177
那么,让语句仅在某个块中不需要时才释放内存的目的是吗?
NoBugs 2013年

219
@NoBugs,是的,鼓励变量仅在需要它们的地方存在。
蝙蝠侠

67
let块表达式let (variable declaration) statement是非标准的,以后会被删除,bugzilla.mozilla.org/show_bug.cgi?id=1023609
Gajus 2014年

19
因此,我只是想不出使用var的任何情况。有人可以举例说明使用var的情况吗?
路易斯·西埃拉

621

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>


54
这真的很酷。我希望“ i”可以在循环体的外部定义,并包含在方括号内,并且不会在“ i”周围形成“ closure”。当然,您的示例证明了这一点。从语法的角度来看,我认为这有点令人困惑,但是这种情况非常普遍,因此以这种方式支持它是有意义的。非常感谢您提出这个建议。
卡罗尔·科伦达

9
IE 11支持let,但是它会警告所有按钮“ 6”。您是否有任何消息说let应该如何表现?
Jim Hunziker,2015年

10
喜欢你的答案看起来是正确的行为:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
吉姆Hunziker

11
确实,这是Javascript的一个常见陷阱,现在我可以明白为什么它let会真正有用。在循环中设置事件侦听器不再需要立即调用的函数表达式以i在每次迭代时进行局部作用域。
阿德里安·莫萨

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

198

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)了一篇非常详细的文章,阐述了为什么他认为情况并非如此

但是,今天绝对不是这种情况。实际上,我们实际上需要问自己使用该let语句是否安全。该问题的答案取决于您的环境:

  • 如果您正在编写服务器端JavaScript代码(Node.js),则可以安全地使用该let语句。

  • 如果您正在编写客户端JavaScript代码并使用基于浏览器的编译器(例如Traceurbabel-standalone),则可以安全地使用该let语句,但是就性能而言,代码可能不是最佳选择。

  • 如果您正在编写客户端JavaScript代码并使用基于Node的编译器(例如traceur shell脚本Babel),则可以安全地使用该let语句。并且由于您的浏览器仅会知道已转译的代码,因此应限制性能方面的弊端。

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

    仍然有些浏览器根本不支持let

在此处输入图片说明


如何跟踪浏览器支持

用于将上行的最新概述哪些浏览器支持let在你阅读这个答案时声明,请参阅Can I Use


(*)因为提升了 JavaScript变量,所以可以在声明全局和功能范围的变量之前对其进行初始化和使用。这意味着声明始终在作用域的顶部。

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


14
关于答案v4:i在功能块中无处不在!从undefined(由于吊装)开始,直到您分配一个值为止!ps:let也被吊起(在其包含块的顶部),但是会ReferenceError在第一次分配之前在块中给出一个何时引用。(ps2:我是一个支持分号的家伙,但您真的不需要在分号后加上分号)。话虽如此,感谢您添加有关支持的真实性检查!
GitaarLAB '16

@GitaarLAB:根据Mozilla开发人员网络的说法:“在ECMAScript 2015中,let绑定不受变量提升的约束,这意味着let声明不会移到当前执行上下文的顶部。” -无论如何,我对答案做了一些改进,以弄清let和之间的起吊行为之间的区别var
John Slegers '18

1
您的答案有了很大的改善(我彻底检查了)。请注意,您在注释中引用的相同链接也表示:“从开始直到初始化完成,(变量)变量位于“临时死区”中。这意味着已经在相关范围中保留了“标识符”(文本字符串“保留”以指向“某物”),否则它将成为根/主机/窗口范围的一部分。就我个人而言,“吊装”只不过是将声明的“标识符”保留/链接到其相关范围而已。不包括其初始化/分配/可修改性!
GitaarLAB'3

和.. + 1。您链接的那篇Kyle Simpson文章非常好,谢谢!关于“时间死区”又称“ TDZ”也很清楚。一个有趣的事情,我想补充:我读过关于MDN是letconst建议只使用当你真正需要它们的附加功能,因为执行/检查了这些额外的功能(如只写常量)导致“更多的工作'(以及作用域树中的其他作用域节点),以便(当前)引擎执行/检查/验证/设置。
GitaarLAB'3

1
请注意,MDN表示IE确实可以正确解释let。哪有 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Katinka海塞林克

146

下面是一个在解释let的关键字一些例子。

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

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

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


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

50
虽然说IE不支持它在技术上是正确的,但说它是仅适用于mozilla的扩展名则更正确。
olliej

55
@olliej,实际上Mozilla领先于游戏。参见ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf的
泰勒·

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


112

可接受的答案遗漏了一点:

{
  let a = 123;
};

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

19
接受的答案不会在其示例中解释这一点。可接受的答案仅在for循环初始化程序中进行了演示,从而极大地缩小了的局限性let。已投票。
乔恩·戴维斯

37
@ stimpy77它明确指出“ let的作用域是最近的封闭块”;是否需要包括所有体现的方式?
戴夫牛顿

6
有很多例子,但没有一个例子能很好地说明问题..我可能会同时赞成公认的答案和这个答案?
乔恩·戴维斯

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

81

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

循环内

letin循环声明的变量只能在该循环内部引用。

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-它具有块范围并且具有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

51

这是两者之间差异的示例(刚刚开始支持chrome):
在此处输入图片说明

如您所见,var j变量仍然具有for循环范围(Block Scope)之外的值,但是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);


2
我在这里看什么工具?
巴顿

20
Chrome devtools
vlio20

作为Cinnamon桌面小程序的开发人员,我还没有接触过如此出色的工具。
巴顿

48

有一些细微的差异- let作用域的行为更像变量作用域在任何其他语言中的作用。

例如,它的作用域为封闭的块,它们在声明之前不存在,等等。

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


11
还值得注意的是,ECMAScript是标准,let并包含在第6版草案中,并且很有可能会出现在最终规范中。
理查德·

23
那就是3年的差异:D
olliej

4
仅仅停留在这个问题上,在2012年仍然只有Mozilla浏览器支持let。Safari,IE和Chome都没有。
pseudosavant

2
意外地意外创建局部块范围的想法是一个好主意,请注意,let不要使用let在块顶部定义的变量。如果您的if语句不只是几行代码,您可能会忘记,只有在定义变量后才能使用该变量。大点!!!
埃里克·比斯哈德

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

26

主要的区别是范围的区别,而let只能在声明的范围内使用,例如在for循环中,例如var可以在循环外部访问。从MDN中的文档中(也有MDN的示例):

let允许您声明范围仅限于使用它的块,语句或表达式的变量。这与var不同关键字关键字在全局范围内或在整个函数本地定义变量,而不管块范围如何。

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
}`

在程序和函数的顶层,letvar不同,不会在全局对象上创建属性。例如:

var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

在块内使用时,让该变量的作用域限制在该块内。请注意var之间的区别,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网站的更多信息


24
  • 可变不吊装

    let不会葫芦他们出现在块的整个范围。相比之下,var可能葫芦如下。

    {
       console.log(cc); // undefined. Caused by hoisting
       var cc = 23;
    }
    
    {
       console.log(bb); // ReferenceError: bb is not defined
       let bb = 23;
    }

    其实,每@Bergi,两者varlet高挂

  • 垃圾收集

    块范围的let作用与关闭和回收垃圾的垃圾回收有关。考虑,

    function process(data) {
        //...
    }
    
    var hugeData = { .. };
    
    process(hugeData);
    
    var btn = document.getElementById("mybutton");
    btn.addEventListener( "click", function click(evt){
        //....
    });

    click处理程序回调并不需要hugeData在所有的变量。从理论上讲,process(..)运行后,可以对巨大的数据结构hugeData进行垃圾收集。但是,某些JS引擎可能仍必须保留这种庞大的结构,因为click函数在整个作用域内都是封闭的。

    但是,块作用域可以使这种庞大的数据结构被垃圾回收。

    function process(data) {
        //...
    }
    
    { // anything declared inside this block can be garbage collected
        let hugeData = { .. };
        process(hugeData);
    }
    
    var btn = document.getElementById("mybutton");
    btn.addEventListener( "click", function click(evt){
        //....
    });
  • let 循环

    let在循环中可以将其重新绑定到循环的每个迭代中,并确保从上一个循环迭代结束时重新为其分配值。考虑,

    // print '5' 5 times
    for (var i = 0; i < 5; ++i) {
        setTimeout(function () {
            console.log(i);
        }, 1000);  
    }

    但是,替换varlet

    // print 1, 2, 3, 4, 5. now
    for (let i = 0; i < 5; ++i) {
        setTimeout(function () {
            console.log(i);
        }, 1000);  
    }

    因为let使用a)初始化程序表达式b)每次迭代(之前是评估增量表达式)的名称创建一个具有这些名称的新词汇环境,所以此处有更多详细信息。


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

因此,让我们吊起,但不可用吗?与“未吊装”有什么不同?
N-ate

希望Brian或Bergi回来回答这个问题。是否悬挂了let的声明,而不是赋值?谢谢!
N-ate

1
@ N-ate,这是Bergi的一篇文章,也许您可​​以在其中找到答案。
zangw

有趣的是,甚至在出租时也被称为吊装。从技术上讲,我认为解析引擎正在对其进行预捕获,但是出于所有意图和目的,程序员应将其视为不存在。另一方面,var的提升对程序员有影响。
N-ate

19

这是一个示例,可以补充其他人已经写的内容。假设您要创建一个函数数组adderFunctions,其中每个函数都使用一个Number参数,并返回参数和该函数在数组中的索引的和。尝试adderFunctions使用var关键字生成循环不会像某些人天真的期望那样起作用:

// 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,每个函数闭包中的in引用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行为符合预期。

现在,图像混合的两种行为,你可能会看到它为什么不建议混合新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。


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

校正:从技术上讲,Arrow功能语法此处=>描述developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
巴顿

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

17

区别在于每个变量声明的变量的范围

实际上,范围差异会带来许多有用的后果:

  1. let变量仅在其最接近的封闭块({ ... })中可见。
  2. let变量仅可在声明变量发生的代码行中使用(即使已将其吊起)!)。
  3. let变量可以不被随后的重新声明varlet
  4. 全局let变量不会添加到全局window对象。
  5. let变量很容易与闭包一起使用(它们不会引起竞争条件)。

由于let降低了变量的可见性而施加了限制,并增加了提早发现意外的名称冲突的可能性。这样可以更轻松地跟踪和推断变量,包括变量的可达性(帮助回收未使用的内存)。

因此,let在大型程序中使用变量或以新的出乎意料的方式组合独立开发的框架时,变量不太可能引起问题。

var如果您确定要在循环中使用闭包(#5)或在代码中声明外部可见的全局变量(#4)时希望单绑定效果,则可能仍然有用。var如果export移出转译程序空间并转移到核心语言中,则可能会替代使用for出口。

例子

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为5”五次:

i is 5
i is 5
i is 5
i is 5
i is 5

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

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

14

可能以下两个功能显示出差异:

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
}

13

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


2
这是我第一次见到有人喜欢像可变阴影一样的行为。不,我们的目的不是为了让阴影
约翰·豪根兰

1
目的?它是一个结构,您可以随意使用它,一种有趣的方式是这样的。
德米特里

13

功能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被调用时,用声明的变量foo var仍可在if语句外部访问。该变量foo将在函数范围内的任何位置可用。testVar

变量let

当第二个函数testLet被调用时,用声明的变量bar let只能在if语句内部访问。因为变量与声明的let块作用域(其中块是大括号中的代码例如if{}for{}function{})。

let 变量不被悬挂:

var和之间的另一个区别let是声明let 没有声明的变量。一个示例是说明此行为的最佳方法:

的变量let 不会被吊起:

console.log(letVar);

let letVar = 10;
// referenceError, the variable doesn't get hoisted

的变量var 确实被吊起:

console.log(varVar);

var varVar = 10;
// logs undefined, the variable gets hoisted

Global let不隶属于window

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


什么时候应该let用完var

使用letvar,只要你能,因为它只是范围的更具体。这样可以减少在处理大量变量时可能发生的命名冲突。var当您希望将全局变量显式地放在window对象上时可以使用。(如果确实需要,请务必仔细考虑)。


9

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

这不会产生编译错误:

var x = 1;
var x = 2;

这将:

let x = 1;
let x = 2;

9

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

let并且const是块作用域。

test.js

{
    let l = 'let';
    const c = 'const';
    var v = 'var';
    v2 = 'var 2';
}

console.log(v, this.v);
console.log(v2, this.v2);
console.log(l); // ReferenceError: l is not defined
console.log(c); // ReferenceError: c is not defined


8

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


7

如果我没看错规范,那么let 值得庆幸的是,也可以利用它来避免用于模拟仅私有成员的自调用函数 - 一种流行的设计模式,它降低了代码的可读性,使调试复杂化,没有增加任何实际的代码保护或其他好处-除了可能使某人满意之外对语义的渴望,所以停止使用它。/ rant

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

请参阅“ 模拟专用接口


您能详细说明一下立即调用函数表达式如何不提供“代码保护” let吗?(我假设您的意思是IIFE具有“自我调用功能”。)
Robert Siemer

以及为什么要hiddenProperty在构造函数中进行设置?hiddenProperty“类”中的所有实例只有一个。
罗伯·西默


4

一些技巧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"

的getter和setter方法let

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个属性类型/名称/值的新对象,并使用来自node的属性值对其进行初始化?
AlainIb

在示例3中,您将重新声明导致异常的节点。所有这些示例也可以完美地工作var
Rehan Haider

4

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

4

ES6引入了两个新的关键字(letconst)替代var

当需要块级减速时,可以使用let和const代替var。

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

在此处输入图片说明


3

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
}

3

下面显示了'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

gfoo,通过定义let最初是在全球范围内,而当我们声明gfoo里面又if clause范围变化,当一个新的值赋给变量该范围内它不会影响全局范围。

hfoo,定义的by var最初位于全局范围内,但是当我们在内声明它时if clause,它将再次考虑全局范围hfoo,尽管再次使用var声明了它。当我们重新分配它的值时,我们看到全局范围hfoo也受到了影响。这是主要区别。


2

正如刚才提到的:

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

范例1:

在我的两个示例中,我都有一个函数myfuncmyfunc包含一个myvar等于10 的变量。在我的第一个示例中,我检查是否myvar等于10(myvar==10)。如果是,我会myvar使用var关键字声明一个变量 (现在我有两个myvar变量),并为其分配一个新值(20)。在下一行中,我在控制台上打印其值。在条件块之后,我再次myvar在控制台上打印的值。如果查看的输出myfuncmyvar则其值等于20。

让关键字

例2: 在第二个示例中var,我没有在条件块中使用keyword,而是声明myvar使用letkeyword。现在,当我打电话给myfunc 我时,会得到两个不同的输出:myvar=20myvar=10

因此,区别非常简单,即其范围。


3
请不要张贴代码的图片,这在SO上被认为是不好的做法,因为将来的用户将无法搜索到它(以及可访问性问题)。同样,此答案没有添加其他答案尚未解决的内容。
inostia

2

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

在执行上下文的创建阶段,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并非如此。


2

我认为术语和大多数示例有点让人不知所措,我个人与之不同的主要问题是了解“块”是什么。在某种程度上,我意识到,一个块将是除IF声明以外的任何花括号。{函数或循环的左括号将定义一个新块,其中定义的任何内容在同一事物(函数或循环)let的右括号后将不可用};考虑到这一点,更容易理解:

let msg = "Hello World";

function doWork() { // msg will be available since it was defined above this opening bracket!
  let friends = 0;
  console.log(msg);

  // with VAR though:
  for (var iCount2 = 0; iCount2 < 5; iCount2++) {} // iCount2 will be available after this closing bracket!
  console.log(iCount2);
  
    for (let iCount1 = 0; iCount1 < 5; iCount1++) {} // iCount1 will not be available behind this closing bracket, it will return undefined
  console.log(iCount1);
  
} // friends will no be available after this closing bracket!
doWork();
console.log(friends);


1

现在,我认为使用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深度:let和const可以更好地理解它。


要深入了解它,请参阅链接-davidwalsh.name/for-and-against-let
swaraj patil 2016年

1

本文明确定义了var,let和const之间的区别

const 是表示不会重新分配标识符的信号。

let表示可以重新分配变量的信号,例如循环中的计数器或算法中的值交换。它还表明该变量将仅在其定义的块中使用,而该块并不总是整个包含函数。

var现在是在JavaScript中定义变量时可用的最弱信号。该变量可能会也可能不会被重新分配,并且该变量可能会或可能不会用于整个功能,或者仅出于块或循环的目的。

https://medium.com/javascript-scene/javascript-es6-var-let-or-const-ba58b8dcde75#.esmkpbg9b

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.