5648

ECMAScript 6 引入let语句

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

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

4

39 回答 39

7497

范围规则

主要区别在于范围规则。由关键字声明的变量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

于 2012-07-12T02:53:34.897 回答
756

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存储在 click 处理程序闭包中,而不是i.

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

一般的解决方法是将其包装在匿名函数中并i作为参数传递。现在也可以通过使用letinstead来避免此类问题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>

于 2015-05-27T10:16:23.697 回答
284

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 代码并使用基于节点的转译器(如traceur shell 脚本Babel),则可以安全地使用该let语句。而且因为您的浏览器只会知道转译的代码,所以性能缺陷应该是有限的。

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

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

在此处输入图像描述


如何跟踪浏览器支持

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


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

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

于 2016-02-23T18:35:27.890 回答
171

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

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

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

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

于 2009-04-17T20:11:47.053 回答
135

接受的答案缺少一点:

{
  let a = 123;
};

console.log(a); // ReferenceError: a is not defined
于 2015-06-02T20:59:17.957 回答
118

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

在函数内部

在函数内部(但在块外部),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.

带闭包的循环

如果在循环中使用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
于 2016-11-23T22:52:38.350 回答
95

在最基本的条件下,

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

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

⚡️沙盒玩↓

编辑 let vs var

于 2020-05-11T17:04:48.587 回答
64

这是两者之间差异的示例(对 chrome 的支持刚刚开始):
在此处输入图像描述

如您所见,该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);

于 2015-03-06T10:41:10.797 回答
61

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

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

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

于 2009-04-17T21:38:22.663 回答
61

