使用“let”和“var”有啥区别?
Posted
技术标签:
【中文标题】使用“let”和“var”有啥区别?【英文标题】:What's the difference between using "let" and "var"?使用“let”和“var”有什么区别? 【发布时间】:2014-05-07 23:02:48 【问题描述】:ECMAScript 6 引入了the let
statement。
我听说它被描述为 local
变量,但我仍然不太确定它与 var
关键字的行为有何不同。
有什么区别?什么时候应该使用let
而不是var
?
【问题讨论】:
ECMAScript 是标准,let
包含在 6th edition draft 中,很可能会出现在最终规范中。
请参阅kangax.github.io/es5-compat-table/es6 了解 ES6 功能(包括 let)的最新支持矩阵。在编写 Firefox 时,Chrome 和 IE11 都支持它(虽然我相信 FF 的实现不是很标准)。
很长一段时间以来,我都不知道 for 循环中的变量的作用域是它所包含的函数。我记得第一次弄清楚这一点,并认为这很愚蠢。我确实看到了一些力量,尽管现在知道如何出于不同的原因使用这两者,以及在某些情况下您可能实际上希望在 for 循环中使用 var 而不是将其范围限定为块。
随着 ES6 功能支持的改进,有关 ES6 采用的问题将焦点从功能支持转移到性能差异。因此,here's a site I found benchmarking performance differences between ES6 and ES5。请记住,随着引擎针对 ES6 代码进行优化,这可能会随着时间而改变。
【参考方案1】:
提升是一种 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 开发人员引入了 let
和 const
关键字。虽然let
和const
是块作用域,而不是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
的值进行初始化。
我添加了这个视觉插图来了解提升的变量和函数是如何保存在内存中的
这个解释摘自我在medium写的一篇文章
【讨论】:
【参考方案2】:范围规则
主要区别在于范围规则。由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 中错误的主要来源之一。
看看这个来自another Stack Overflow question的例子:
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
关键字声明的变量是 hoisted(在代码运行之前使用 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
【讨论】:
请记住,您可以随时创建块。函数()代码;让inBlock = 5; 代码; ; 那么 let 语句的目的仅仅是在某个块中不需要时释放内存吗? @NoBugs,是的,鼓励变量只存在于需要的地方。let
块表达式 let (variable declaration) statement
是非标准的,将来会被删除,bugzilla.mozilla.org/show_bug.cgi?id=1023609。
let
在***范围内不与var
相同——let
明确不会创建全局范围的引用:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…【参考方案3】:
仅当您想在脚本中将变量设置为全局变量或者您想在同一范围内重新声明相同的变量时,才使用 var 关键字。 随着 ES2015 的到来,当你想将变量设置为函数作用域、块作用域、循环作用域或不想在同一作用域内重新声明变量时,使用 let 关键字。
【讨论】:
嗨,Pranay,请不要重复现有的答案。已经有很多答案详尽地涵盖了这一点。【参考方案4】: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();
【讨论】:
已被接受的答案(以及大多数其他答案)已经涵盖了这一点。新答案,尤其是旧问题的答案,应注意不要重复现有内容。相反,他们应该专注于问题的新颖解决方案或问题的答案。【参考方案5】:ES6 引入了两个新的关键字(let 和 const)替代 var。
当您需要块级减速时,您可以使用 let 和 const 而不是 var。
下表总结了var、let和const的区别
【讨论】:
提升的列不正确。他们都提升变量。与var
的区别在于它们提升但不初始化为undefined
值。如果它们不提升,它们将不会在封闭块中屏蔽同名变量:***.com/q/63337235/2326961【参考方案6】:
我刚刚遇到一个用例,我必须使用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
块中可见。
【讨论】:
什么时候必须创建一个动态变量名,并且以后必须访问它?创建一个对象并为其分配键和值要好得多。 其实那是因为 JavaScriptlet
proposition 的重新声明是不允许的。【参考方案7】:
在 2015 年之前,使用 var
关键字是声明 JavaScript 变量的唯一方法。
在ES6(一个JavaScript版本)之后,它允许2个新的关键字let & const。
let
= 可以重新赋值 const
= 不能重新赋值(const 来自常量,短格式'const')
示例:
假设在这里声明一个国家/地区名称/您的母亲姓名const
是最合适的。因为迟早改变国家名称或母亲姓名的机会较小。
您的年龄、体重、薪水、自行车速度等这些类型的数据经常变化或需要重新分配。在这些情况下,使用let
。
【讨论】:
【参考方案8】:在最基本的术语中,
for (let i = 0; i < 5; i++)
// i accessible ✔️
// i not accessible ❌
for (var i = 0; i < 5; i++)
// i accessible ✔️
// i accessible ✔️
⚡️沙盒玩↓
【讨论】:
【参考方案9】:
let
和 var
有什么区别?
使用var
语句定义的变量在整个the function 中都是已知的,从函数开始就在其中定义。 (*)
使用let
语句定义的变量仅在定义它的the block 中已知,从定义的那一刻起。 (**)
要了解区别,请考虑以下代码:
// 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 写了a very elaborate article on why he believes that won't be the case。
然而,今天绝对不是这样。事实上,我们实际上需要问自己使用let
语句是否安全。该问题的答案取决于您的环境:
如果您正在编写服务器端 JavaScript 代码 (Node.js),则可以安全地使用 let
语句。
如果您正在编写客户端 JavaScript 代码并使用基于浏览器的转译器(如 Traceur 或 babel-standalone),您可以安全地使用 let
语句,但是您的代码可能不是最优的在性能方面。
如果您正在编写客户端 JavaScript 代码并使用基于节点的转译器(如 traceur shell script 或 Babel),则可以安全地使用 let
语句。而且因为您的浏览器只会知道转译后的代码,所以性能缺陷应该是有限的。
如果您正在编写客户端 JavaScript 代码并且不使用转译器,则需要考虑浏览器支持。
还有一些浏览器根本不支持let
:
如何跟踪浏览器支持
有关在您阅读此答案时哪些浏览器支持 let
语句的最新概述,请参阅 this Can I Use
page。
(*) 全局和函数范围的变量可以在声明之前初始化和使用,因为 JavaScript 变量是 hoisted。 这意味着声明总是位于范围的顶部。
(**) 块范围的变量未提升
【讨论】:
关于答案 v4:i
在功能块中随处可见!它以undefined
开头(由于提升),直到您分配一个值! ps:let
也被提升(到它的包含块的顶部),但在第一次分配之前在块中引用时会给出ReferenceError
。 (ps2:我是一个支持分号的人,但你真的不需要在 block 后面加分号)。话虽如此,感谢您添加有关支持的现实检查!
@GitaarLAB :根据Mozilla Developer Network :“在 ECMAScript 2015 中,let 绑定不受变量提升的影响,这意味着 let 声明不会移动到当前执行上下文的顶部。” - 无论如何,我对我的答案进行了一些改进,以澄清let
和var
之间提升行为的区别!
您的回答改进了很多(我彻底检查了)。请注意,您在评论中引用的同一链接还说:“(let) 变量从 块开始 到处理初始化都处于“临时死区”中。”这意味着“标识符”(文本字符串“保留”指向“某物”)已经保留在相关范围内,否则它将成为根/主机/窗口范围的一部分.对我个人而言,“提升”只是将声明的“标识符”保留/链接到其相关范围;不包括它们的初始化/赋值/可修改性!
还有..+1。您链接的 Kyle Simpson 文章是一篇优秀的文章,谢谢! “时间死区”又名“TDZ”也很清楚。我想补充一件有趣的事情:我在 MDN 上读到 let
和 const
建议仅在您真正需要它们的附加功能时使用,因为强制/检查这些额外的功能功能(如只写常量)导致(当前)引擎执行/检查/验证/设置的“更多工作”(以及范围树中的其他范围节点)。
请注意,MDN 说 IE 可以正确解释 let。它是哪一个? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…【参考方案10】:
let
也可以用来避免闭包问题。它绑定新值而不是保留旧引用,如下面的示例所示。
for(var i=1; i<6; i++)
$("#div" + i).click(function () console.log(i); );
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Clicking on each number will log to console:</p>
<div id="div1">1</div>
<div id="div2">2</div>
<div id="div3">3</div>
<div id="div4">4</div>
<div id="div5">5</div>
上面的代码演示了一个经典的 JavaScript 闭包问题。对i
变量的引用存储在点击处理程序闭包中,而不是i
的实际值。
每个单击处理程序都将引用同一个对象,因为只有一个计数器对象包含 6,因此每次单击都会得到 6 个。
一般的解决方法是将其包装在一个匿名函数中并将i
作为参数传递。现在也可以通过使用let
而不是var
来避免此类问题,如下面的代码所示。
(在 Chrome 和 Firefox 50 中测试)
for(let i=1; i<6; i++)
$("#div" + i).click(function () console.log(i); );
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Clicking on each number will log to console:</p>
<div id="div1">1</div>
<div id="div2">2</div>
<div id="div3">3</div>
<div id="div4">4</div>
<div id="div5">5</div>
【讨论】:
这真的很酷。我希望“i”在循环体之外定义,包含在括号内,并且不会在“i”周围形成“闭包”。当然,您的示例证明并非如此。我认为从语法的角度来看这有点令人困惑,但这种情况非常普遍,以这种方式支持它是有意义的。非常感谢您提出这个问题。 IE 11 支持let
,但它会为所有按钮提示“6”。你有任何消息来源说let
应该如何表现?
看起来你的答案是正确的行为:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
确实这是 Javascript 中的一个常见陷阱,现在我明白为什么 let
会非常有用。在循环中设置事件侦听器不再需要在每次迭代时立即调用函数表达式以在本地范围内限定 i
。
使用“let”只是推迟了这个问题。所以每次迭代都会创建一个私有的独立块作用域,但“i”变量仍然可能被块内的后续更改破坏,(授予迭代器变量通常在块内没有更改,但其他声明let 块内的变量很可能是)并且在块内声明的任何函数在调用时都可能破坏块内声明的其他函数的“i”值,因为它们确实共享相同的私有块范围因此对“i”的引用相同。【参考方案11】:
下面显示了 '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
时,其范围已更改 并且当一个新值被分配给该范围内的变量时,它不会影响全局范围。
而hfoo
,由var
定义,最初是在全局范围 中,但是当我们在if clause
中声明它时,它考虑了全局范围hfoo,尽管var 已经被再次用于声明它。当我们重新分配它的值时,我们看到全局范围 hfoo 也受到了影响。这是主要区别。
【讨论】:
【参考方案12】:有一些细微的差别——let
作用域的行为更像是变量作用域在任何其他语言中的行为或多或少。
例如它的范围是封闭块,它们在声明之前不存在,等等。
但值得注意的是let
只是较新的 Javascript 实现的一部分,具有不同程度的browser support。
【讨论】:
另外值得注意的是,ECMAScript 是标准,let
包含在 6th edition draft 中,并且很可能会出现在最终规范中。
刚刚偶然发现这个问题,在 2012 年仍然只有 Mozilla 浏览器支持 let
。 Safari、IE 和 Chome 都没有。
意外创建部分块范围的想法是一个好点,请注意,let
不会提升,使用由定义在块顶部的let
定义的变量。如果您有一个不仅仅是几行代码的if
语句,您可能会忘记在定义该变量之前不能使用该变量。好点!!!
这是 let 和 var 之间最重要的区别之一,它不在公认的答案中哈哈。特别是考虑到由于提升和范围界定可能发生的众多错误。如果您不提及提升,我觉得 let 和 var 之间没有太多区别。
@EricB: yes and no: "在 ECMAScript 2015 中,let
会将变量提升 到块的顶部。但是,在之前引用块中的变量" “switch 语句,因为只有一个底层块”也是如此。来源:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…【参考方案13】:
这是explanation of the let
keyword 的一些示例。
let
的工作方式与var
非常相似。主要区别在于var
变量的作用域是整个封闭函数
***上的This table 显示了哪些浏览器支持 Javascript 1.7。
请注意,只有 Mozilla 和 Chrome 浏览器支持它。 IE、Safari 和其他可能没有。
【讨论】:
链接文档中的关键文本似乎是,“let 的工作方式非常类似于 var。主要区别在于 var 变量的范围是整个封闭函数”。 @olliej,实际上 Mozilla 只是领先于游戏。请参阅第 19 页 ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf @TylerCrompton 这只是一组已保留多年的单词。当 mozilla 添加 let 时,它纯粹是一个 mozilla 扩展,没有相关规范。 ES6 应该为 let 语句定义行为,但这是在 mozilla 引入语法之后出现的。记住 moz 也有 E4X,它完全死了,只有 moz。 IE11 增加了对let
msdn.microsoft.com/en-us/library/ie/dn342892%28v=vs.85%29.aspx的支持
现在let
支持除Opera、Blackberry 和QQ 浏览器之外的所有最新浏览器。【参考方案14】:
以下是两者区别的示例(对 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);
【讨论】:
【参考方案15】:由于我目前正试图深入了解 JavaScript,因此我将分享我的简短研究,其中包含一些已经讨论过的精彩文章以及其他一些从不同角度的细节。
如果我们了解 function 和 block 作用域之间的区别,那么了解 var 和 let 之间的区别会更容易>.
让我们考虑以下情况:
(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,其中将包含与每次迭代对应的VariableEnvironment 和所有LexicalEnvironments。
还有一个更简单的例子
功能范围
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 :(
【讨论】:
【参考方案16】:我认为这些术语和大多数示例都有些压倒性,
我个人遇到的主要问题是理解什么是“块”。
在某些时候我意识到,除了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);
【讨论】:
【参考方案17】:let 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.
【讨论】:
【参考方案18】:看看这张图片,我创建了一个非常简单的示例来演示const
和let
变量。如您所见,当您尝试更改 const
变量时,您将收到错误消息(Attempting to override 'name' which is constant'),但请查看let
变量。 ..
首先我们声明let age = 33
,然后分配一些其他值age = 34;
,这没关系,我们尝试更改let
变量时没有任何错误
【讨论】:
代码应该写在 SO 帖子中,而不是通过屏幕截图分享。【参考方案19】:我想将这些关键字链接到执行上下文,因为执行上下文在所有这些中都很重要。执行上下文有两个阶段:创建阶段和执行阶段。此外,每个执行上下文都有一个变量环境和外部环境(它的词法环境)。
在执行上下文的创建阶段,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 的情况。
【讨论】:
【参考方案20】:主要区别在于 scope 不同,而 let 只能在它声明的 scope 内可用,就像在 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
在块内使用时,让变量的范围限制在该块内。注意 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 website
【讨论】:
我不知道最后一个例子是否准确。因为通过不是从函数而是直接命令行调用它,它仍然被认为是同一函数的一部分。因此,如果您从函数外部调用它,它的行为方式不应相同。【参考方案21】:如果我正确阅读了规范,那么 let
谢天谢地 也可以用来避免 self invoking functions 用于模拟私有成员 - 一种流行的设计模式会降低代码的可读性,使代码复杂化调试,这并没有增加真正的代码保护或其他好处——除了可能满足某人对语义的渴望,所以停止使用它。 /咆哮
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
见“Emulating private interfaces”
【讨论】:
您能否详细说明立即调用的函数表达式如何不提供“代码保护”而let
提供? (我假设您的意思是具有“自我调用功能”的 IIFE。)
那么为什么要在构造函数中设置hiddenProperty
?您的“类”中的所有实例只有一个hiddenProperty
。【参考方案22】:
let
块范围
使用let
关键字声明的变量是块范围的,这意味着它们仅在声明它们的block 中可用。
在顶层(函数之外)
在顶层,使用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);
时间死区
由于the temporal dead zone,使用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
【讨论】:
这是关于 JS 中声明的非常清楚的解释......突然意识到我在for
循环之一中的问题在哪里:-|【参考方案23】:
函数 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
被调用时,用var
声明的变量foo 仍然可以在if
语句之外访问。这个变量foo
将在testVar
函数范围内无处不在可用。
带有let
的变量:
当第二个函数testLet
被调用时,使用let
声明的变量bar 只能在if
语句中访问。因为用let
声明的变量是块作用域(其中块是大括号之间的代码,例如if
、for
、function
)。
let
变量不会被提升:
var
和let
之间的另一个区别是使用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 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
?
尽可能使用let
而不是var
,因为它只是范围更具体。这减少了在处理大量变量时可能发生的潜在命名冲突。 var
可以在您希望全局变量显式位于 window
对象上时使用(如果确实需要,请务必仔细考虑)。
【讨论】:
【参考方案24】:如上所述:
区别在于范围。
var
作用域为最近的 函数 块和let
的范围是最近的封闭块,它 可以小于一个功能块。如果在任何外部,两者都是全局的 块。让我们看一个例子:
示例 1:
在我的两个示例中,我都有一个函数myfunc
。 myfunc
包含一个变量 myvar
等于 10。
在我的第一个示例中,我检查 myvar
是否等于 10 (myvar==10
) 。如果是,我再次使用 var
关键字声明一个变量 myvar
(现在我有两个 myvar 变量)并为其分配一个新值(20)。在下一行中,我在控制台上打印它的值。在条件块之后,我再次在控制台上打印myvar
的值。如果您查看myfunc
的输出,myvar
的值等于 20。
示例 2:
在我的第二个示例中,我没有在条件块中使用 var
关键字,而是使用 let
关键字声明 myvar
。现在,当我调用 myfunc
时,我会得到两个不同的输出:myvar=20
和 myvar=10
。
所以区别很简单,即它的范围。
【讨论】:
请不要发布代码图片,这被认为是对 SO 的不良做法,因为未来用户将无法搜索它(以及可访问性问题)。同样,这个答案没有增加其他答案尚未解决的任何内容。【参考方案25】:使用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
时
var
和 ES5 中的变量在函数中具有作用域,这意味着变量在函数内有效,而不是在函数本身之外。
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
时,对于每次循环迭代,该变量都有作用域并且具有共享副本。
【讨论】:
【参考方案26】:检查此链接 MDN
let x = 1;
if (x === 1)
let x = 2;
console.log(x);
// expected output: 2
console.log(x);
// expected output: 1
【讨论】:
这将有助于用文字解释let
正在做什么,尽管这会使其与此处的另一个答案重复
他想说的是if
之外的let
定义了变量x=1
。 if
-statement 将触发(因为x===1 is true
。现在是棘手的部分,这也是您需要跟踪let
与var
的主要原因。在if
内,他试图设置x=2
,在 console.log(x)
中将打印 2,但是,x
“在外面”的 if 仍然具有值 1
,所以另一个 console.log(x)
给出 1,因为 @ 的值987654337@“globally”仍然是1
。答案没有解释这一点,所以在我看来不应该被认为是一个好的答案。【参考方案27】:
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
【讨论】:
【参考方案28】:var
是全局范围(可提升)变量。
let
和 const
是块作用域。
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 defined
console.log(c); // ReferenceError: c is not defined
【讨论】:
【参考方案29】:这是一个添加到其他人已经编写的内容的示例。假设您要创建一个函数数组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
在循环结束时adderFunctions
中每个匿名函数的值(1000)。这根本不是我们想要的:我们现在在内存中有一个包含 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
和 const
与较旧的 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。
【讨论】:
@aborz:第二个例子中的匿名函数语法也很酷。这正是我在 C# 中所习惯的。我今天学到了一些东西。 更正:技术上,此处描述的箭头函数语法 => developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 其实你不需要let value = i;
。 for
语句创建一个词法块。【参考方案30】:
区别在于每个声明的变量的scope。
在实践中,范围差异会产生许多有用的后果:
let
变量仅在其最近的封闭块中可见 ( ...
)。
let
变量只能在声明变量之后出现的代码行中使用(即使they are hoisted!)。
let
变量不能被后续的var
或let
重新声明。
全局 let
变量不会添加到全局 window
对象中。
let
变量易于使用带有闭包(它们不会导致 race conditions)。
let
施加的限制降低了变量的可见性,并增加了提前发现意外名称冲突的可能性。这使得跟踪和推理变量变得更加容易,包括它们的reachability(帮助回收未使用的内存)。
因此,let
变量在用于大型程序或以新的和意想不到的方式组合独立开发的框架时不太可能导致问题。
var
如果您确定在循环中使用闭包 (#5) 或在代码中声明外部可见的全局变量 (#4) 时需要单一绑定效果,那么var
可能仍然有用。如果 export
迁移出转译器空间并迁移到核心语言中,则可能会取代使用 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); // 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
【讨论】:
#5 不是由竞争条件引起的。通过使用var
而不是let
,代码等价于:var i = 0; while (i < 5) doSomethingLater(); i++;
i
在闭包之外,在doSomethingLater()
被执行的时候,i
已经增加了5倍,因此输出是i is 5
五倍。通过使用let
,变量i
位于闭包内,因此每个异步调用都会获得自己的i
副本,而不是使用var
创建的“全局”副本。
@DanielT.:我不认为将变量定义从循环初始化程序中提取出来的转换可以解释任何事情。这只是for
语义的正常定义。一个更准确的转换,虽然更复杂,是经典的for (var i = 0; i < 5; i++) (function(j) setTimeout(_ => console.log(
i is $j), 125/*ms*/); )(i);
,它引入了一个“函数激活记录”来保存i
的每个值,名称为j
。功能。以上是关于使用“let”和“var”有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
IF 一个函数被赋值给一个使用 var 声明的变量和 let 有啥区别?