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

What is the difference between "let" and "var"?

提问人:TM. 提问时间:4/18/2009 最后编辑:U. WindlTM. 更新时间:10/25/2023 访问量:2243915

问:

ECMAScript 6 引入了 let 语句

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

有什么区别?什么时候应该用代替?letvar

JavaScript 范围 ecmascript-6 var let

评论


答:

184赞 Ben S 4/18/2009 #1

下面是 let 关键字的解释和一些示例。

let工作方式非常像.主要区别在于变量的作用域是整个封闭函数varvar

维基百科上的这张表显示了哪些浏览器支持 Javascript 1.7。

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

评论

7赞 Michael Burr 4/18/2009
链接文档中的关键文本似乎是,“let 的工作方式非常像 var。主要区别在于 var 变量的作用域是整个封闭函数”。
59赞 Tyler Crompton 6/19/2012
@olliej,实际上Mozilla只是领先于游戏。请参阅第 19 页,共 ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
1赞 olliej 7/12/2012
@TylerCrompton这只是保留多年的一组词。当mozilla添加let时,它纯粹是一个mozilla扩展,没有相关的规范。 ES6应该定义let语句的行为,但这是在mozilla引入语法之后出现的。请记住,moz 也有 E4X,它完全死了,只有 moz。
11赞 eloyesp 12/24/2013
IE11 添加了对 msdn.microsoft.com/en-us/library/ie/dn342892%28v=vs.85%29.aspx 的支持let
2赞 Shapon Pal 1/8/2019
现在支持除Opera,Blackberry和QQ浏览器之外的所有最新浏览器。let
64赞 olliej 4/18/2009 #2

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

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

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

评论

14赞 Richard Ayotte 3/31/2012
还值得注意的是,ECMAScript 是标准,包含在第 6 版草案中,并且很可能出现在最终规范中。let
8赞 pseudosavant 7/14/2012
只是在这个问题上徘徊,在2012年仍然只有Mozilla浏览器支持。Safari、IE 和 Chome 都没有。let
4赞 Eric Bishard 5/7/2015
意外地创建部分块范围的想法是一个很好的观点,请注意,不要提升,使用由块顶部定义的变量定义。如果语句不仅仅是几行代码,则可能会忘记在定义该变量之前不能使用该变量。好点!!letletif
1赞 Jay 6/22/2015
这是 let 和 var 之间最重要的区别之一,它不在公认的答案中哈哈。特别是考虑到由于提升和范围界定而可能发生的许多错误。我觉得如果你不提到吊装,let 和 var 之间没有太多区别。
4赞 GitaarLAB 5/21/2016
@EricB:是和否:“在 ECMAScript 2015 中,会将变量提升到块的顶部。但是,在变量声明之前引用块中的变量会导致 ReferenceError(我的笔记:而不是 good old )。从块开始到声明被处理,变量都处于'时间死区'。“switch 语句,因为只有一个底层块”也是如此。资料来源:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...letundefined
8152赞 39 revs, 29 users 27%ThinkingStiff #3

范围规则

主要区别在于范围规则。由 关键字声明的变量的作用域为直接函数体(因此称为函数作用域),而变量的作用域为 表示的直接封闭块(因此是块作用域)。varlet{ }