主要区别在于作用域的不同,而let只能在它声明的作用域内可用,例如在 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
}`

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

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 网站的更多信息

于 2017-03-22T14:39:46.200 回答
33
  • 变量不提升

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

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

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

于 2016-01-17T15:11:31.843 回答
31

不同之处在于每个声明的变量的范围

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

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

所施加的限制let降低了变量的可见性并增加了提前发现意外名称冲突的可能性。这使得跟踪和推理变量变得更容易,包括它们的可达性(帮助回收未使用的内存)。

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

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

例子

1. 在最近的封闭块之外不使用: 此代码块将引发引用错误,因为第二次使用x发生在声明它的块之外let

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

相比之下,与var作品相同的例子。

2. 声明前不使用:
这段代码会ReferenceError在代码运行前抛出a,因为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
于 2017-05-22T01:09:39.303 回答
26

这是一个添加到其他人已经编写的内容的示例。假设您要创建一个函数数组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每个函数的闭包中的每个匿名函数都引用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并按预期运行。

现在,图像混合这两种行为,您可能会明白为什么不建议在同一脚本中混合let新旧行为。这样做可能会导致一些非常混乱的代码。constvar

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。

于 2014-08-18T00:58:29.177 回答
23

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

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
}
于 2015-12-17T03:22:03.993 回答
17

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

这不会产生编译错误:

var x = 1;
var x = 2;

这会:

let x = 1;
let x = 2;
于 2015-08-11T00:35:29.740 回答
17

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

于 2016-07-08T00:21:11.467 回答
17

函数 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被调用时,用 声明的变量 foovar仍然可以在if语句之外访问。该变量在函数范围内的任何地方foo都可用。testVar

变量let

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

let变量不会被提升:

var和之间的另一个区别let是声明为的变量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

在全局范围内声明的变量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

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

于 2018-09-09T13:08:18.867 回答
17

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

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

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

在此处输入图像描述

于 2020-01-26T11:39:21.787 回答
13

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

于 2017-10-28T12:42:16.280 回答
10

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

请参阅“模拟私有接口

于 2016-10-14T05:01:10.210 回答
10

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

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

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

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

于 2018-05-22T13:12:36.923 回答
8

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

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)
于 2016-07-21T17:42:16.157 回答
8

这个解释取自我在Medium上写的一篇文章:

提升是一种 JavaScript 机制,其中变量和函数声明由解析器移动到其作用域的顶部,解析器在 JavaScript 解释器开始实际代码执行之前将源代码读入中间表示。因此,无论变量或函数在哪里声明,它们都将被移动到其作用域的顶部,而不管它们的作用域是全局的还是局部的。这意味着

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

实际上是这样解释的

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

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

hi = “say hi”
console.log (hi); // say hi
var hi; Regarding function declarations, we can invoke them before actually declaring them like so:

sayHi(); // Hi

function sayHi() {
   console.log('Hi');
}; Function expressions, on the other hand, are not hoisted, so we’ll get the following error:

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

var sayHi = function() {
  console.log('Hi');
}; ES6 introduced JavaScript developers the `let` and `const` keywords. While `let` and `const` are block-scoped and not function

范围,因为var在讨论它们的提升行为时它不应该有所作为。我们将从最后开始,JavaScript 提升letconst.

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

正如我们在上面看到的,let不允许我们使用未声明的变量,因此解释器显式地输出一个引用错误,指示hi在初始化之前无法访问该变量。如果我们将上面的内容更改letconst

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

因此,最重要的是,JavaScript 解析器搜索变量声明和函数,并在代码执行之前将它们提升到其作用域的顶部,并在内存中为它们赋值,以便解释器在执行代码时遇到它们,他将识别它们并且将能够使用分配的值执行代码。使用 声明的变量letconst在执行开始时未初始化的变量,而var使用undefined

我添加了这个视觉插图来帮助理解提升的变量和函数是如何保存在内存中的在此处输入图像描述

于 2022-01-12T18:20:10.143 回答
7

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

gfoolet最初定义的 , 在全局范围内,当我们在其范围内gfoo再次声明更改时,并且当为该范围内的变量分配新值时,它不会影响全局范围。if clause

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

于 2019-09-07T11:25:15.033 回答
6

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
}
于 2017-12-17T10:47:31.203 回答
6

让 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.
于 2019-04-18T00:49:32.580 回答
6

我刚刚遇到一个用例,我不得不使用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

上面的代码不起作用,因为eval引入了一个新的代码块。声明 usingvar将在此代码块之外var声明一个变量,因为在函数范围内声明了一个变量。

let另一方面,在块范围内声明一个变量。因此,a变量只会在eval块中可见。

于 2020-10-25T17:15:21.910 回答
4

正如刚才提到的:

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

示例 1:

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

让关键字

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

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

于 2018-08-07T10:25:57.247 回答
3

现在我认为使用以下语句可以更好地为语句块定义变量范围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 和 const以更好地理解它。

于 2016-07-01T08:22:35.487 回答
3

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

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

于 2019-02-13T16:07:22.670 回答
3

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

如果我们了解functionblock scope之间的区别,理解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 value for each call
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]

timer()被调用时,将创建一个ExecutionContext,它将包含变量环境和与每次迭代对应的所有词汇环境。

还有一个更简单的例子

功能范围

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 :(
}
于 2019-03-11T16:52:45.457 回答
2

这篇文章明确定义了var、let和const的区别

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

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

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

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

于 2016-12-27T09:44:55.907 回答
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);

于 2019-04-28T02:21:13.280 回答
0

在MDN中查看此链接

let x = 1;

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

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

console.log(x);
// expected output: 1
于 2018-05-18T07:27:41.083 回答
0

在此处输入图像描述

看看这张图片,我创建了一个非常简单的示例来演示constlet变量。如您所见,当您尝试更改const变量时,您会收到错误消息(尝试覆盖 'name' which is constant'),但请查看let变量...

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

于 2019-02-16T17:17:45.373 回答
-1

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

在“var”之上引入“let”和“const”的主要目标是拥有块作用域而不是传统的词法作用域。 本文非常简要地解释了 "var" 和 "let" 之间的区别,并且还涵盖了关于 "const" 的讨论

于 2016-05-19T11:41:10.847 回答
-1

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

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

let= 可以重新分配
const= 不能重新分配( const 来自常量,缩写形式 'const' )

例子:

  • 假设在这里声明一个国家/地区名称/您的母亲的名字const是最合适的。因为很快或以后更改国家名称或您母亲的名字的机会较小。

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

于 2020-12-15T15:01:45.073 回答
-3

var变量的作用域是函数和全局作用域,但let变量作用域是块作用域(大括号 {})

const myFun=()=> {
      var str1 = "hello";
      let str2 = "program";
    
      console.log(str1, str2); // hello program
    
      {
        var myvar1 = "hiii"
        let myvar2 = "ooo";
        console.log(myvar1, myvar2); // hiii ooo
      }
    
      console.log(myvar1); // hiii
      console.log(myvar2); // ReferenceError
    }
    console.log(myvar1); // not defined
    myFun();
于 2021-05-04T17:48:25.053 回答
-3

仅当您要在脚本中将变量设置为全局变量或要在同一范围内重新声明同一变量时,才使用 var 关键字。随着 ES2015 的到来,当你想将变量设置为函数作用域、块作用域、循环作用域或不想在同一作用域内重新声明变量时,使用 let 关键字。

于 2021-05-07T07:59:52.017 回答