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

ECMAScript 6引入了#0语句

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

什么时候应该使用let而不是var

2108174 次浏览

这里有一个#0关键字的解释和一些例子。

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

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

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

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

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

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

范围界定规则

主要区别在于作用域规则。由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); // Moooconsole.log(baz); // ReferenceError}
run();

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

看看这个例子另一个堆栈溢出问题

var funcs = [];// let's create 3 functionsfor (var i = 0; i < 3; i++) {// and store them in funcsfuncs[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 seefuncs[j]();}

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

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

吊装

虽然用var关键字声明的变量是悬挂(在代码运行之前用undefined初始化),这意味着它们甚至在声明之前就可以在其封闭范围内访问:

function run() {console.log(foo); // undefinedvar foo = "Foo";console.log(foo); // Foo}
run();

let变量在评估其定义之前不会初始化。在初始化之前访问它们会导致ReferenceError。该变量被称为从块开始到初始化被处理的“时间死区”。

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

创建全局对象属性

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

var foo = "Foo";  // globally scopedlet bar = "Bar"; // not allowed to be globally scoped
console.log(window.foo); // Fooconsole.log(window.bar); // undefined

重新声明

在严格模式下,var将允许您在同一范围内重新声明相同的变量,而let会引发一个语法错误。

'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

这是一个添加到其他人已经编写的示例。假设你想创建一个函数数组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); // => falseconsole.log(add12(8) === 1008); // => trueconsole.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); // => falseconsole.log(add24(18) === 42); // => falseconsole.log(add18(24) === add24(18)); // => falseconsole.log(add18(24) === 2018); // => falseconsole.log(add24(18) === 2018); // => falseconsole.log(add18(24) === 1033); // => trueconsole.log(add24(18) === 1030); // => true

别让这种事发生在你身上。用棉绒。

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

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

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

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>

公认的答案缺少一点:

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

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

这不会产生编译错误:

var x = 1;var x = 2;

这将:

