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

三世轮回 提交于 2019-12-06 02:29:12

ECMAScript 6引入let语句

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

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


#1楼

范围规则

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

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

  console.log(foo, bar);

  {
    let baz = "Bazz";
    console.log(baz);
  }

  console.log(baz); // ReferenceError
}

run();

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

另一个stackoverflow问题看这个示例:

var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

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

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

吊装

尽管使用var关键字声明的变量被“提升”到块的顶部,这意味着即使在声明它们之前,也可以在其封闭范围内访问它们:

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

创建全局对象属性

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

var foo = "Foo";  // globally scoped
let bar = "Bar"; // globally scoped

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

重新声明

在严格模式下, var使您可以在同一范围内重新声明相同的变量,而let引发SyntaxError。

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

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

#2楼

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

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

这次, ifor循环的每次迭代中反弹。 现在,每个函数在创建函数时都会保留i的值,并且adderFunctions行为符合预期。

现在,对两种行为进行图像混合,您可能会明白为什么不建议在同一脚本中将较新的letconst与较旧的var混合使用。 这样做可能会导致一些令人困惑的代码。

const doubleAdderFunctions = [];

for (var i = 0; i < 1000; i++) {
    const j = i;
    doubleAdderFunctions[i] = x => x + i + j;
}

const add18 = doubleAdderFunctions[9];
const add24 = doubleAdderFunctions[12];

// It's not fun debugging situations like this, especially when the
// code is more complex than in this example.
console.log(add18(24) === 42); // => false
console.log(add24(18) === 42); // => false
console.log(add18(24) === add24(18)); // => false
console.log(add18(24) === 2018); // => false
console.log(add24(18) === 2018); // => false
console.log(add18(24) === 1033); // => true
console.log(add24(18) === 1030); // => true

不要让这种情况发生在您身上。 使用短绒。

注意:这是一个教学示例,旨在演示循环中的var / let行为以及具有易于理解的函数闭包。 这将是添加数字的糟糕方法。 但是在其他上下文中,在现实世界中可能会遇到在匿名函数闭包中捕获数据的通用技术。 YMMV。


#3楼

这是两者之间差异的示例(刚刚开始支持chrome):

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


#4楼

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>


#5楼

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

{
  let a = 123;
};

console.log(a); // ReferenceError: a is not defined
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!