function run() {
  var foo = "Foo";
  let bar = "Bar";

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

run();

将关键字引入语言的原因是函数范围令人困惑,并且是 JavaScript 中错误的主要来源之一。let

另一个 Stack Overflow 问题中看一下这个例子:

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

人们必须创建立即调用的函数才能从循环中捕获正确的值,但这也是毛茸茸的。

提升

使用 keyword 声明的变量被提升和初始化,这意味着即使在声明它们之前,它们也可以在其封闭范围内访问,但是它们的值是在到达声明语句之前:varundefined

function checkHoisting() {
  console.log(foo); // undefined
  var foo = "Foo";
  console.log(foo); // Foo
}

checkHoisting();

let变量被提升,但在计算其定义之前不会初始化。在初始化之前访问它们会导致 .据说该变量从块开始到处理声明语句都处于时间死区ReferenceError

function checkHoisting() {
  console.log(foo); // ReferenceError
  let foo = "Foo";
  console.log(foo); // Foo
}

checkHoisting();

创建全局对象属性

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

var foo = "Foo"; // globally scoped
let bar = "Bar"; // globally scoped but not part of the global object

console.log(window.foo); // Foo
console.log(window.bar); // undefined

重新声明

在严格模式下,将允许您在同一作用域中重新声明相同的变量,同时引发 SyntaxError。varlet

'use strict';
var foo = "foo1";
var foo = "foo2"; // No problem, 'foo1' is replaced with 'foo2'.

let bar = "bar1"; 
let bar = "bar2"; // SyntaxError: Identifier 'bar' has already been declared

评论

73赞 average Joe 12/14/2012
请记住,您可以随时创建块。function() { code;{ let inBlock = 5; } code;
254赞 NoBugs 6/7/2013
那么,let 语句的目的是否只是为了在某个块中不需要时释放内存?
298赞 batman 6/7/2013
@NoBugs,是的,我们鼓励变量只存在于需要的地方。
74赞 Gajus 12/17/2014
let块表达式是非标准的,将来会被删除,bugzilla.mozilla.org/show_bug.cgi?id=1023609let (variable declaration) statement
10赞 Eric Bishard 5/7/2015
尽管不推荐使用 let 封闭块,但您可以通过创建带有大括号的显式块来执行相同的操作。{ let bar = foo; let foo = bar; ; ; console.log(bar + foo); }只需将 let 放在代码块的顶部,用大括号括起来即可。codemore code
27赞 abroz 8/18/2014 #4

这里有一个例子,可以添加到其他人已经写过的内容上。假设您要创建一个函数数组,其中每个函数都接受一个 Number 参数,并返回参数的总和以及数组中函数的索引。尝试使用关键字进行循环生成不会像某人天真地期望的那样工作:adderFunctionsadderFunctionsvar

// 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

上面的过程不会生成所需的函数数组,因为 的范围超出了创建每个函数的块的迭代。相反,在循环结束时,每个函数的闭包是指 中每个匿名函数在循环结束时的值 (1000)。这根本不是我们想要的:我们现在在内存中有一个包含 1000 个不同函数的数组,它们的行为完全相同。如果我们随后更新 的值,突变将影响所有 .iforiiadderFunctionsiadderFunctions

但是,我们可以使用关键字重试: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

这一次,是循环每次迭代的反弹。现在,每个函数都保留创建函数时的值,并按预期运行。iforiadderFunctions

现在,图像混合了这两种行为,您可能会明白为什么不建议在同一脚本中混合较新的和较旧的。这样做可能会导致一些非常令人困惑的代码。letconstvar

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

不要让这种情况发生在你身上。使用轻铸机。

注意:这是一个教学示例,旨在演示循环中的 / 行为,并使用易于理解的函数闭包。这将是一种可怕的数字添加方式。但是,在匿名函数闭包中捕获数据的一般技术可能会在现实世界的其他上下文中遇到。YMMV。varlet

评论

2赞 Barton 2/20/2015
@aborz:在第二个示例中,匿名函数语法也非常酷。这正是我在 C# 中习惯的。我今天学到了一些东西。
0赞 Barton 3/16/2015
更正:从技术上讲,此处描述的箭头函数语法 => developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
3赞 Toothbrush 10/23/2015
实际上,您不需要.该语句创建一个词法块。let value = i;for
69赞 vlio20 3/6/2015 #5

以下是两者之间差异的示例:
enter image description here

如您所见,该变量在 for 循环范围(块范围)之外仍然具有一个值,但该变量在 for 循环范围之外未定义。var jlet i

"use strict";
console.log("var:");
for (var j = 0; j < 2; j++) {
  console.log(j);
}

console.log(j);

console.log("let:");
for (let i = 0; i < 2; i++) {
  console.log(i);
}

console.log(i);

821赞 Gurpreet Singh 5/27/2015 #6

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 闭包问题。对变量的引用存储在单击处理程序闭包中,而不是 的实际值。ii

每个单击处理程序都将引用同一个对象,因为只有一个计数器对象包含 6 个,因此每次单击都会获得 6 个。

一般的解决方法是将其包装在匿名函数中并作为参数传递。现在也可以通过使用代替来避免此类问题,如下面的代码所示。iletvar

(在 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>

评论

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

公认的答案缺少一点:

{
  let a = 123;
};

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

评论

21赞 Jon Davis 9/22/2015
公认的答案在其示例中没有解释这一点。公认的答案仅在循环初始值设定器中演示了它,大大缩小了 限制的应用范围。点赞。forlet
45赞 Dave Newton 4/1/2016
@stimpy77 它明确指出“let 的范围限定为最近的封闭块”;是否需要将表现的每一种方式都包括在内?
9赞 Jon Davis 4/1/2016
有很多例子,但没有一个能正确地证明这个问题。我可能已经对接受的答案和这个答案都投了赞成票?
6赞 webelo 11/22/2017
这一贡献表明,“块”可以简单地是一组用括号括起来的线;也就是说,它不需要与任何类型的控制流、循环等相关联。
16赞 RDoc 8/11/2015 #8

至少在 Visual Studio 2015 TypeScript 1.5 中,“var”允许在一个块中对同一变量名称进行多个声明,而“let”则不允许。

这不会生成编译错误:

var x = 1;
var x = 2;

这将:

let x = 1;
let x = 2;
34赞 zangw 1/17/2016 #9
  • 变量未吊装

    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){
        //....
    });
    

    处理程序回调根本不需要该变量。从理论上讲,在运行之后,庞大的数据结构可能会被垃圾回收。但是,某些 JS 引擎可能仍然必须保留这个庞大的结构,因为该函数在整个范围内都有闭包。clickhugeDataprocess(..)hugeDataclick

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

    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 循环

    letin the loop 可以将其重新绑定到循环的每次迭代,确保从上一次循环迭代结束时重新为其分配值。考虑

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

    因为创建一个新的词法环境,这些名称是 a) 初始值设定项表达式 b) 每次迭代(在计算增量表达式之前),所以更多详细信息在这里。let

