变量

变量是一种数据结构,用于为值分配代表性名称。它们可以包含任何类型的数据。

变量的名称称为标识符。有效的标识符必须遵循以下规则:

  • 标识符可以包含 Unicode 字母、美元符号 ($)、下划线字符 (_)、数字 (0-9),甚至某些 Unicode 字符。
  • 标识符不能包含空格,因为解析器使用空格来分隔输入元素。例如,如果您尝试调用变量 my Variable 而非 myVariable,解析器会看到两个标识符 myVariable,并抛出语法错误(“unexpected token: identifier”)。
  • 标识符必须以字母、下划线 (_) 或美元符号 ($) 开头,不能以数字开头,以免数字和标识符混淆:

    let 1a = true;
    
    > Uncaught SyntaxError: Invalid or unexpected token
    

    如果 JavaScript 允许标识符开头使用数字,则会允许仅由数字组成的标识符,从而导致用作数字的数字与用作标识符的数字之间发生冲突:

    let 10 = 20
    
    10 + 5
    > ?
    
  • 在语法上具有意义的“预留字词”不能用作标识符。

  • 标识符不得包含特殊字符 (! . , / \ + - * =)。

以下并非创建标识符的严格规则,而是业界最佳实践,有助于简化代码维护。如果您的具体项目采用不同的标准,请改为遵循这些标准以保持一致。

遵循 JavaScript 内置方法和属性设置的示例,驼峰式命名法(也称为“驼峰式”)是用于由多个字词组成的标识符的非常常见的惯例。驼峰式大小写是指将除第一个单词之外的每个单词的第一个字母都大写,以免出现空格,从而提高可读性。

let camelCasedIdentifier = true;

某些项目使用其他命名惯例,具体取决于上下文和数据的性质。例如,的首字母通常要大写,因此多字词类名称通常使用驼峰式命名法的变体,通常称为“大驼峰式命名法”或Pascal命名法。

class MyClass {

}

标识符应简洁地描述其包含的数据的性质(例如,currentMonthDaystheNumberOfDaysInTheCurrentMonth 更好),并且一目了然(originalValueval 更好)。本模块中使用的 myVariable 标识符在孤立示例的上下文中有效,但在生产代码中非常无用,因为它们不提供有关其包含的数据的信息。

标识符不应过于具体地说明其包含的数据,因为其值可能会因脚本对这些数据的操作方式或未来维护人员做出的决策而发生变化。例如,最初被赋予标识符 miles 的变量可能需要在项目后续阶段更改为以公里为单位的值,这需要维护者更改对该变量的所有引用,以免日后造成混淆。为避免出现这种情况,请改用 distance 作为标识符。

JavaScript 不会向以下划线字符 (_) 开头的标识符授予任何特殊特权或含义,但通常会使用这些标识符来表明变量、方法或属性是“私有的”,这意味着它们只能在包含它们的对象上下文中使用,不应在该上下文之外访问或修改。这是从其他编程语言延续下来的惯例,早于 JavaScript 添加私有属性

变量声明

您可以通过多种方式让 JavaScript 知道某个标识符,此过程称为“声明”变量。使用 letconstvar 关键字声明变量。

let myVariable;

使用 letvar 声明一个可随时更改的变量。这些关键字会告知 JavaScript 解释器,一串字符是可能包含值的标识符。

在现代代码库中工作时,请使用 let 而非 varvar 仍然适用于现代浏览器,但它具有一些不直观的行为,这些行为是在最早版本的 JavaScript 中定义的,之后无法更改以保持向后兼容性。在 ES6 中添加了 let,以解决 var 设计方面的一些问题。

通过为声明的变量赋值,可以初始化该变量。使用单个等号 (=) 为变量赋值或重新赋值。您可以在声明它时执行此操作:

let myVariable = 5;

myVariable + myVariable
> 10

您还可以使用 let(或 var)声明变量,而无需立即对其进行初始化。如果您这样做,则变量的初始值为 undefined,直到代码为其分配值为止。

let myVariable;

myVariable;
> undefined

myVariable = 5;

myVariable + myVariable
> 10

值为 undefined 的变量不同于尚未声明标识符的未定义变量。引用未声明的变量会导致错误。

