在ES5及早期版本中,语言包含5种原始类型:字符串型、数字型、布尔型、null和undefined。ES6引入了第6种原始类型:Symbol。在Symbol出现以前,人们一直通过属性名来访问所有属性,无论属性名由什么元素构成,全部通过一个字符串类型的名称来访问;私有名称原本是为了让开发者们创建非字符串属性名称而设计的,但是一般的技术无法检测这些属性的私有名称。

创建Symbol

所有原始值,除了Symbol意外都有各自的字面形式,例如布尔类型的true或数字类型的42。可以通过全局的Symbol函数创建一个Symbol,就像这样:

  1. let firstName = Symbol();
  2. let person = {};
  3. person[firstName] = "Nicholas";
  4. console.log(person[firstName]); //"Nicholas"

在上面这段代码中,创建了一个名为firstName的Symbol,用它将一个新的属性赋值给person对象,每当你想访问这个属性时一定要用到最初定义的Symbol。记得要合理命名Symbol变量,这样可以轻松区分出它所指代的内容。

注意,由于Symbol是原始值,因此调用new Symbol()会导致程序抛出错误。也可以执行new Object()创建一个Symbol的实例。

Symbol函数**接受一个可选参数,其可以让你添加一段文本描述即将创建的Symbol,这段描述不可用于属性访问,但是建议你在每次创建Symbol时都添加这样一段描述,以便于阅读代码和调试Symbol程序。

  1. let firstName = Symbol("first name");
  2. let person = {};
  3. person[firstName] = "Nicholas";
  4. console.log("first name" in person); //false
  5. console.log(person[firstName]); //"Nicholas
  6. console.log(firstName); //"Symbol(first name)"

Symbol的描述被存储在内部的[[Description]]属性中,只有当调用Symbol的toString()方法时,才可以读取这个属性。在执行console.log()时,隐式调用了firstName的toString()方法,所以它的描述会被打印到日志中,但不能直接在代码里访问[[Description]]。

Symbol的使用方法

`所有使用可计算属性名的地方,都可以使用Symbol。前面我们看到的都是在括号中使用Symbol,事实上,Symbol也可以用于可计算对象字面量属性名、Object.defineProperty()方法和Object.defineProperties()方法的调用过程中。

  1. let firstName = Symbol("first name");
  2. //使用一个可计算对象字面量属性
  3. let person = {
  4. [firstName] :"Nicholas"
  5. };
  6. //将属性设置为只读
  7. Object.defineProperty(person,firstName,{writable:false}); //Object.defineProperty() 方法会直接在一个
  8. //对象上定义一个新属性,
  9. //或者修改一个对象的现有属性, 并返回这个对象。
  10. let lastName = Symbol("last name");
  11. Object.defineProperties(person,{
  12. [lastName] : {
  13. value:"Zakas",
  14. writable:false
  15. }
  16. });
  17. console.log(person[firstName]); //"Nicholas"
  18. console.log(person[lastName]); //"Zakas"

在此实例中,首先通过可计算对象字面量属性语法为person对象创建了一个Symbol属性firstName。后面一行代码将这个属性设置为只读。随后,通过Object.defineProperties()方法创建一个只读的Symbol属性lastName,此处再次使用了对象字面量属性,但却是作为Object.defineProperties()方法的第二个参数使用。

Symbol共享体系

为了能够在不同的代码中共享同一个Symbol属性名,要使用Symbol.for()方法。它只接受一个参数,即即将创建的Symbol的字符串标识符,这个参数同样也被用作Symbol的描述:

  1. let uid = Symbol.for("uid");
  2. let object = {};
  3. object[uid] = "12345";
  4. console.log(object[uid]); //"12345"
  5. console.log(uid); //"Symbol(uid)"

Symbol.for()方法,首先在全局Symbol注册表中搜索键为”uid”的Symbol是否存在,若存在,直接返回已有的Symbol;否则,创建一个新的Symbol,并使用这个键在Symbol注册表中注册,随即返回新创建的Symbol。
可以使用Symbol.keyFor()方法在Symbol全局注册表中检索与Symbol有关的键:

  1. let uid = Symbol.for("uid");
  2. console.log(Symbol.keyFor(uid)); //"uid"
  3. let uid2 = Symbol.for("uid");
  4. console.log(Symbol.keyFor(uid2)); //"uid"
  5. let uid3 = Symbol("uid");
  6. console.log(Symbol.keyFor(uid3)); //undefined

uid和uid2都返回了”uid”这个键,而在Symbol全局注册表中不存在uid3这个Symbol,也就是不存在与之有关的键,所以最终返回undefined。