let x = 1;let x = 2;

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

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 variableconsole.log(x);  // 71}console.log(x);  // 31}
  • 变量未提升

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

    {console.log(cc); // undefined. Caused by hoistingvar cc = 23;}
    {console.log(bb); // ReferenceError: bb is not definedlet bb = 23;}

    实际上,Per@Bergi,#0和#1都被提升了

  • 垃圾收集

    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 collectedlet hugeData = { .. };process(hugeData);}
    var btn = document.getElementById("mybutton");btn.addEventListener( "click", function click(evt){//....});
  • let loops

    let in the loop can re-binds it to each iteration of the loop, making sure to re-assign it the value from the end of the previous loop iteration. Consider,

    // print '5' 5 timesfor (var i = 0; i < 5; ++i) {setTimeout(function () {console.log(i);}, 1000);}

    但是,将var替换为let

    // print 1, 2, 3, 4, 5. nowfor (let i = 0; i < 5; ++i) {setTimeout(function () {console.log(i);}, 1000);}

    因为let创建了一个新的词法环境,其中包含a)初始化表达式b)每次迭代的名称(以前是评估增量表达式),更多细节是这里

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大师凯尔·辛普森写了一篇非常详细的文章,解释了为什么他认为不会是这样

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

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

  • 如果您正在编写客户端JavaScript代码并使用基于浏览器的转译器(如跟踪你巴别塔-独立),您可以安全地使用let语句,但是您的代码在性能方面可能不是最佳的。

  • 如果你正在编写客户端JavaScript代码并使用基于Node的转译器(如跟踪外壳脚本巴别塔),你可以安全地使用let语句。而且,因为你的浏览器只会知道转译的代码,所以性能缺陷应该是有限的。

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

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

输入图片描述


如何跟踪浏览器支持

有关阅读此答案时哪些浏览器支持let语句的最新概述,请参阅此#1页


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

(**)阻止作用域变量不被提升

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

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

现在我认为使用let可以更好地定义语句块的变量范围:

function printnums(){// i is not accessible herefor(let i = 0; i <10; i+=){console.log(i);}// i is not accessible here
// j is accessible herefor(var j = 0; j <10; j++){console.log(j);}// j is accessible here}

我认为人们会在这里开始使用let,这样他们就会像其他语言、Java、C#等一样在JavaScript中拥有类似的范围。

对JavaScript中的作用域没有清晰理解的人过去常常犯这个错误。

使用let不支持提升。

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

请参阅ES6在深度:让和const以更好地理解它。

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

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的getter和setter:

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)

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

见“模拟私有接口

let

块范围

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

在顶层(函数之外)

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

var globalVariable = 42;let blockScopedVariable = 43;
console.log(globalVariable); // 42console.log(blockScopedVariable); // 43
console.log(this.globalVariable); // 42console.log(this.blockScopedVariable); // undefined

在函数内部

在函数内部(但在块外部),let的作用域与var相同。

(() => {var functionScopedVariable = 42;let blockScopedVariable = 43;
console.log(functionScopedVariable); // 42console.log(blockScopedVariable); // 43})();
console.log(functionScopedVariable); // ReferenceError: functionScopedVariable is not definedconsole.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined

在一个街区里

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

{var globalVariable = 42;let blockScopedVariable = 43;console.log(globalVariable); // 42console.log(blockScopedVariable); // 43}
console.log(globalVariable); // 42console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined

在一个循环中

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

for (var i = 0; i < 3; i++) {var j = i * 2;}console.log(i); // 3console.log(j); // 4
for (let k = 0; k < 3; k++) {let l = k * 2;}console.log(typeof k); // undefinedconsole.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); // undefinedvar noTDZ = 43;console.log(hasTDZ); // ReferenceError: hasTDZ is not definedlet 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

constlet非常相似-它是块范围的并且具有TDZ。然而,有两件事是不同的。

没有重新分配

使用const声明的变量不能重新分配。

const a = 42;a = 43; // TypeError: Assignment to constant variable.

请注意,这并不意味着该值是不可变的。它的属性仍然可以更改。

const obj = {};obj.a = 42;console.log(obj.a); // 42

如果你想要一个不可变的对象,你应该使用#0

const obj = Object.freeze({a: 40});obj.a = 42;console.log(obj.a); // 40console.log(obj.b); // undefined

构造器是必需的

在使用const声明变量时,您始终必须指定一个值。

const a; // SyntaxError: Missing initializer in const declaration

本文明确定义了var、let和const之间的区别

const表示标识符不会被重新分配。

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

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

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

主要区别是范围的区别,而只能在它声明的范围内可用,例如在for循环中,var可以在循环外访问。从MDN的留档(示例也来自MDN):

允许您声明范围仅限于使用它的块、语句或表达式的变量。这与var关键字不同,后者全局定义变量,或局部定义整个函数,而不管块范围如何。

声明的变量的作用域是定义它们的块,以及任何包含的子块。通过这种方式,的工作原理非常类似于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 variableconsole.log(x);  // 2}console.log(x);  // 1}`

在程序和函数的顶层,与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 globallet b = 22; // the scope is inside the if-block
console.log(a);  // 11console.log(b);  // 22}
console.log(a); // 11console.log(b); // 2

也不要忘记它的ECMA6功能,所以它还没有完全支持,所以最好总是使用Babel等将其转换为ECMA5…

区别在于使用each声明的变量的范围

在实践中,范围上的差异产生了一些有用的结果:

  1. let变量仅在其最近的封闭块({ ... })中可见。
  2. let变量仅在发生之后变量被声明的代码行中可用(即使它们被吊起来了!)。
  3. let变量可能不会被后续的varlet重新声明。
  4. 全局let变量不会添加到全局window对象中。
  5. let变量是易于使用闭包(它们不会导致比赛条件)。

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

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

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

示例

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);  // OKconsole.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 0i is 1i is 2i is 3i 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 5i is 5i is 5i is 5i is 5

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

letconst是块范围。

test.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 definedconsole.log(c); // ReferenceError: c is not defined

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 variableconsole.log(x);  // 2}console.log(x);  // 1}

点击这里MDN

let x = 1;
if (x === 1) {let x = 2;
console.log(x);// expected output: 2}
console.log(x);// expected output: 1

使用时let

let关键字将变量声明附加到它所包含的任何块(通常是{ .. }对)的范围。换句话说,let隐式劫持了任何块的变量声明范围。

let变量不能在window对象中访问,因为它们不能全局访问。

function a()\{\{ // this is the Max Scope for let variablelet x = 12;}console.log(x);}a(); // Uncaught ReferenceError: x is not defined

使用时var

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

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

如上所述:

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

示例1:

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

let关键字

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

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

函数VS块范围:

varlet的主要区别在于用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被调用时,用var声明的变量foo仍然可以在if语句之外访问。这个变量foo将在testVar函数的范围内可用到处都是

let的变量:

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

let变量不会被提升:

varlet之间的另一个区别是使用let别被吊起来声明的变量。一个例子是说明这种行为的最佳方式:

let不要的变量被提升:

console.log(letVar);
let letVar = 10;// referenceError, the variable doesn't get hoisted

var的变量被提升:

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 5console.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

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

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

在执行上下文的创建阶段,var、let和const仍然会将其变量存储在内存中,并在给定执行上下文的变量环境中使用未定义的值。区别在于执行阶段。如果您在为其赋值之前使用引用使用var定义的变量,它将只是未定义。不会引发异常。

但是,在声明之前,您不能引用用let或const声明的变量。如果您尝试在声明之前使用它,那么在执行上下文的执行阶段将引发异常。现在,由于执行上下文的创建阶段,该变量仍将在内存中,但引擎不允许您使用它:

function a(){b;let b;}a();> Uncaught ReferenceError: b is not defined

使用var定义的变量,如果引擎在当前执行上下文的变量环境中找不到该变量,则会上到作用域链(外部环境)并在外部环境的变量环境中检查该变量。如果在那里找不到,它将继续搜索作用域链。let和const不是这种情况。

let的第二个特点是它引入了块范围。块由花括号定义。例子包括函数块、if块、for块等。当你在块内声明一个变量时,该变量只在块内可用。事实上,每次运行块时,例如在for循环中,它都会在内存中创建一个新变量。

ES6还引入了用于声明变量的const关键字。const也是块范围的。let和const的区别在于const变量需要使用初始化器声明,否则会产生错误。

最后,当涉及到执行上下文时,使用var定义的变量将附加到“this”对象。在全局执行上下文中,这将是浏览器中的窗口对象。let或const不是这种情况。

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变量时,您将得到一个错误

试图覆盖“名称”,这是一个常量。

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

但是看看let变量。

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

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

如果我们理解函数块范围之间的区别,理解var之间的区别会更容易。

让我们考虑以下案例:

(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 call5. [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 environment5. [setTimeout, i]  [i=5]LexicalEnvironment4. [setTimeout, i]    [i=4]LexicalEnvironment3. [setTimeout, i]      [i=3]LexicalEnvironment2. [setTimeout, i]       [i=2]LexicalEnvironment1. [setTimeout, i]         [i=1]LexicalEnvironment0. [setTimeout, i]           [i=0]

timer()被调用时,会创建一个执行入口,它将包含与每次迭代对应的变量环境和所有词典环境

一个简单的例子

【功能范围】

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 vs var。这都是关于范围的。

变量是全局的并且基本上可以在任何地方访问,而让变量不是全局的并且只存在直到关闭括号杀死它们。

请参阅下面的示例,并注意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.

我认为术语和大多数例子都有点压倒性,我个人对差异的主要问题是理解什么是“块”。在某种程度上,我意识到,除了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 undefinedconsole.log(iCount1);  
} // friends will no be available after this closing bracket!doWork();console.log(friends);

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

let定义的gfoo最初在全球范围中,当我们在if clause中再次声明gfoo时,它是范围改变了,当一个新值被分配给该范围内的变量时,它是不影响全局范围。

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

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

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

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

在此处输入图片描述

从最基本的角度来说,

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

我刚刚遇到一个用例,我必须使用var而不是let来引入新变量。这是一个案例:

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

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块中可见。

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

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

let=可以重新赋值
const=不能重新赋值(const来自常量,缩写为'const')

示例:

  • 假设,声明一个国家名称/你的母亲的名字,const是最合适的,因为有较少的机会改变一个国家名称或你的母亲的名字很快或以后。

  • 你的年龄,体重,工资,自行车的速度,更像这些类型的数据,经常变化或需要重新分配。

这个解释来自我在中等写的一篇文章:

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

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

实际上被解释为

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

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

hi = “say hi”console.log (hi); // say hivar 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提升器letconst

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

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

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

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

我添加了这个视觉插图来帮助理解如何提升变量和函数被保存在内存中输入图像在这里描述“/></a></p></blockquote></div>
                                                                            </div>
                                </div>
                            </div>
                        </div>
                                                <div class=

var   --> Function scopelet   --> Block scopeconst --> Block scope

var

在此代码示例中,变量i是使用var声明的。因此,它有一个职能范围。这意味着您只能从function x内部访问i。您不能从function x外部读取它

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

在此示例中,您可以看到iif块内声明。但它是使用var声明的。因此,它获得了函数作用域。这意味着您仍然可以在function x内访问变量i。因为var总是被作用域到函数。即使变量iif块内声明,因为它使用了var,它的作用域到父function x

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

现在变量ifunction y内声明。因此,i的作用域为function y。您可以在function y内访问i。但不能从function 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有块范围。

constlet的行为相同。但不同的是,当您将值分配给const时,您不能重新分配。但您可以使用let重新分配值。

在这个例子中,变量i是在if块内声明的。所以它只能从if块内访问。我们不能从if块外访问它。(这里const的工作方式与let相同)

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

(let, const)var的另一个区别是,您可以在声明之前访问var定义的变量。它会给你undefined。但是如果你使用letconst定义的变量这样做,它会给你一个错误。

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

console.log(x); // ERRORlet x = 100;

“var”是函数作用域,“let”是块作用域

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

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

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