2022 年 11 月 13 日

旧的“var”

本文用于理解旧脚本

本文中的信息有助于理解旧脚本。

这不是我们编写新代码的方式。

在关于 变量 的第一章节中,我们提到了三种变量声明方式

  1. let
  2. const
  3. 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 穿透 iffor 或其他代码块。这是因为在很久以前的 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" 包含两个操作

  1. 变量声明 var
  2. 变量赋值 =

声明在函数执行开始时处理(“提升”),但赋值始终在它出现的位置工作。因此,代码本质上像这样工作

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 有两个主要区别

  1. var 变量没有块作用域,它们的可见性作用于当前函数,或者如果在函数外部声明,则作用于全局。
  2. var 声明在函数开始时处理(全局的脚本开始)。

还有一个与全局对象相关的非常细微的差别,我们将在下一章中介绍。

这些差别使得 var 在大多数情况下比 let 更差。块级变量非常棒。这就是为什么 let 早在标准中引入,并且现在是声明变量的主要方式(与 const 一起)。

教程地图

评论

在评论之前请阅读此内容…
  • 如果您有改进建议 - 请 提交 GitHub 问题 或提交拉取请求,而不是评论。
  • 如果您无法理解文章中的内容 – 请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行 – 将它们包装在 <pre> 标记中,对于 10 行以上 – 使用沙盒(plnkrjsbincodepen…)