【ES6基础】const介绍
开篇
在ES6之前,JavaScript被其他编程语言诟病没有定义常量的能力,甚至在大多数企业的开发文档中,对于常量的定义都使用var。一般经常会使用所有字母大写和下划线组成的变量名。当然这种妥协的“常量”是随时可变的。例如以下代码:
var MAX_COUNT = 0;
MAX_COUNT = 1 //WARNING
好在E6引入了const语法,让JavaScript获得了真正意义上的定义常量能力,接下来小编将和大家一起学习如何使用const,通过本篇文章,你将学到以下内容:
const介绍
可变的对象变量
如何让对象的属性不可变?
作用域范围
如何选择var/let/const
本篇文章阅读时间预计10分钟
01
const介绍
使用const语法定义变量,一旦定义初始化,我们就不能改变他们的值,因此这就称为常量。如果你尝试改变一个const变量,则会抛出异常。此外,如果你使用const只声明变量,不进行初始化,也会抛出异常。如以下代码,试图改变一个常量,引擎就会抛出异常:
const pi = 3.141;
pi = 4;
// not possible in this universe, or in other terms,
// throws Read-only error
由于ES6可以为程序工程化提供内存安全的优势,便是因为const定义常量的原理是阻隔变量所对应的内存地址被改变。
变量与内存之间的关系由三个部分组成:变量名、内存绑定和内存地址。如下图所示:
ES6在对变量的引用进行读取时,会从该变量当前所对应的内存地址所指向内存空间中读取内容。当变量改变时,引擎会重新从内存分配一个新的内存空间用于存储新值,并将新的内存地址与变量进行绑定。const的原理便是在变量名与内存地址之间建立不可变的绑定,当尝试重新赋值,重新分配新的内存空间时,引擎便会抛出异常。
在某些情况,const定义变量并非值不可变。以V8引擎为例,如字符串、数字、布尔值、undined等值类型只占用一组内存空间,这些类型的值在内存空间中是连续的、不可拆分的。而对于对象、数组等稀疏的引用类型值,由于属性值是可以变化的,所以为了最快地进行内存调度,当对象的属性被改变或添加了新的属性时,都需要重新计算内存地址偏移值。因此使用const定义对象时,由于所创建的内存只绑定一处的,所以默认情况下对象这种由若干内存空间片段组成的值并不会全部被锁定,因此使用const定义对象时,对象的属性值是可变的。
02
可变的对象变量
上一小节我们提及到,当我们使用const定义对象变量时,由于对象是引用类型值,改变对象属性的值时,而非对象本身,因此更改对象的属性是可行的,重新定义整个对象变量则会抛出异常,如下段代码所示:
const a = {
name: "Mehul"
};
console.log(a.name);
a.name = "Mohan";
console.log(a.name);
a = {}; //throws read-only exception
上述代码输出:
Mehul
Mohan
< Error thrown >
在此示例中,a变量是引用值类型,对象地址是不能改变的,但是这个对象本身的属性是可以改变的。当我们尝试将一个新对象分配给a变量时,引擎就会抛出异常。
03
如何让对象的属性值不可变
上一小节,我们了解了,使用const定义对象变量时,对象变量的属性是可以更改的,如何让其不能更改呢,其实只要配合ES5中的Object.freeze()方法,便可以获得一个第一层属性(首层)不可变的对象。如果第一层属性中存在对象嵌套,嵌套对象的属性仍然是可以改变的。如下段代码所示:
const ob1 = {
prop1: 1,
prop2: {
prop2_1: 2
}
};
Object.freeze(ob1);
ob1.prop1 = 4;
// (frozen) ob1.prop1 is not modified
ob1.prop2.prop2_1 = 4;
// (frozen) modified, because ob1.prop2.prop2_1 is nested
ob1.prop2 = 4;
// (frozen) not modified, bar is a key of obj1
ob1 = {};
// (const) ob2 not redeclared (used const)
如何实现所有层级的属性不可变呢?我们可以用递归的方式调用Object.freeze()方法进行实现,如下段代码所示(代码来源MDN):
function deepFreeze(object) {
// Retrieve the property names defined on object
var propNames = Object.getOwnPropertyNames(object);
// Freeze properties before freezing self
for (let name of propNames) {
let value = object[name];
object[name] = value && typeof value === "object" ?
deepFreeze(value) : value;
}
return Object.freeze(object);
}
var obj2 = {
internal: {
a: null
}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue'; // fails silently in non-strict mode
obj2.internal.a; // null
04
作用域范围
关于作用域的概念,小编在这篇文章《【ES6基础】let和作用域》已经介绍过了,不清楚的可以点击链接进行查看,const和let一样,也是块作用域变量,他们遵循相同的作用域规则,如下段代码所示:
const a = 12; // accessible globally
function myFunction() {
console.log(a);
const b = 13; // accessible throughout function
if (true) {
const c = 14; // accessible throughout the "if" statement
console.log(b);
}
console.log(c);
}
myFunction();
上述代码输出:
12
13
ReferenceError Exception
05
如何选择var/let/const
从ES6引入let的语法,设计的初衷便是替代var。从工程化的角度来说,我们应从ES6开始遵从以下三原则:
一般情况下,使用const在定义常量。
只有明确值会被改变时,我们才使用let定义变量。
不在使用var。
06
结束语
今天的内容就介绍到这里,为了更好的使用ES6,我们应该尽快适应使用const定义常量,使用let定义变量。
往期
热点
文章
《JavaScript基础——前端不懂它,会再多框架也不过只是会用而已!》
《JavaScript基础——你真的了解JavaScript吗?》
《JavaScript基础——回调(callback)是什么?》
《JavaScript基础——深入学习async/await》
专注分享当下最实用的前端技术。关注前端达人,与达人一起学习进步!
长按关注"前端达人"