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

2024-11-02 20:49:00
admin
原创
47
摘要:问题描述:ECMAScript 6 引入了该let语句。我听说它被描述为一个局部变量,但我仍然不太确定它的行为与var关键字有何不同。有什么区别?什么时候应该let用 代替var?解决方案 1:范围规则主要区别在于作用域规则。var关键字声明的变量的作用域为直接函数体(因此称为函数作用域),而let变量的作用...

问题描述:

ECMAScript 6 引入了该let语句。

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

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


解决方案 1:

范围规则

主要区别在于作用域规则。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();

运行代码片段Hide results展开片段

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

运行代码片段Hide results展开片段

My value: 3`funcs[j]();`由于匿名函数绑定到同一个变量,因此每次调用时都会输出到控制台。

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

提升

var使用关键字声明的变量会被提升和初始化,这意味着它们在声明之前就可以在其封闭范围内访问,但它们的值undefined在到达声明语句之前就已经存在:

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

checkHoisting();

运行代码片段Hide results展开片段

let变量被提升但未初始化,直到评估其定义为止。在初始化之前访问它们会导致ReferenceError。据说该变量从块的开始到声明语句被处理为止处于暂时死区中。

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

checkHoisting();

运行代码片段Hide results展开片段

创建全局对象属性

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

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

运行代码片段Hide results展开片段

重新申报

在严格模式下,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

运行代码片段Hide results展开片段

解决方案 2:

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>

运行代码片段Hide results展开片段

上述代码演示了一个经典的 JavaScript 闭包问题。对i变量的引用存储在点击处理程序闭包中,而不是 的实际值i

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

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

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

运行代码片段Hide results展开片段

解决方案 3:

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 变量是提升的。*这意味着声明总是移动到范围的顶部。

)块范围变量不会被提升

解决方案 4:

这里对该关键字进行了解释let并给出了一些示例。

let工作原理与 非常相似var。主要区别在于var变量的作用域是整个封闭函数

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

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

解决方案 5:

let

块范围

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

在顶层(函数之外)

在顶层,使用声明的变量let不会在全局对象上创建属性。

var globalVariable = 42;
let blockScopedVariable = 43;

console.log(globalVariable); // 42
console.log(blockScopedVariable); // 43

console.log(this.globalVariable); // 42
console.log(this.blockScopedVariable); // undefined

函数内部

在函数内部(但在块之外),let具有与 相同的范围var

(() => {
  var functionScopedVariable = 42;
  let blockScopedVariable = 43;

  console.log(functionScopedVariable); // 42
  console.log(blockScopedVariable); // 43
})();

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

在块内

在块内部使用声明的变量let不能在该块外部访问。

{
  var globalVariable = 42;
  let blockScopedVariable = 43;
  console.log(globalVariable); // 42
  console.log(blockScopedVariable); // 43
}

console.log(globalVariable); // 42
console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined

循环内部

在循环中声明的变量let只能在该循环内引用。

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

解决方案 6:

接受的答案缺少一点:

{
  let a = 123;
};

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

解决方案 7:

从最基本的角度来说,

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

解决方案 8:

下面是两者区别的一个例子:

在此处输入图片描述

可以看到,var j变量在for循环作用域(块作用域)之外仍然有值,但是let ifor循环作用域之外变量是未定义的。

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

运行代码片段Hide results展开片段

解决方案 9:

主要的区别在于作用域的不同,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
}`

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

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...有关更多信息,请访问babel 网站

解决方案 10:

存在一些细微的差别——let作用域的行为更像其他语言中的变量作用域。

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

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

解决方案 11:

  • 变量未提升

let不会提升到它们出现的块的整个范围。相反,var可以按如下所示进行提升。

{
   console.log(cc); // undefined. Caused by hoisting
   var cc = 23;
}

{
   console.log(bb); // ReferenceError: bb is not defined
   let bb = 23;
}

实际上,根据@Bergi的说法,和都被提升了var`let`。

  • 垃圾收集

块作用域let对于闭包和垃圾回收来说很有用。考虑一下,

function process(data) {
    //...
}

var hugeData = { .. };

process(hugeData);

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

处理程序回调根本click不需要该变量。理论上,运行后,巨大的数据结构可以被垃圾回收。但是,某些 JS 引擎可能仍必须保留这个巨大的结构,因为该函数在整个范围内都有闭包。hugeData`process(..)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循环

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) 每次迭代(在评估增量表达式之前)创建一个使用这些名称的新词汇环境,所以更多详细信息请参见这里。

解决方案 12:

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

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

  1. let变量仅在其最近的封闭块中可见({ ... })。

  2. let变量仅可在声明变量之后的代码行中使用(即使它们被提升了!)。

  3. let变量不能被后续的var或重新声明let

  4. 全局let变量不会添加到全局window对象中。

  5. let变量很容易与闭包一起使用(它们不会引起竞争条件)。

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

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

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