myVariable
> Uncaught ReferenceError: myVariable is not defined

let myVariable;

myVariable
> undefined

将标识符与值相关联通常称为“绑定”。letvarconst 关键字后面的语法称为“绑定列表”,允许使用逗号分隔的多个变量声明(以预期的分号结尾)。这使得以下代码段在功能上完全相同:

let firstVariable,
     secondVariable,
     thirdVariable;
let firstVariable;
let secondVariable;
let thirdVariable;

重新分配变量的值时不会使用 let(或 var),因为 JavaScript 已经知道该变量存在:

let myVariable = true;

myVariable
> true

myVariable = false;

myVariable
> false

您可以根据变量的现有值为其重新分配新值:

let myVariable = 10;

myVariable
> 10

myVariable = myVariable * myVariable;

myVariable
> 100

如果您尝试在生产环境中使用 let 重新声明变量,则会收到语法错误:

let myVariable = true;
let myVariable = false;
> Uncaught SyntaxError: redeclaration of let myVariable

浏览器的开发者工具let(和 class)的重新声明更为宽松,因此您可能不会在开发者控制台中看到相同的错误。

为了保持与旧版浏览器的兼容性,var 允许在任何上下文中不必要地重新声明,而不会出错:

var myVariable = true;
var myVariable = false;

myVariable\
> false

const

const 关键字用于声明常量,这是一种必须立即初始化且无法更改的变量。常量的标识符遵循与使用 let(和 var)声明的变量相同的规则:

const myConstant = true;

myConstant
> true

声明常量时,必须立即为其分配值,因为常量在创建后无法重新分配,因此任何未初始化的常量都将永远保持 undefined 状态。如果您尝试声明常量但不对其进行初始化,则会收到语法错误:

const myConstant;
Uncaught SyntaxError: missing = in const declaration

如果尝试以与使用 let(或 var)声明的变量相同的方式更改使用 const 声明的变量的值,则会导致类型错误:

const myConstant = true;

myConstant = false;
> Uncaught TypeError: invalid assignment to const 'myConstant'

不过,当常量与对象相关联时,可以更改该对象的属性。

const constantObject = { "firstvalue" : true };

constantObject
> Object { firstvalue: true }

constantObject.secondvalue = false;

constantObject
> Object { firstvalue: true, secondvalue: false }

包含对象的常量是对可变数据值的不可变引用。虽然常量本身无法更改,但可以更改、添加或移除引用的对象的属性:

const constantObject = { "firstvalue" : true };

constantObject = false
> Uncaught TypeError: invalid assignment to const 'constantObject'

如果您不希望重新分配变量,最佳做法是将其设为常量。使用 const 会告知项目的开发团队或未来的维护人员不要更改该值,以免破坏代码对其使用方式的假设,例如,变量最终会根据预期的数据类型进行评估。

变量范围

变量的作用域是指脚本中可使用该变量的部分。在变量作用域之外,它不会被定义为包含 undefined 值的标识符,而是像未声明一样。

根据您声明变量时使用的关键字以及定义变量的上下文,您可以将变量的作用域限定为块语句(块作用域)、单个函数(函数作用域)或整个 JavaScript 应用(全局作用域)。

代码块作用域

您使用 letconst 声明的任何变量都将限定在其最近的包含块语句中,这意味着该变量只能在该块中访问。尝试在包含块之外访问块级范围的变量会导致与尝试访问不存在的变量相同的错误:

{
    let scopedVariable = true;
    console.log( scopedVariable );
}
> true

scopedVariable
> ReferenceError: scopedVariable is not defined

对于 JavaScript,块级范围的变量存在于包含它的块之外。例如,您可以在代码块内声明一个常量,然后在该代码块外声明另一个使用相同标识符的常量:

{
  const myConstant = false;
}
const myConstant = true;

scopedConstant;
> true

虽然声明的变量无法扩展到其父块,但可供所有子孙块使用:

{
    let scopedVariable = true;
    {
    console.log( scopedVariable );
    }
}
> true

声明的变量的值可从子块内更改:

{
    let scopedVariable = false;
    {
    scopedVariable = true;
    }
    console.log( scopedVariable );
}
> true

