跳到主要内容
  1. Skills/
  2. 前端编程/

正确声明 javascript 的变量

·字数 2397·5 分钟
js

var 是 javascript 中用来声明变量的关键字。使用它所声明的变量,与在其它编程语言的声明的变量有不同的表现行为,即作用域表现不同。没有搞清楚它之前,很容易在你的 javascript 中为程序留下 bug。 看完本文之后,你将了解 var 本身的问题,以及如何避免。

有人说,javascript 是一种定义非常糟糕、混乱的编程语言。如果你是其它像 java、C++ 这些语言的开发者,可能会认同这一观点。如果编程的初学者,可能不会有任何体会,反而会觉得这是对 javascript 的无端指责。在软件测试与 debug 过程中,发现往往对变量数据类型、作用域的宽松,反而会为软件本身带来更多潜在的问题。严格清晰的定义反而有助软件程序行为的确定性。

javascript 的作者在设计 javascript 时,并没有想象到 javascript 会成为统治前端开发的第一语言。当时仅仅是用于为浏览器添加扩展能力的脚本语言,如今却服务于前端网面开发、服务后端多种应用场景。因此,在当时看似够用而缺少的语言特性,如今反而变成影响 javascript 更大规模使用。技术发展永远不会停止前进的脚步。javascript本身非常开放,并且js 最主要的运行时环境–各大浏览器js 运行时的同步跟进,使得为它升级语言规范成为可能。它也 有望成为一门最多用户参与规范定制、功能设计、最开放的语言。

说了这么多,有悟想说的是,因为历史原因,一些看似不合理的东西一直被遗留下来,而后来人会不断的改进。

了解了这些背景,以后对 javascript 语言级别的改进就不用惊讶了,因为有些改进在其它的编程语言中已经自然地存在了很长时间。

本文为什么要谈这个 var 关键字?因为这关键字涉及变量定义,是编程中不可缺少的步骤,有悟觉得非常必要(同时,有悟也曾经被困扰过):

  • 对于新手,因为没有太多编程经验,会被一些看似相似的东西混淆
  • 对于其它语言的经验老手,就是了解 javascript 的关键字特性,然后利用

理清这个关键字与其它相似功能关键字的区别,有助于编写出质量更高的 javascript 程序。

javascript 的简易运行环境 #

  1. 使用 浏览器(chrome、firefox)的 devtools,使用快捷F12 打开。
  2. nodejs 命令行,安装后输入 node 就可以启动一个 REPL 交互式执行环境,或者 node xxx.js 执行 js 脚本。
  3. deno 命令行 js 执行器,安装后输入 deno 可以启动一个 REPL 交互式执行环境,或者 deno run xxx.js 执行 js 脚本。

变量作用域的概念 #

在定义变量后,通过名称或者引用可以访问到该变量的值,这个可被访问到的范围就是变量的作用域。从这里又可延伸出全局变量(当前的执行上下文)、本地变量(也叫局部变量,被限制在函数体或者语句块中使用)。

变量声明与问题 #

var 的基本作用 #

  deno
Deno 1.13.0
exit using ctrl+d or close()
> var y = 1
undefined
> y
1
>

var 可以在编程过程中定义一个变量,这与在其它编程语言中是一致的,本身没什么好讲。

关键是使用 var 所声明出来的变量,会有这些特点:

  • 使用 var函数外部 声明的变量是全局变量
  • 可随意重复定义
var v // 声明一个变量,但它此时可以不赋值,初始化为 undefined

{
    v = 1 // 在一个块中对v 的设置会直接修改全局变量的值
    console.log(v)
}

{
    var v = "hello" // 重复定义无关系,因为被会覆盖掉
}

console.log(v)

function youwu_today() {
    v = 2 // 修改全局变量
    var v1 = 123456 // 声明一个作用域为 scope 函数的变量
    console.log(v1)
}
youwu_today()
console.log(v)
console.log(v1) // v1 不在作用域中
//          ^
// ReferenceError: v1 is not defined

