变量

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

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

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

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

    如果 JavaScript 允许标识符以数字开头,那将允许完全由数字组成的标识符,从而导致用作数字的数字和用作标识符的数字之间发生冲突

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

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

以下不是创建标识符的严格规则,但它们是行业最佳实践,可以使维护代码更容易。如果您的特定项目有不同的标准,请遵循这些标准以保持一致性。

遵循 JavaScript 内置方法和属性设置的示例,驼峰式命名法(也风格化为“camelCase”)是由多个单词组成的标识符的非常常见的约定。驼峰式命名法是一种实践,即除了第一个单词外,将每个单词的首字母大写,以提高可读性而无需空格。

let camelCasedIdentifier = true;

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

class MyClass {

}

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

标识符不应过于具体地描述它们包含的数据,因为它们的值可能会根据脚本对数据的操作或未来维护人员做出的决策而更改。例如,最初给定标识符 miles 的变量可能需要在项目后期更改为公里值,这要求维护人员更改对该变量的所有引用,以避免将来出现混淆。为防止这种情况,请改用 distance 作为您的标识符。

JavaScript 没有为以下划线字符 (_) 开头的标识符赋予任何特殊权限或含义,但它们通常用于表示变量、方法或属性是“私有的”,这意味着它们仅供包含它的对象的上下文中使用,不应在该上下文之外访问或修改。这是一种从其他编程语言继承而来的约定,早于 JavaScript 私有属性 的添加。

变量声明

有多种方法可以让 JavaScript 识别标识符,这个过程称为“声明”变量。变量使用 letconstvar 关键字声明。

let myVariable;

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

在现代代码库中工作时,请使用 let 而不是 varvar 在现代浏览器中仍然有效,但它有一些在最早版本的 JavaScript 中定义的非直观行为,并且后来无法更改以保持向后兼容性。let 在 ES6 中添加,以解决 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

尝试更改使用 const 声明的变量的值,就像您可能更改使用 let(或 var)声明的变量的值一样,会导致类型错误

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