评论

6赞 Drenai 12/31/2016
是的,它们被吊起,但由于(鼓声)时间死区,它们表现得好像没有被吊起一样 - 一个非常戏剧性的名称,用于在声明之前无法访问的标识符:-)
333赞 John Slegers 2/24/2016 #10

和 和有什么不一样?letvar

  • 使用语句定义的变量从函数的开头开始,在整个定义它的函数中都是已知的。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

在这里,我们可以看到我们的变量只在第一个 for 循环中已知,而不是在之前和之后。然而,我们的变量在整个函数中是已知的。ji

此外,请注意,块范围的变量在声明之前是未知的,因为它们没有被提升。您也不得在同一块中重新声明相同的块范围变量。这使得块作用域变量比全局变量或函数作用域变量更不容易出错,后者是悬挂的,在多个声明的情况下不会产生任何错误。


今天使用安全吗?let

有些人会争辩说,将来我们将只使用 let 语句,而 var 语句将过时。JavaScript 大师凯尔·辛普森 (Kyle Simpson) 写了一篇非常详尽的文章,解释了为什么他认为情况并非如此

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

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

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

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

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

    还有一些浏览器根本不支持:let

enter image description here


如何跟踪浏览器支持

有关在您阅读此答案时哪些浏览器支持该声明的最新概述,请参阅此“我可以使用吗”页面let


(*) 全局变量和函数范围的变量可以在声明之前初始化和使用,因为 JavaScript 变量是提升的。这意味着声明始终移动到作用域的顶部。

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

评论

23赞 GitaarLAB 5/21/2016
关于答案 v4:在功能块中随处可见!它从(由于吊装)开始,直到您分配一个值!ps:也被提升(到它包含块的顶部),但在第一次赋值之前在块中引用时会给出一个。(ps2:我是一个支持分号的人,但你真的不需要在块后加分号)。话虽如此,感谢您添加有关支持的现实检查!iundefinedletReferenceError
0赞 John Slegers 2/27/2018
@GitaarLAB : 根据 Mozilla 开发者网络的说法:“在 ECMAScript 2015 中,let 绑定不受变量提升的约束,这意味着 let 声明不会移动到当前执行上下文的顶部。 - 无论如何,我对我的答案做了一些改进,应该澄清 和 之间提升行为的差异!letvar
2赞 GitaarLAB 3/2/2018
你的答案改善了很多(我彻底检查了)。请注意,您在评论中引用的同一链接还说:“(let)变量从块开始到初始化处理都处于”时间死区“中。这意味着“标识符”(指向“某物”的文本字符串“保留”)保留在相关作用域中,否则它将成为根/主机/窗口作用域的一部分。就我个人而言,“提升”无非是将声明的“标识符”保留/链接到其相关范围;不包括它们的初始化/赋值/可修改性!
0赞 GitaarLAB 3/2/2018
和。。+1.您链接的凯尔·辛普森(Kyle Simpson)文章是一篇很棒的读物,谢谢!关于“时间死区”又名“TDZ”也很清楚。我想补充的一件有趣的事情:我已经在 MDN 上读到了这一点,并且建议仅在您实际需要它们的附加功能时才使用,因为强制执行/检查这些额外功能(例如只写 const)会导致“更多工作”(以及作用域树中的额外作用域节点)用于(当前)引擎强制执行/检查/验证/设置。letconst
2赞 Katinka Hesselink 2/6/2019
请注意,MDN 表示 IE 确实正确解释了 let。它是什么?developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/......
-2赞 Gurucharan M K 5/19/2016 #11

ECMAScript 6 又添加了一个关键字来声明除 “let” 之外的 “const” 之外的变量。

在“var”上引入“let”和“const”的主要目标是具有块范围而不是传统的词法范围。我的文章非常简要地解释了“var”和“let”之间的区别,并且还涵盖了对“const”的讨论

3赞 swaraj patil 7/1/2016 #12

现在我认为使用以下命令更好地将变量范围限定为语句块: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,这样他们就会像其他语言、Java、C# 等一样在 JavaScript 中拥有类似的范围。

对 JavaScript 中的范围不清楚了解的人过去常常犯这个错误。

不支持使用 进行吊装。let

通过这种方法,JavaScript 中存在的错误将被删除。

请参考 ES6 In Depth: let 和 const 以更好地理解它。

评论

0赞 VLAZ 9/18/2023
"使用 let 不支持吊装。变量是用 let 还是 const hoisted 声明的?- 声明是提升的,而不是初始化的。
17赞 Dmytro 7/8/2016 #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++);
            }
        }
    }
})();
.as-console-wrapper { max-height: 100% !important; }

这导致计数 [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]。

评论

