购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.1.3 const定义常量

ECMAScript一度被其他编程语言的开发者指责没有定义常量的能力,甚至在大多数企业的JavaScript开发规范文档中,对于常量的定义都是使用var、下划线以及大写字母组成的变量名。当然,这种妥协的“常量”是随时可以被改变的。

3.1.3.1 使用语法

const的引入使ECMAScript获得了真正的定义常量的能力。

在上一章节中,我们提到了ES2015 可以为程序工程化提供内存安全的优势,便是因为const定义常量的原理是阻隔变量名所对应的内存地址被改变。

变量与内存之间的关系由三个部分组成:变量名、内存绑定和内存(内存地址),如图 3.7所示。

图3.7 变量与内存的关系

ECMAScript在对变量的引用进行读取时,会从该变量当前所对应的内存地址所指向的内存空间中读取内容。而当用户改变变量的值时,引擎会重新从内存中分配一个新的内存空间以存储新的值,并将新的内存地址与变量进行绑定。const的原理便是在变量名与内存地址之间建立不可变的绑定,当后面的程序尝试申请新的内存空间时,引擎便会抛出错误。

const的实现原理确实只限于创造一个不可变的内存绑定,而在某些情况下,并非 值不可变 。以Google V8 为例,如字符串、数字、布尔值、undefined等值类型是只占用一组内存空间的,即这些类型的值在内存空间中是连续的、不被拆分的。而对于对象、数组等稀疏的引用类型值,在内存空间中可能会拆分成若干个段落。虽然Google V8 在对JavaScript运行时的内存管理中使用的是堆内存(heap),而不是栈内存(stack),但因为对象的属性是可以变化的,所以为了最快地进行内存调度,当对象的属性被改变或添加了新的属性时,都需要重新计算内存地址偏移值。下面以一个简单的例子来理解其中的原理。

当这个foo对象字面量被定义时,它的内存存储空间情况如表 3.2 所示。

表 3.2 foo对象字面量定义的内存存储空间

const在这段代码中所起到的作用是:将常量foo与内存偏移值为 0 的内存空间绑定起来并锁死。然而内存偏移值分别为 1 和 2 的a和b属性并没有得到强绑定,导致了以下情况的发生。

即便是使用const定义的对象常量,其内容依然能通过修改属性值而被修改,这显然不是我们所希望的。因为const所创建的内存绑定是只绑定一处的,所以默认情况下对象这种由若干内存空间片段组成的值并不会全部被锁定。相对地,如上文中说到的字符串、数字、布尔值、undefined等值类型因为只使用了一段内存空间,所以它们若由const定义,便是天生的 值不可变

那么,如何能获得一个 值不可变 的对象呢?其实只要配合ES5 中的Obj ect.freeze()方法,便可以获得一个 首层属性 不可变的对象。但若首层属性中存在对象,那么默认情况下首层以下的对象依然可以被改变,所以我们需要一个小工具来创建一个完全的值不可变对象。

3.1.3.2 const与块级作用域

除了let会产生块级作用域以外,const同样可以产生块级作用域,其定义的常量也同样遵循变量在作用域中的生命周期。也就是说,与我们所熟知的全局常量不同的是,使用const定义的常量同样是可以灵活使用的,比如定义一些只针对于某个作用域或是某个函数、算法内部的常量。

以实现一个用于获取JSONP资源的函数JSONP()为例。

我们认为其中的一些参数是可以被定义为常量的,如data和callback。

将global用const在这个外部函数所形成的作用域中定义为window的一个别名,以便代码中全局变量控制的部分能具有更好的可读性;此外,需要一个默认的选项表以作为缺省值,而这个默认值自身自然是没有被修改和暴露到外层作用域的需要,所以我们也将它作为一个局部常量,使用const定义在这个作用域中。

我们只使用了一次let来定义url是因为url需要被重新赋值,所以并没有被使用const定义为常量。 i1PGinDXNpcWvxVEBgeRZMxUVCmaNuH3NJ06e/5bRWTTvwQ4gTKHLzZDCDyq1kBS

点击中间区域
呼出菜单
上一章
目录
下一章
×