本文中的信息有助于理解旧脚本。
这不是我们编写新代码的方式。
在关于 变量 的第一章节中,我们提到了三种变量声明方式
let
const
var
var
声明类似于 let
。大多数时候,我们可以用 var
替换 let
,反之亦然,并且可以正常工作
var message = "Hi";
alert(message); // Hi
但从内部来看,var
是一个截然不同的野兽,它起源于非常久远的时间。它通常不用于现代脚本,但仍然潜伏在旧脚本中。
如果你不打算遇到这样的脚本,你甚至可以跳过本章或推迟阅读。
另一方面,在将旧脚本从 var
迁移到 let
时,了解差异非常重要,以避免出现奇怪的错误。
“var” 没有块级作用域
使用 var
声明的变量要么是函数作用域,要么是全局作用域。它们可以通过块可见。
例如
if (true) {
var test = true; // use "var" instead of "let"
}
alert(test); // true, the variable lives after if
由于 var
忽略代码块,因此我们得到了一个全局变量 test
。
如果我们使用 let test
而不是 var test
,那么变量将只能在 if
内部可见
if (true) {
let test = true; // use "let"
}
alert(test); // ReferenceError: test is not defined
循环也是如此:var
不能是块级或循环局部
for (var i = 0; i < 10; i++) {
var one = 1;
// ...
}
alert(i); // 10, "i" is visible after loop, it's a global variable
alert(one); // 1, "one" is visible after loop, it's a global variable
如果一个代码块在函数内部,那么 var
将成为函数级变量
function sayHi() {
if (true) {
var phrase = "Hello";
}
alert(phrase); // works
}
sayHi();
alert(phrase); // ReferenceError: phrase is not defined
正如我们所看到的,var
穿透 if
、for
或其他代码块。这是因为在很久以前的 JavaScript 中,块没有词法环境,而 var
是它的残余。
“var” 容忍重新声明
如果我们在同一作用域中用 let
两次声明同一个变量,这是一个错误
let user;
let user; // SyntaxError: 'user' has already been declared
使用 var
,我们可以多次重新声明一个变量。如果我们对已声明的变量使用 var
,它将被忽略
var user = "Pete";
var user = "John"; // this "var" does nothing (already declared)
// ...it doesn't trigger an error
alert(user); // John
“var” 变量可以在其使用下方声明
当函数开始(或脚本开始时对于全局变量)时,会处理 var
声明。
换句话说,var
变量从函数的开头定义,无论定义在哪里(假设定义不在嵌套函数中)。
所以这段代码
function sayHi() {
phrase = "Hello";
alert(phrase);
var phrase;
}
sayHi();
…在技术上与这个相同(将 var phrase
移到上面)
function sayHi() {
var phrase;
phrase = "Hello";
alert(phrase);
}
sayHi();
…甚至与这个相同(记住,代码块被忽略)
function sayHi() {
phrase = "Hello"; // (*)
if (false) {
var phrase;
}
alert(phrase);
}
sayHi();
人们还将这种行为称为“提升”(raising),因为所有 var
都被“提升”到函数的顶部。
因此,在上面的示例中,if (false)
分支永远不会执行,但这并不重要。它内部的 var
在函数的开头被处理,因此在 (*)
时变量存在。
声明被提升,但赋值没有。
最简单的演示方法是举一个例子
function sayHi() {
alert(phrase);
var phrase = "Hello";
}
sayHi();
代码行 var phrase = "Hello"
包含两个操作
- 变量声明
var
- 变量赋值
=
。
声明在函数执行开始时处理(“提升”),但赋值始终在它出现的位置工作。因此,代码本质上像这样工作
function sayHi() {
var phrase; // declaration works at the start...
alert(phrase); // undefined
phrase = "Hello"; // ...assignment - when the execution reaches it.
}
sayHi();
因为所有 var
声明都在函数开始时处理,所以我们可以在任何地方引用它们。但在赋值之前,变量是未定义的。
在上面的两个示例中,alert
都可以 بدون خطأ运行,因为变量 phrase
存在。但它的值尚未赋值,所以它显示 undefined
。
IIFE
过去,由于只有 var
,并且它没有块级可见性,所以程序员发明了一种方法来模拟它。他们所做的事情被称为“立即调用的函数表达式”(缩写为 IIFE)。
这不是我们现在应该使用的东西,但你可以在旧脚本中找到它们。
IIFE 如下所示
(function() {
var message = "Hello";
alert(message); // Hello
})();
这里,创建一个函数表达式并立即调用它。因此,代码立即执行并拥有自己的私有变量。
函数表达式用括号 (function {...})
包装,因为当 JavaScript 引擎在主代码中遇到 "function"
时,它将其理解为函数声明的开始。但函数声明必须有一个名称,所以这种代码会产生一个错误
// Tries to declare and immediately call a function
function() { // <-- SyntaxError: Function statements require a function name
var message = "Hello";
alert(message); // Hello
}();
即使我们说:“好的,让我们添加一个名称”,但这不起作用,因为 JavaScript 不允许立即调用函数声明
// syntax error because of parentheses below
function go() {
}(); // <-- can't call Function Declaration immediately
因此,函数周围的括号是一种技巧,向 JavaScript 表明该函数是在另一个表达式的上下文中创建的,因此它是一个函数表达式:它不需要名称,并且可以立即调用。
除了括号之外,还有其他方法可以告诉 JavaScript 我们指的是函数表达式
// Ways to create IIFE
(function() {
alert("Parentheses around the function");
})();
(function() {
alert("Parentheses around the whole thing");
}());
!function() {
alert("Bitwise NOT operator starts the expression");
}();
+function() {
alert("Unary plus starts the expression");
}();
在以上所有情况下,我们都声明了一个函数表达式并立即运行它。让我们再次注意:现在没有理由编写这样的代码。
总结
与 let/const
相比,var
有两个主要区别
var
变量没有块作用域,它们的可见性作用于当前函数,或者如果在函数外部声明,则作用于全局。var
声明在函数开始时处理(全局的脚本开始)。
还有一个与全局对象相关的非常细微的差别,我们将在下一章中介绍。
这些差别使得 var
在大多数情况下比 let
更差。块级变量非常棒。这就是为什么 let
早在标准中引入,并且现在是声明变量的主要方式(与 const
一起)。
评论
<code>
标记,对于多行 – 将它们包装在<pre>
标记中,对于 10 行以上 – 使用沙盒(plnkr、jsbin、codepen…)