0赞 Bekim Bacaj 10/5/2021
是的,它增加了不必要的混乱,而且不应该有。
0赞 Dmytro 10/6/2021
@Bekim Bacaj 这是一个人为的例子,说明了 let 和 var 之间的区别。也就是说,在循环结束时,let 声明的变量超出范围,而 var 保持不变。由程序员根据他们的意图和先前的经验决定他们选择将哪些结构合并到他们的代码中。这个例子的目的不是引起混淆,而是给读者一个起点,让他们以创造性的方式玩转 let 结构,以熟悉它。
7赞 zloctb 7/22/2016 #14

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

评论

0赞 AlainIb 6/15/2017
请问这是什么意思?您创建一个具有 3 个属性类型/名称/值的新对象,并使用 Node 的属性值初始化它们?let { type, name, value } = node;
1赞 Rehan Haider 1/9/2019
在示例 3 中,您正在重新声明导致异常的节点。这些示例也非常适合。var
0赞 TylerH 4/14/2021
这并不能回答问题;它可以从对每个代码块正在做什么的解释中受益。
0赞 VLAZ 9/18/2023
1.、2.和3。不依赖于 .这是在破坏。不是声明所固有的。您也可以使用解构。此外,它根本不是“hack”,而是官方语言规范的一部分。 jsbin.com/tibiquqohe/1/edit?js,console 与“Getter and setter with let:”部分基本相同,但它展示了对象初始化语法。同样,与 的任何机制无关。jsbin.com/qobeyamoda/1/edit?js,console这个答案根本没有解决这个问题。letletvarlet
11赞 Daniel Sokolowski 10/14/2016 #15

如果我没看错规范,那么谢天谢地,还可以利用它来避免用于模拟私有成员的自调用函数 - 一种流行的设计模式,它降低了代码的可读性,使调试复杂化,没有增加真正的代码保护或其他好处 - 除了可能满足某人对语义的渴望,所以停止使用它。 /咆哮let

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

请参阅'模拟专用接口'

评论

