【ES6基础】const介绍

2,081 阅读5分钟

在ES6之前,JavaScript被其他编程语言诟病没有定义常量的能力,甚至在大多数企业的开发文档中,对于常量的定义都使用var。一般经常会使用大写字母和下划线组成的变量名进行规范约束。当然这种妥协的“常量”是随时可变的。例如以下代码:

var MAX_COUNT=0;
MAX_COUNT=1 //WARNING

好在E6引入了const,让JavaScript获得了真正的定义常量的能力,接下来小编将和大家一起学习const,通过本篇文章,你将学到以下内容:

  • const介绍
  • 可变的对象变量
  • 如何让对象的属性不可变?
  • 作用域范围
  • 如何选择var/let/const

本篇文章阅读时间预计10分钟

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的原理便是在变量名与内存地址之间建立不可变的绑定,当尝试重新分配新的内存空间时,引擎便会抛出异常。

在某些情况,并非值不可变。以V8引擎为例,如字符串、数字、布尔值、undined等值类型只占用一组内存空间的,这些类型的值再内存空间中是连续的、不可拆分的。而对于对象、数组等稀疏的引用类型值,由于属性值是可以变化的,所以为了最快地进行内存调度,当对象的属性被改变或添加了新的属性时,都需要重新计算内存地址偏移值。因此使用const定义对象时,由于所创建的内存只绑定一处的,所以默认情况下对象这种由若干内存空间片段组成的值并不会全部被锁定,因此使用const定义对象时,对象的属性值是可变的。

可变的对象变量

上一小节我们提及到,当我们使用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变量时,引擎就会抛出异常。

如何让对象的属性值不可变呢?

上一小节,我们了解了,使用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

作用域范围

关于作用域的概念,小编在这篇文章《【ES基础】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

如何选择var/let/const

从ES6引入let的语法,设计的初衷便是替代var。从工程化的角度来说,我们应从ES6后遵从以下三原则:

  1. 一般情况下,使用const在定义常量。
  2. 只有明确值会被改变时,我们才使用let定义变量。
  3. 不再使用var。

结束语

今天的内容就介绍到这里,为了更好的使用ES6,我们应该尽快适应使用const定义常量,使用let定义变量。

更多精彩内容,请微信关注”前端达人”公众号!