使用“let”和“var”有什么区别?
范围规则
主要区别在于范围规则。由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
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
和var
?
要了解差异,请考虑以下代码:
// 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 代码并使用基于浏览器的转译器(如Traceur或babel-standalone),您可以安全地使用该
let
语句,但是您的代码在性能方面可能不是最佳的。如果您正在编写客户端 JavaScript 代码并使用基于 Node 的转译器(如traceur shell 脚本或Babel),则可以安全地使用该
let
语句。并且因为您的浏览器只会知道转译的代码,所以性能缺陷应该是有限的。如果您正在编写客户端 JavaScript 代码并且不使用转译器,则需要考虑浏览器支持。
还有一些浏览器根本不支持
let
:
如何跟踪浏览器支持
有关let
在您阅读此答案时哪些浏览器支持该声明的最新概述,请参阅此Can I Use
页面。
(*) 全局和功能范围的变量可以在声明之前进行初始化和使用,因为 JavaScript 变量是被提升的。这意味着声明总是在作用域的顶部。
(**) 块作用域变量没有被提升
接受的答案缺少一点:
{
let a = 123;
};
console.log(a); // ReferenceError: a is not defined