JavaScript 常量真的不可变吗?

阅读数:3222 2019 年 7 月 7 日

JavaScript 中的常量究竟如何工作,常量就是“在正常执行中无法被程序改变的值”吗?事实就是如此吗?非也,本文将给你答案。

前几天我在 Twitter 上谈到了 JavaScript 中的 const。Kyle Simpson 指出我之前分享的一篇文章中有关于 const 的一个误解。但我的想法是,至少我能理解这种糊涂来自哪里,因为 const 与我预想的工作方式不太一样(注,我并不是说 const 是错的,只是说它与我的预想有差别。)

虽然我跟 Kyle Simpson 对话很短,但是这段对话仍然涉及到了许多词汇和概念。因此我觉得自己不妨深入研究一下,从而使自己不仅更加理解常量的概念,而且更加了解用 const 声明的变量在 JavaScript 中到底是如何工作的。

需要指出的是,Kyle Simpson 的确写过一篇关于此主题的帖子,但是这篇博客已经下线了。而且他的确通过 Wayback Machine 分享过一个版本。这篇博客值得一读,因为他写得很深入。

什么是常量

如果你在谷歌搜“什么是编程中所谓的常量?”,你会发现许多页面将常量定义为维基百科中定义的那样,即“在正常执行中无法被程序改变的值。”

表面上看,这似乎很简单,就是设置一个值,一个无法改变的值。这对于可读性和错误检查会十分有用。但是,并非所有语言都有常量,而且拥有常量的各个语言处理常量的方式也不太一样。例如,在一些语言中,常量所代表的值的类型是有限的。

一旦你走出简单的值类型范围后,事情就会变得令人糊涂了。这一点很重要,这也是我对 JavaScript 中的常量感到糊涂的起点。这里,我会举一个例子,而不是解释。
现在,假设我要像这样设置一个常量:

复制代码
const NAME = "Brian";

似乎很明显的是,给 NAME 指定任何新的值都会出现错误。但是,如果我像下面这样做呢:

复制代码
const ME = {name:'Brian'};

如果我改变 ME.name 的值,是否还会出错?有人会说本质上我并没有改变 ME 的值,因为它仍然指向同样的对象,即使这个对象已经被改变了。但要说明的是,在 JavaScript 中,这并不会出错。

在这里,会出现计算机科学家们熟悉的概念,那就是基元和不变性。我们会谈一点这方面的内容,但为了避免让本文成为一本计算机科学书籍,我不会深入地探讨这两个概念。

简单来说,不可改变的对象是指一个创建后便无法改变其状态的对象。而 JavaScript 中的基元是指“一个不是对象但没有方法的数据。” (来源: MDN)

JavaScript 中的常量

JavaScript ES6(又称 ES2015) 中加入了 const 关键词。之前,通常的约定是使用一个标准的变量,但它必须以全大写命名,如 MY_CONSTANT。但这并没有真正规定一个变量是否能够被更改,它只是对程序员的一个提示,提示他们不要更改该变量。

用 const 声明的 JavaScript 常量可以是全局范围的,也可以是区块范围的。如果常量位于区块内(即在{ 和}之间),它们就自动是区块范围的。如果它们没有在区块内,则它们是全局范围的。但是,与用 var 声明的变量不同的是,它们不会成为 window 对象的特性。如果它们位于模块中,位于区块外用 const 声明的变量对于该模块而言将是全局范围的。

const 和 var 另一个有趣的区别在于,它们提升 (hoist) 的方式不同。当你用 const 或 let 声明一个变量时,该声明将被提升,但是它的初始化值并不是 undefined,因此如果你尝试在声明前访问它的话,会出现引用错误。正如下面所示,第一个 console.log 引用了以 var 定义的变量,它的返回值为 undefined。而第二个 console.log 引用了以 const 定义的变量,却出现了一个错误。

这一现象被称之为暂时性死区,这个名字使它听起来比实际上更加不吉利。

如前所述,最后一条关于 JavaScript const 重要的一点是:
const 声明为一个值创建了一个只读的引用。这并不是说其代表的值是不可改变的,而只是说变量标识符不能被重新指定罢了。(来源

这也是关于 const 的困惑再次出现的地方。当你将 const 用于JavaScript 基元类型(如布尔值、数字、字符串等)时,const 的行为与你预想的一致(任何重新指定值的尝试都会导致错误)。但是,当你将 const 用于JavaScript 对象时(包括数列、函数等),这个对象仍然是可以更改的,也就是说,这个对象的特性仍然可以被更改。

想要了解 let 和 const 作用域的详情,请阅读 Axel Rauschmayer 的 “为没耐心的程序员设计的JavaScript”这篇文章。

你应该用 Const 吗?

回答这个问题有点难,特别是因为 let 拥有同样的区块作用域和提升优势(我将后者描述为一个潜在的优势,因为如果你在声明某个变量前不小心访问了某个变量的话,你提升 var 的方式可能会导致出现不太常见的错误)。鼓吹 const 优势的人通常关注于代码的可读性。通过使用 const,你相当于表明了这个特定的变量不应该被更改,并且在某种程度上,你强调了这一点。然而,人们好像经常对此产生误解这一事实把支持 const 可读性的观点给削弱了,就像本文开头所讲的那样。这的确能够一定程度上防止他人重新指定这个变量的值,但是我想引用一下Kyle 的话:

事实上,许多程序员都承认,这一保护能够避免一些不知情的程序员意外地改变某个 const。然而,有个例外,我认为在现实中很可能发生的情况是,当一个程序员需要更改某个变量但却获得了一个“const- 抛出”错误时,他可能会将 const 直接修改成 let,然后继续工作。

所以,如果 const 提供的保护较为有限的话,那么我们可能只需要在乎风格偏好了,特别是在 let 和 const 之间做选择时。但如果你的变量需要代表一个基元值,而且这个基元值并不会改变,那么很显然,使用 const 是个合理的选择。但是,请注意,如果该值并非一个基元值,那么从可读性的角度来讲,使用 const 会给人带来更多困惑,而不是帮助。

英文原文:https://remotesynthesis.com/blog/javascript-const

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论