看似非常方便的随时访问、修改全局变量的功能,却是长程序所害怕的,它会让程序变得更不好维护、行为预测。在某个的函数中被修改了会难以察觉,查错变得相当麻烦。

这些特点在某些编程语言中是被限制使用或者甚至不被支持的。因为它在大规模使用时非常容易产生问题。 当然在短程序中,这种用法可以减少很频繁的变量声明。

未声明的变量 #

这个标题怪怪的,未声明哪来的变量。

// 默认情况下,未声明变量的,可在 node 或者浏览器 dev tools 控制台中执行
var v
{
    v = 1 // 在一个块中对v 的设置会直接修改全局变量的值
    console.log(v)
}

function youwu_today() {
    v = 2 // 修改全局变量
    var v1 = 123456 // 声明一个 scope 函数范围内的变量
    console.log(v1)
    uv = "abcd" // strict 模式下会报, ReferenceError。无法通过 deno 执行 。
//     ^
//  ReferenceError: uv is not defined
}
youwu_today()
console.log(v)
// console.log(v1) // v1 不在作用域内
//          ^
// ReferenceError: v1 is not defined
console.log(uv)
uv = "efg"   // 在函数youwu_today中定义的变量可以修改
console.log(uv) 

上面这段代码在使用 node xxx.js 可以执行,deno 为 strict 模式,无法通过。

没有使用 var 声明变量,更可怕,是一个全局变量。随便修改、随便用。使用这种方式定义的变量,要非常小心。

ES2015(ES6)声明变量的新方式 #

在 javascript 的 ES2015(ES6)规范中,为它新增了两种新的变量定义方式,constlet。它们是对在 js 中声明变量的改进。

const 是声明一个不可变的变量,即常量,了解 java 编程的开发者都非常熟悉。

{
    const c = "hello"

// c = "world" // 不能修改
//   ^
// TypeError: Assignment to constant variable.

}
console.log(c) // 超出作用域,无法访问
//          ^
// c is not defined

let,它与 var 很接近,非常容易混淆。使用 let 声明的变量作用域为当前语句块,避免了那种莫名其妙冒出来的变量,也避免了被语句块的函数修改造成难以查错的尴尬。

// 作用域差异
{
    // 在块中声明变量
    var v = 1      // v 是全局变量
    console.log(v)

    let l = "hello" // let 变量仅在块中可用
    console.log(l)
}

console.log(v)
console.log(l) // 超出 l 的作用域
//          ^
// ReferenceError: l is not defined
// 重复定义的差异
{   
    var v = 1
    var v = "world"
    let l = 1
    let l = "world" // 不能重复定义
//      ^
// Identifier 'l' has already been declared
}

通过上面两段示例代码可见,let 让 js 更接近其它编程语言,更符合现代软件开发,而非脚本粘贴。

如果你的编程水平暂时理解不了这种差别时,请尽量使用let来声明变量。

如何更好的声明变量 #

通过本文,至少要学习到以下几点,帮助编写质量更高的 JavaScript 程序。

宽松与严格 #

变量的可变性、可访问范围从宽松到严格的顺序是:

未声明的变量 -> var 变量 -> let 变量 -> const 变量

声明变量的姿势 #

javascript 提供了多种方式来声明变量,它们在某些场合下是可混合使用或者替换的,但为了编写高质量的程序,请尽量按照以下原则:

  • 声明变量时,尽量确定变量的用途,即不允许修改的使用 const,限制作用域的使用 let
  • 在不清楚该使用哪种方式来声明变量或者 无声明、var 声明、let 声明这三种方式都可以的情况下,直接采用 let 来声明变量。

即使全局变量能为你编程达到非常大的便利,也要节制的使用

编写本文时,参考了: var 描述 let JavaScript 中的 Var,Let 和 Const 有什么区别