0赞 Robert Siemer 3/1/2020
您能否详细说明一下立即调用的函数表达式如何不提供“代码保护”,而如何提供“代码保护”?(我假设你的意思是具有“自调用功能”的 IIFE。let
0赞 Robert Siemer 3/1/2020
为什么要在构造函数中设置?“类”中的所有实例只有一个。hiddenPropertyhiddenProperty
151赞 Michał Perłakowski 11/24/2016 #16

let

块范围

使用关键字声明的变量是块范围的,这意味着它们仅在声明它们的中可用。let

在顶层(函数外部)

在顶层,声明的变量 using 不会在全局对象上创建属性。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

循环内部

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

带闭合的循环

如果使用 instead 而不是 in 循环,则每次迭代都会得到一个新变量。这意味着您可以在循环中安全地使用闭包。letvar

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

不重新申报

不能使用 多次声明同一变量。您也不能使用与使用 声明的另一个变量使用相同的标识符来声明一个变量。letletvar

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与 — 它是块范围的,并且具有 TDZ。然而,有两件事是不同的。let

无需重新分配

声明的变量 using 无法重新赋值。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
2赞 anandharshan 12/27/2016 #17

本文清楚地定义了 var、let 和 const 之间的区别

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

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

var现在是定义变量时可用的最弱信号 在 JavaScript 中。变量可以重新赋值,也可以不赋值,并且 变量可以用于也可以不用于整个函数,或者仅用于 块或循环的用途。

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

68赞 Alireza 3/22/2017 #18

主要区别在于作用域差异,而 let 只能在它声明的作用域内可用,例如在 for 循环中,var 可以在循环之外访问。来自 MDN 中的文档(示例也来自 MDN):

let 允许您声明范围仅限于使用它的块、语句或表达式的变量。这与 var 关键字不同,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
}`

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

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

在块内使用时,let 将变量的范围限制为该块。请注意 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......欲了解更多信息,请访问巴别塔网站

评论

0赞 ACopeLan 8/29/2020
我不知道最后一个例子是否准确。因为不是从函数而是从直接命令行调用它,它仍然被认为是同一函数的一部分。因此,如果从函数外部调用它,则它的行为方式不应相同。
34赞 mormegil 5/22/2017 #19

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

在实践中,范围的差异会带来许多有用的后果:

  1. let变量仅在其最近的封闭块 () 中可见。{ ... }
  2. let变量只能在声明变量出现的代码行中可用(即使它们被提升了!
  3. let变量不能由后续的 或 重新声明。varlet
  4. 全局变量不会添加到全局对象中。letwindow
  5. let变量易于与闭包一起使用(它们不会导致争用条件)。

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

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

var如果您确定在循环中使用闭包 (#2) 或在代码 (#2) 中声明外部可见的全局变量时需要单绑定效果,则可能仍然有用。如果 export 从转译器空间迁移到核心语言中,则 for exports 的使用可能会被取代。var

例子

1. 在最近的封闭块之外不使用:此代码块将抛出引用错误,因为第二次使用 of 发生在用 :xlet

{
    let x = 1;
}
console.log(`x is ${x}`);  // ReferenceError during parsing: "x is not defined".

相比之下,同样的例子有效。var

2. 声明前不使用:此代码块将在代码运行之前抛出一个,因为在声明之前使用:
ReferenceErrorx

{
    x = x + 1;  // ReferenceError during parsing: "x is not defined".
    let x;
    console.log(`x is ${x}`);  // Never runs.
}

相比之下,相同的示例在不引发任何异常的情况下进行解析和运行。var

3. 无需重新声明:以下代码演示了声明为 with 的变量以后可能不会重新声明:let

let x = 1;
let x = 2;  // SyntaxError: Identifier 'x' has already been declared

4. 未附加到窗口的全局变量:

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. 易于与闭包一起使用:用 声明的变量不能很好地用于循环内的闭包。下面是一个简单的循环,它输出变量在不同时间点的值序列:vari

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

...只要我们坚持使用,输出就保持不变。相反,如果我们改用:letvar 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

评论

11赞 Daniel T. 6/2/2017
#5 不是由争用条件引起的。通过使用 代替 ,代码等价于 : 在闭包之外,并且在执行时已经递增了 5 倍,因此输出是 5 倍。通过使用 ,该变量位于闭包内,因此每个异步调用都会获得自己的副本,而不是使用使用 创建的“全局”副本。varletvar i = 0; while (i < 5) { doSomethingLater(); i++; }idoSomethingLater()ii is 5letiivar
0赞 mormegil 7/25/2017
@DanielT.:我不认为将变量定义从循环初始值设定项中提升出来的转换可以解释任何事情。这只是 语义的正常定义。一个更准确的转换,虽然更复杂,是经典的 i is ${j},它引入了一个“函数激活记录”,以保存每个值和函数内部的名称。forfor (var i = 0; i < 5; i++) { (function(j) { setTimeout(_ => console.log(), 125/*ms*/); })(i); }ij
11赞 Moslem Shahsavan 10/28/2017 #20

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

let并且是块范围。const

测试.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

-2赞 Nurlan 5/18/2018 #21

MDN 中查看此链接

let x = 1;

if (x === 1) {
let x = 2;

console.log(x);
// expected output: 2
}

console.log(x);
// expected output: 1

评论

0赞 pushkin 6/1/2018
用语言解释正在做什么也会有所帮助,尽管这会使它成为这里另一个答案的重复let
0赞 Thomas Darvik 9/27/2018
他想说的是,外部定义了变量。-语句将触发 (因为 .现在是棘手的部分,这也是您需要跟踪的主要原因 vs .里面他试图设置,在将打印 2 中,但是,“在外面”的 if 仍然有值,所以另一个给出 1,因为 “全局” 的值仍然是 。答案没有解释这一点,所以在我看来不应该被认为是一个好的答案。letifx=1ifx===1 is trueletvarifx=2console.log(x)x1console.log(x)x1
9赞 Ankur Soni 5/22/2018 #22

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

varES5 中的变量在函数中具有作用域,这意味着变量在函数内有效,而不是在函数本身之外有效。

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

评论

0赞 VLAZ 9/18/2023
"let 变量不能在 window 对象中访问,因为它们不能全局访问。不可以,声明可以是全局的。但是,它们不会附加到全局对象let
3赞 N Randhawa 8/7/2018 #23

如上所述:

区别在于范围界定。 范围限定为最近的函数 块,范围限定为最近的封闭块,该块 可以小于功能块。两者都是全球性的,如果没有任何的话 块。让我们看一个例子:varlet

示例1:

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

let keyword

示例2:在我的第二个示例中,我没有在条件块中使用关键字,而是使用 关键字 声明。现在,当我调用时,我得到两个不同的输出:和 .varmyvarletmyfuncmyvar=20myvar=10

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

评论

4赞 inostia 8/25/2018
请不要发布代码图片,这在 SO 上被认为是不好的做法,因为未来的用户无法搜索到它(以及可访问性问题)。同样,这个答案没有添加其他答案尚未解决的内容。
19赞 Willem van der Veen 9/9/2018 #24

函数 VS 块范围:

和之间的主要区别在于,声明的变量是函数范围的。而声明的函数是块范围的。例如:varletvarlet

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

当第一个函数被调用时,用 声明的变量 foo 仍然可以在语句外部访问。此变量在函数范围内的任何位置都可用。testVarvariffootestVar

带有 let 的变量:

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

let变量不会被提升:

和之间的另一个区别是声明了 don't get hoisted 的变量。示例是说明此行为的最佳方式:varletlet

带有 DON'T GET HOISTED 的变量:let

console.log(letVar);

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

变量 do get hoisted:var

console.log(varVar);

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

Global 不依附于:letwindow

在全局作用域中声明的变量(即不在函数中的代码)不会作为全局对象的属性添加。例如(此代码在全局范围内):letwindow

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

尽可能使用,因为它只是范围更具体。这减少了在处理大量变量时可能发生的潜在命名冲突。 当您希望全局变量显式位于对象上时,可以使用(如果确实有必要,请始终仔细考虑)。letvarvarwindow

评论

0赞 VLAZ 9/18/2023
"let variables don't be hoist:变量是用 let 还是 const hoisted 声明的? - 声明是提升的,而不是初始化的。这就是 Temporal Dead Zone 存在的全部原因 - 因为运行时可以在到达声明之前知道它。因此,知道这是一个错误,因为该变量在声明之前就被引用了。因为宣言是吊起来的。let
3赞 Daniel Viglione 2/14/2019 #25

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

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

评论

0赞 VLAZ 9/18/2023
"使用 var 定义的变量将附加到“this”对象。这是不正确的 - 它发生在全局上下文中,并且只是因为在全局上下文中与全局对象相同。没有将声明附加到其他机制。恰好是两种不同机制的横截面。jsbin.com/yujezitafo/1/edit?js,consolethisvarthis
0赞 VLAZ 9/18/2023
"如果在那里找不到 [var declaration],它将继续搜索作用域链。let 和 const 的情况并非如此。这也是不正确的 - 仍将检查范围链或声明 jsbin.com/murobobiqe/1/edit?js,consoleletconst
0赞 VLAZ 9/18/2023
"let 和 const 的区别在于,const 变量需要使用初始值设定项进行声明,否则会产生错误。这是一个倒退的解释。主要区别在于不允许重新分配。因此,由于它不能重新分配,因此需要立即初始化。否则,未初始化的声明将是一个错误,正如您所期望的那样,因为您以后无法赋值。const
-1赞 Mile Mijatović 2/17/2019 #26

const name = 'Max';
let age = 33;
var hasHobbies = true;

name = 'Maximilian';
age = 34;
hasHobbies = false;

const summarizeUser = (userName, userAge, userHasHobby) => {
    return (
    'Name is ' +
    userName +
    ', age is ' +
    userAge +
    ' and the user has hobbies: ' +
    userHasHobby
    );
}

console.log(summarizeUser(name, age, hasHobbies));

从运行上面的代码可以看出,当您尝试更改变量时,将出现错误const

尝试覆盖常量“name”。

TypeError:对 const 'name' 的赋值无效。

但看看变量。let

首先我们声明 ,然后分配一些其他值 ,这是可以的;当我们尝试更改变量时,我们没有任何错误let age = 33age = 34;let

评论

0赞 TylerH 4/14/2021
代码应该在 SO 帖子中写出来,而不是通过屏幕截图共享。
0赞 VLAZ 9/18/2023
这个答案没有解释 和 之间的区别是什么。充其量,它表明不能被覆盖,这不是一回事。letvarconst
3赞 Lucian 3/12/2019 #27

由于我目前正在尝试深入了解 JavaScript,我将分享我的简短研究,其中包含一些已经讨论过的优秀文章以及从不同角度进行的其他一些细节。

如果我们理解函数块作用域之间的区别,那么理解 varlet 之间的区别会更容易。

让我们考虑以下情况:

(function timer() {
    for(var i = 0; i <= 5; i++) {
        setTimeout(function notime() { console.log(i); }, i * 1000);
    }
})();


   Stack            VariableEnvironment //one VariablEnvironment for timer();
                                       // when the timer is out - the value will be the same for each iteration
5. [setTimeout, i]  [i=5] 
4. [setTimeout, i]  
3. [setTimeout, i]
2. [setTimeout, i]
1. [setTimeout, i]
0. [setTimeout, i]

####################    

(function timer() {
    for (let i = 0; i <= 5; i++) {
        setTimeout(function notime() { console.log(i); }, i * 1000);
    }
})();

   Stack           LexicalEnvironment - each iteration has a new lexical environment
5. [setTimeout, i]  [i=5]       
                      LexicalEnvironment 
4. [setTimeout, i]    [i=4]     
                        LexicalEnvironment 
3. [setTimeout, i]      [i=3]       
                         LexicalEnvironment 
2. [setTimeout, i]       [i=2]
                           LexicalEnvironment 
1. [setTimeout, i]         [i=1]
                             LexicalEnvironment 
0. [setTimeout, i]           [i=0]

当被调用时,将创建一个 ExecutionContext,它将包含 VariableEnvironment 和与每次迭代对应的所有 LexicalEnvironmentstimer()

还有一个更简单的例子

功能范围

function test() {
    for(var z = 0; z < 69; z++) {
        //todo
    }
    //z is visible outside the loop
}

块范围

function test() {
    for(let z = 0; z < 69; z++) {
        //todo
    }
    //z is not defined :(
}

简而言之,let 和 var 之间的区别在于 var 是函数范围的,let 是块范围的。

6赞 daCoda 4/18/2019 #28

让 vs var。这一切都与范围有关。

var 变量是全局的,基本上可以在任何地方访问,而 let 变量不是全局变量,只有在右括号杀死它们之前才会存在。

请参阅下面的示例,并注意 lion (let) 变量在两个 console.logs 中的行为方式有何不同;它在第二个控制台 .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.

评论

0赞 VLAZ 9/18/2023
"var 变量是全局的,基本上可以在任何地方访问这根本不是真的 声明要么是全局的,要么包含在函数中。它们从来都不是全球性的。 也是完全错误的,从来都不是这样。varconsole.log(giraffe); //will print 'giraffe', as giraffe is a global variable (var).
2赞 Rafael Herscovici 4/28/2019 #29

我认为这些术语和大多数示例有点让人不知所措, 我个人对这种差异的主要问题是理解什么是“块”。 在某个时候,我意识到,除了语句之外,块将是任何大括号。 函数或循环的左括号将定义一个新块,其中定义的任何内容在相同事物(函数或循环)的右括号之后将不可用; 考虑到这一点,就更容易理解了: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);

6赞 Piklu Dey 9/7/2019 #30

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

最初定义的 ,在全局范围内,当我们再次在其作用域内声明时,更改了,并且当将新值分配给该作用域内的变量时,它不会影响全局作用域gfooletgfooif clause

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

19赞 Srikrushna 1/26/2020 #31

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

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

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

enter image description here

评论

10赞 Géry Ogam 8/11/2020
吊装柱不正确。他们都举起了可变的。区别在于它们会提升但不初始化为该值。如果它们不提升,它们就不会在封闭块中屏蔽同名变量:stackoverflow.com/q/63337235/2326961varundefined
111赞 Hasan Sefa Ozalp 5/12/2020 #32

用最基本的术语来说,

for (let i = 0; i < 5; i++) {
  // i accessible ✔️
}
// i not accessible ❌

for (var i = 0; i < 5; i++) {
  // i accessible ✔️
}
// i accessible ✔️

⚡️ 沙盒玩↓

编辑 let 与 var

6赞 Sarvar Nishonboyev 10/26/2020 #33

我刚刚遇到了一个用例,我不得不使用它来引入新变量。这是一个案例:varlet

我想创建一个具有动态变量名称的新变量。

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

上面的代码不起作用,因为引入了一个新的代码块。声明 using 将在此代码块之外声明一个变量,因为在函数作用域中声明一个变量。evalvarvar

let另一方面,在块作用域中声明一个变量。因此,变量将仅在块中可见。aeval

评论

2赞 11/10/2020
什么时候必须创建动态变量名称,并且以后必须访问它?创建一个对象并为其分配键和值要好得多。
0赞 Bekim Bacaj 10/5/2021
事实上,这是因为不允许重新声明 JavaScript 命题let
0赞 VLAZ 9/18/2023
请参阅 JavaScript 中的“变量”变量并使用对象。
-2赞 Taib Islam Dipu 12/15/2020 #34

在 2015 年之前,使用关键字是声明 JavaScript 变量的唯一方法。var

在 ES6(JavaScript 版本)之后,它允许 2 个新关键字 let & const

let= 可以重新分配 = 不能重新分配
( const 来自常量、短格式 'const' )
const

例:

  • 假设,在这里申报一个国家名称/您的母亲名字是最合适的。因为迟早更改国家名称或母亲姓名的机会较小。const

  • 您的年龄、体重、薪水、自行车速度等,这些类型的数据经常更改或需要重新分配。这些情况,被使用。let

22赞 Ran Turner 1/13/2022 #35

解释摘自我在 Medium 上写的一篇文章:

提升是一种 JavaScript 机制,其中变量和函数 声明由解析器移动到其作用域的顶部,该解析器将声明移动到其作用域的顶部,该解析器将声明移动到其作用域的顶部。 将源代码读入中间表示形式,然后 实际的代码执行由 JavaScript 解释器开始。所以,它实际上 无论变量或函数在哪里声明,它们都会 移动到其范围的顶部,无论其范围是否为 全局或本地。这意味着

console.log (hi);     
var hi = "say hi";

实际上被解释为

var hi = undefined;
console.log (hi);
hi = "say hi";

因此,正如我们刚才所看到的,变量被提升到了顶端 ,并且正在使用 undefined 的值进行初始化 这意味着我们可以在实际之前实际分配它们的值 在代码中声明它们,如下所示:var

hi = “say hi”
console.log (hi); // say hi
var hi;

关于函数声明,我们可以在实际声明它们之前调用它们,如下所示:

sayHi(); // Hi

function sayHi() {
   console.log('Hi');
};

另一方面,函数表达式不会被提升,因此我们将收到以下错误:

sayHi(); //Output: "TypeError: sayHi is not a function

var sayHi = function() {
  console.log('Hi');
}; 

ES6 向 JavaScript 开发人员引入了 和 关键字。while 和 是块范围的,而不是函数 范围,因为在讨论他们的时不应该有什么不同 吊装行为。我们将从最后开始,JavaScript 提升和 .letconstletconstvarletconst

console.log(hi); // Output: Cannot access 'hi' before initialization 
let hi = 'Hi';

正如我们在上面看到的,不允许我们使用未声明的 变量,因此解释器显式输出引用错误 表示之前无法访问该变量 初始化。如果我们将上述更改为lethiletconst

console.log(hi); // Output: Cannot access 'hi' before initialization
const hi = 'Hi';

因此,最重要的是,JavaScript 解析器搜索变量 声明和函数,并将它们提升到其范围的顶端 在代码执行之前,并在内存中为它们赋值,因此 如果解释器在执行代码时会遇到它们,他会 将识别它们,并能够使用它们的 分配的值。用或保留声明的变量 在执行开始时未初始化,而该变量 声明 是用值 进行初始化的。letconstvarundefined

我添加了这个视觉插图,以更好地帮助理解如何吊装 变量和函数保存在内存中enter image
description here

评论

0赞 TylerH 2/11/2022
Stack Overflow 上需要对引用的内容进行适当的归属。这包括明确披露隶属关系,并在从其他位置复制内容时清楚地显示......即使你是作者。
15赞 Kasun Jalitha 9/10/2022 #36
var   --> Function scope  
let   --> Block scope
const --> Block scope

VAR的

在此代码示例中,变量是使用 声明的。因此,它具有功能范围。这意味着您只能从 .你不能从外面读取它ivarifunction xfunction x

function x(){
  var i = 100;
  console.log(i); // 100
}
 
console.log(i); // Error. You can't do this

x();

在此示例中,您可以看到 is 在块内声明。但它是使用 .因此,它获取函数范围。这意味着您仍然可以访问里面的变量。因为总是将范围限定为函数。即使变量是在块中声明的,但由于它正在使用它,它的作用域为父级。iifvarifunction xvariifvarfunction x

function x(){
  if(true){
    var i = 100;
  }
  console.log(i); 
}

x();

现在变量在 中声明。因此,范围限定为 。您可以访问内部。但不是从外面.ifunction yifunction yifunction yfunction y

function x(){
  function y(){
    var i = 100;
    console.log(i);
  }
  
  y();
}

x();

function x(){
  function y(){
    var i = 100;
  }
  console.log(i); // ERROR
}

x();

让,const

let 和 const 具有块作用域。

const并表现相同。但不同的是,当你赋值时,你不能重新赋值。但是,您可以使用 重新分配值。letconstlet

在此示例中,变量在块内声明。因此,它只能从该块内部访问。我们无法从该块外部访问它。(这里的工作方式与iifififconstlet)

if(true){
  let i = 100;
  console.log(i); // Output: 100
}

console.log(i); // Error

function x(){
  if(true){
    let i = 100;
    console.log(i); // Output: 100
  }
  console.log(i); // Error
}

x();

vs 的另一个区别是,您可以在声明之前访问定义的变量。它会给你.但是,如果您使用 OR 定义的变量执行此操作,则会给您一个错误。(let, const)varvarundefinedletconst

console.log(x);
var x = 100;

console.log(x); // ERROR
let x = 100;

-1赞 paradigm111 11/2/2022 #37

“var”是函数范围,“let”是块范围

当您想在函数中的任何位置使用变量时,可以使用 var,当您只想在该块中使用变量时,可以使用 let。

或者你可以一直使用 var,因为你不太可能在函数内部陷入范围冲突,然后你不需要跟踪你定义为 let 或 var 的变量。

有些人建议一直使用 let,但这不是我的偏好,因为在许多其他编程语言中,局部变量是函数范围的,如果你使用其他变量,那么你可能会发现在使用 JavaScript 时,一直以这种模式思考比 switch 更容易。如果你一直使用 let,你需要记住在更高的范围内定义变量,以便能够在“if”或“while”块之外使用它们,例如,如果你决定以后在函数级别使用它,也需要更多的工作来跟踪所有变量的范围,如果你决定以后在函数级别使用它,也要转换它们的作用域,我认为这更有可能通过在同一函数中意外地为变量选择相同的名称两次而导致变量冲突的问题。

评论

0赞 VLAZ 9/18/2023
许多其他语言具有块范围的变量,并且/使 JS 与这些变量保持一致。letconst
-1赞 Ali Raza 3/25/2023 #38

在 JavaScript 中,let 和 var 都用于变量声明,但它们之间存在一些重要区别:

作用域:let 和 var 之间的主要区别在于它们的作用域。VAR 声明是函数范围的,而 LET 声明是块范围的。

吊装:var 声明被到其范围的顶部,而 let 声明则不吊装。

重新赋值:let 允许您为变量重新赋值,而 var 也可以重新赋值,但也可以重新声明。

时间死区:let 声明具有“时间死区”(TDZ),这意味着如果在声明 let 变量之前尝试访问该变量,则会收到 ReferenceError。

function example() {
  var x = 10;
  if (true) {
    var x = 20; // re-declares x in the same scope
    let y = 30; // y is block-scoped to the if statement
    console.log(x); // outputs 20
    console.log(y); // outputs 30
  }
  console.log(x); // outputs 20 (re-assignment)
  console.log(y); // ReferenceError: y is not defined (block-scoped)
}

总的来说,在现代 JavaScript 开发中,let 通常比 var 更受欢迎,因为它具有块范围和 TDZ 行为,这有助于捕获错误并使代码更具可预测性。