const
声明一个只读
的常量
。
一旦声明,常量
的值就不能改变
。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
上面代码表明改变常量的值会报错。
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
const
实际上保证的,并不是变量
的值
不得改动
,而是变量
指向的那个内存地址
所保存的数据
不得改动
。
对于简单类型的数据(数值
、字符串
、布尔值
),值
就保存在变量
指向的那个内存地址
,因此等同于常量
。
但对于复合类型的数据(主要是对象
和数组
),变量
指向的内存地址
,保存的只是一个指向实际数据
的指针
,const
只能保证这个指针
是固定
的(即总是指向另一个固定
的地址)
,至于它指向的数据结构
是不是可变
的,就完全不能控制了。
因此,将一个对象声明为常量
必须非常小心。
声明对象常量
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo
储存的是一个地址
,这个地址
指向一个对象
。
不可变
的只是这个地址
,即不能把foo
指向另一个地址
,但对象
本身是可变
的,所以依然可以为其添加新属性
。
声明数组常量
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
上面代码中,常量a
是一个数组,这个数组
本身是可写的,但是如果将另一个数组
赋值给a
,就会报错。
冻结对象案例
如果真的想将对象冻结
,应该使用Object.freeze
方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
上面代码中,常量foo
指向一个冻结
的对象
,所以添加新属性不起作用,严格模式
时还会报错。
除了将对象
本身冻结
,对象
的属性
也应该冻结
。
下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
const foo;
// SyntaxError: Missing initializer in const declaration
上面代码表示,对于const
来说,只声明不赋值,就会报错。
const
的作用域与let
命令相同:只在声明所在的块级作用域
内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const
命令声明的常量也是不提升
,同样存在暂时性死区
,只能在声明
的位置后面使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
上面代码在常量MAX
声明之前就调用,结果报错。
const
声明的常量,也与let
一样不可重复声明
。
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
在let
和const
之间,建议优先使用const
,尤其是在全局环境
,不应该设置变量
,只应设置常量
。
const
优于let
有几个原因。
const
可以提醒阅读程序的人,这个变量不应该改变。const
比较符合函数式
编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式
运算。最后一个原因是 JavaScript
编译器会对const
进行优化,所以多使用const
,有利于提高程序的运行效率,也就是说let
和const
的本质区别,其实是编译器
内部的处理不同。
// bad
var a = 1, b = 2, c = 3;
// good
const a = 1;
const b = 2;
const c = 3;
// best
const [a, b, c] = [1, 2, 3];
const
声明常量
还有两个好处
阅读代码
的人立刻会意识到不应该修改这个值。修改变量
值所导致的错误。所有的函数
都应该设置为常量
,避免污染顶级对象
属性值。
长远来看,JavaScript
可能会有多线程
的实现(比如 Intel
公司的 River Trail
那一类的项目),这时let
表示的变量
,只应出现在单线程
运行的代码中,不能是多线程
共享的,这样有利于保证线程安全
。