可以在子块内使用 letconst 初始化新变量,而不会出错,即使它与父块中的变量使用相同的标识符也是如此:

{
    let scopedVariable = false;
    {
    let scopedVariable = true;
    }
    console.log( scopedVariable );
}
> false

函数作用域

使用 var 声明的变量的作用域为其最近的包含函数(或 内的静态初始化块)。

function myFunction() {
    var scopedVariable = true;

    return scopedVariable;
}

scopedVariable;
> ReferenceError: scopedVariable is not defined

调用函数后,情况仍然如此。即使在函数执行期间初始化了变量,该变量在函数作用域之外仍不可用:

function myFunction() {
    var scopedVariable = true;

    return scopedVariable;
}

scopedVariable;
> ReferenceError: scopedVariable is not defined

myFunction();
> true

scopedVariable;
> ReferenceError: scopedVariable is not defined

全局范围

全局变量可在整个 JavaScript 应用中使用,可在所有代码块和函数内使用,也可供网页上的任何脚本使用。

虽然这似乎是一个理想的默认设置,但应用的任何部分都可以访问和修改的变量可能会增加不必要的开销,甚至会与应用中其他位置具有相同标识符的变量发生冲突。这适用于网页呈现过程中涉及的任何 JavaScript,包括第三方库和用户分析等。因此,最好尽可能避免污染全局范围

在父函数之外使用 var 或在父代码块之外使用 letconst 声明的任何变量都是全局变量:

var functionGlobal = true; // Global
let blockGlobal = true; // Global

{
    console.log( blockGlobal );
    console.log( functionGlobal );
}
> true
> true

(function() {
    console.log( blockGlobal );
    console.log( functionGlobal );
}());
> true
> true

如果为变量分配值,但未明确声明该变量(即,从不使用 varletconst 创建该变量),则会将该变量提升到全局范围,即使是在函数或块内进行初始化也是如此。使用此模式创建的变量有时称为“隐式全局变量”。

function myFunction() {
    globalVariable = "global";

    return globalVariable
}

myFunction()\
> "global"

globalVariable\
> "global"

变量提升

变量和函数声明会提升到其作用域的顶部,这意味着 JavaScript 解释器会处理脚本中任何位置声明的任何变量,并在执行脚本之前将其有效地移至其封闭作用域的第一行。这意味着,在使用 var 声明变量之前,可以引用该变量,而不会遇到错误:

hoistedVariable
> undefined

var hoistedVariable;

由于只托管变量声明,而非初始化,因此未使用 varletconst 显式声明的变量不会被提升:

unhoistedVariable;
> Uncaught ReferenceError: unhoistedVariable is not defined

unhoistedVariable = true;

如前所述,系统会为声明但未初始化的变量分配值 undefined。这种行为也适用于提升的变量声明,但仅适用于使用 var 声明的变量。

hoistedVariable
> undefined

var hoistedVariable = 2 + 2;

hoistedVariable\
> 4

这种不直观的行为在很大程度上源自 JavaScript 最早版本中的设计决策,如果更改,就可能会破坏现有网站。

letconst 通过在变量创建之前访问该变量时抛出错误来解决此行为:

{
    hoistedVariable;

    let hoistedVariable;
}
> Uncaught ReferenceError: can't access lexical declaration 'hoistedVariable' before initialization

此错误不同于您在尝试访问未声明的变量时可能会遇到的“hoistedVariable 未定义”错误。由于 JavaScript 已提取该变量,因此它知道该变量将在给定作用域内创建。但是,解译器会抛出错误,而不是在声明该变量之前将其值设为 undefined。使用 letconst(或 class)声明的变量从其封闭块的开头到代码中声明变量的位置,都存在于“时间死区”(“TDZ”)中。

由于存在时间死区,对于作者而言,let 的行为比 var 更直观。这对 const 的设计也至关重要。由于常量无法更改,因此将常量提升到其作用域顶部并赋予 undefined 的隐式值后,无法使用有意义的值对其进行初始化。

检查您的理解情况

标识符可以以哪些字符开头?

一封信
下划线
数字

哪种方法是声明值可随时更改的变量的首选方法?

let
const
var