示例

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 is 5”:

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

解决方案 13:

下面是一个示例,用于补充其他人已经写过的内容。假设您想要创建一个函数数组,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`

现在,想象一下混合这两种行为,您可能会明白为什么不建议在同一脚本中混合新行为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

不要让这种事情发生在你身上。使用 linter。

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

解决方案 14:

该解释取自我在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;

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

sayHi(); // Hi

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

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

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

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

ES6 为 JavaScript 开发人员引入了letconst关键字。虽然letconst是块作用域而不是函数作用域,但var讨论它们的提升行为时应该不会产生任何影响。我们将从最后开始,JavaScript 提升let
const

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

如上所示,let不允许我们使用未声明的变量,因此解释器明确输出引用错误,表示hi在初始化之前无法访问该变量。如果我们将上述内容更改let
const

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

因此,总而言之,JavaScript 解析器会在代码执行之前搜索变量声明和函数,并将它们提升到其范围的顶部,并在内存中为它们赋值,这样,如果解释器在执行代码时遇到它们,它就会识别它们,并能够使用它们所赋的值执行代码。使用let或声明的变量const在执行开始时保持未初始化状态,而使用 声明的变量var则使用 的值进行初始化undefined

我添加了这个直观的图示,以便更好地帮助理解提升的变量和函数是如何保存在内存中的在此处输入图片描述

解决方案 15:

函数 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使用over var

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

解决方案 16:

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

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

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

在此处输入图片描述

解决方案 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++);
            }
        }
    }
})();
.as-console-wrapper { max-height: 100% !important; }

运行代码片段Hide results展开片段

计数结果为 [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++);
            }
        }
    }
})();

运行代码片段Hide results展开片段

仅计算[0, 1]。

解决方案 18:

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

这不会产生编译错误:

var x = 1;
var x = 2;

这将:

let x = 1;
let x = 2;

解决方案 19:

var   --> Function scope  
let   --> Block scope
const --> Block scope

变量

在此代码示例中,变量i使用 声明var。因此,它具有函数作用域。这意味着您只能i从 内部访问function x它。您无法从 外部读取它function x

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

x();

运行代码片段Hide results展开片段

在此示例中,您可以看到在块i内声明if。但它是使用 声明的var。因此,它具有函数作用域。这意味着您仍然可以访问i内部的变量function x。因为var始终将其作用域限定在函数内。即使变量i是在if块内声明的,由于它正在使用 ,因此var其作用域限定在父级function x

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

x();

运行代码片段Hide results展开片段

现在变量i在 内部声明function y。因此,i作用域为function y。您可以i在 内部访问function y。但不能从 外部访问function y

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

x();

运行代码片段Hide results展开片段

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

x();

运行代码片段Hide results展开片段

让,const

let 和 const 具有块作用域。

const和 的let行为相同。但不同之处在于,当你为 赋值时,const你不能重新赋值。但你可以用 重新赋值let

在此示例中,变量i是在if块内声明的。因此,它只能从该if块内部访问。我们无法从该if块外部访问它。(此处的const工作方式与 相同let

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

console.log(i); // Error

运行代码片段Hide results展开片段

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

x();

运行代码片段Hide results展开片段

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

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

运行代码片段Hide results展开片段

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

运行代码片段Hide results展开片段

解决方案 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

运行代码片段Hide results展开片段

解决方案 21:

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

参见“模拟私有接口”

解决方案 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

如果您想了解更多信息,请继续阅读以下内容

关于范围的最著名的面试问题之一也可以满足下面的确切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,对于每次循环迭代,变量都是有范围的并且具有共享副本。

解决方案 23:

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

解决方案 24:

下面展示了 '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,由 定义的var最初是在全局范围中,但是当我们在 中再次声明它时if clause,它会考虑全局范围 hfoo,尽管 var 再次被用来声明它。当我们重新分配它的值时,我们会看到全局范围 hfoo 也受到了影响。这是主要的区别。

解决方案 25:

我刚刚遇到了一个必须使用它来引入新变量的用例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它引入了一个新的代码块。声明使用var将在此代码块之外声明一个变量,因为var在函数范围内声明了一个变量。

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

解决方案 26:

let 与 var 相比,关键在于作用域

var 变量是全局的,基本上可以在任何地方访问,而let 变量不是全局的,只存在到被右括号杀死为止。

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

运行代码片段Hide results展开片段

解决方案 27:

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

解决方案 28:

如上所述:

不同之处在于作用域。var作用域为最近的函数块let作用域为最近的封闭块,后者可以小于函数块。 如果在任何块之外,则两者都是全局的。让我们看一个例子:

例1:

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

let 关键字

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

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

解决方案 29:

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

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

解决方案 30:

由于我目前正在尝试深入了解 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]

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 :(
}

简而言之,let 和 var 之间的区别在于 var 是函数作用域,而 let 是块作用域。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用