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

IT技术 javascript scope ecmascript-6 var let
2020-12-23 22:45:34

ECMAScript 6 引入let语句

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

有什么区别?。什么时候应该letvar?

6个回答

范围规则

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

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

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

另一个 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]();调用时都会输出到控制台,因为匿名函数绑定到同一个变量。

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

吊装

虽然用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"; // not allowed to be 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, 'foo1' is replaced with 'foo2'.

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

let顶级范围等同于var——let明确不会创建全局范围的引用:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-02-18 22:45:34
let块表达式let (variable declaration) statement是非标准的,将来会被删除,bugzilla.mozilla.org/show_bug.cgi?id=1023609
2021-02-20 22:45:34
@NoBugs,是的,并且鼓励变量仅在需要的地方存在。
2021-02-28 22:45:34
那么 let 语句的目的仅仅是在某个块中不需要时释放内存吗?
2021-03-06 22:45:34
请记住,您可以随时创建块。函数(){ 代码;{ 让 inBlock = 5;} 代码; };
2021-03-07 22:45:34

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>

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

什么之间的区别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

在这里,我们可以看到我们的变量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 变量是被提升的这意味着声明总是在作用域的顶部。

(**) 块作用域变量没有被提升

你的回答改进了很多(我彻底检查过)。请注意,您在评论中引用的同一链接还说:“(let)变量从开始到处理初始化都处于“时间死区”中。” 这意味着“标识符”(指向“某事”的文本字符串“保留”)已经在相关范围内保留,否则它将成为根/主机/窗口范围的一部分。对我个人而言,“提升”只不过是将声明的“标识符”保留/链接到其相关范围;不包括它们的初始化/分配/可修改性!
2021-02-24 22:45:34
@GitaarLAB:根据Mozilla 开发者网络:“在 ECMAScript 2015 中,let 绑定不受变量提升的影响,这意味着 let 声明不会移动到当前执行上下文的顶部。” - 无论如何,我对我的答案进行了一些改进,以阐明let之间提升行为的差异var
2021-02-28 22:45:34
关于答案 v4:i在功能块中随处可见!它从undefined(由于提升)开始,直到您分配一个值!ps:let也被提升(到它包含块的顶部),但会ReferenceError在第一次分配之前在块中引用时给出(ps2:我是一个支持分号的人,但在块后你真的不需要分号)。话虽如此,感谢您添加有关支持的现实检查!
2021-03-04 22:45:34
还有..+1。你链接的那篇凯尔辛普森的文章很好读,谢谢你!“时间死区”又名“TDZ”也很清楚。我想补充的一件有趣的事情是:我在 MDN 上读过letconst并被推荐只在你真正需要它们的附加功能时使用,因为强制/检查这些额外的功能(如只写 const)会导致“更多的工作” '(以及范围树中的其他范围节点)用于(当前)引擎强制/检查/验证/设置。
2021-03-04 22:45:34
请注意,MDN 说 IE 确实正确解释了 let 。是哪个?developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-03-04 22:45:34

这是关键字的一些示例解释let

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

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

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

现在let支持除 Opera、Blackberry 和 QQ 浏览器之外的所有最新浏览器。
2021-02-25 22:45:34
2021-03-02 22:45:34
@TylerCrompton 这只是多年来保留的一组词。当 mozilla 添加 let 时,它纯粹是一个 mozilla 扩展,没有相关规范。ES6 应该为 let 语句定义行为,但这是在 mozilla 引入语法之后出现的。记住 moz 也有 E4X,它完全死了,只有 moz。
2021-03-03 22:45:34
@olliej,实际上 Mozilla 正处于领先地位。请参阅ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf 的第 19 页
2021-03-05 22:45:34
链接文档中的关键文本似乎是,“let 的工作方式与 var 非常相似。主要区别在于 var 变量的范围是整个封闭函数”。
2021-03-08 22:45:34

接受的答案缺少一点:

{
  let a = 123;
};

console.log(a); // ReferenceError: a is not defined
@stimpy77 它明确指出“让范围限定为最近的封闭块”;是否需要包括清单的每一种方式?
2021-02-16 22:45:34
接受的答案没有在其示例中解释这一点。接受的答案仅在for循环初始化程序中演示了它,从而大大缩小了let. 赞成。
2021-02-28 22:45:34
有很多例子,但没有一个正确地证明了这个问题。
2021-03-01 22:45:34
这个贡献表明“块”可以简单地是一组括在括号中的行;即它不需要与任何类型的控制流、循环等相关联。
2021-03-08 22:45:34