likes
comments
collection
share

搞清楚ECMAScript 6新增的基本数据类型Symbol

作者站长头像
站长
· 阅读数 12

前言

ECMAScript 6(简称ES6),于2015年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2015(ES2015)。ES6之前,基本数据类型分五种:Boolean、Number、String、Null 和 Undefined,ES6为防止因对象合并发生属性名冲突而被覆盖,增加了一种 Symbol 基本数据类型,表示独一无二的值

下面将从创建方式(Symbol() 函数 和 Symbol.for())、Symbol.for() 和 Symbol.keyFor()使用场景(作对象的属性名和替代某些场景下的常量)和 遍历(Object.getOwnPrototypeSymbols()和Reflect.ownKeys())四个方面来学习。

Symbol 介绍

创建 Symbol 类型值

创建 Symbol 类型值,不是通过 new 关键字(因为 new 关键字是创建一个对象的实例),而是 Symbol() 函数,其 typeof 值为 "symbol"

const nameProp = Symbol()
console.log(nameProp, typeof nameProp) // Symbol() "symbol"

因为 Symbol 表示的是独一无二的值,即使连着创建两个 Symbol 类型的值,也是不同的,例如:

const nameProp = Symbol()
const ageProp = Symbol()
console.log(nameProp, ageProp, nameProp === ageProp) // Symbol() Symbol() false

后面还会介绍另一种方式(Symbol.for())创建 Symbol 类型值。

增加描述内容

Symbol.prototype.description:访问其”实例“的描述

为字面化,易理解,建议增加描述内容即在 Symbol() 函数传入参数,通过 .description 访问描述 (该描述也会被视作该 Symbol 类型值的 key):

const nameProp = Symbol("name")
const ageProp = Symbol("age")

console.log(nameProp, nameProp.description) // Symbol(name) "local"

Symbol.for() 和 Symbol.keyFor()

如果要使用前面已创建过的 Symbol 类型值,可使用 Symbol.for(key),该方法接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局(Symbol.for() 定义的 Symbol 类型的值具有全局登记特性)。

Symbol.for()Symbol() 区别在于, 调用 Symbol("name") 30 次,返回 30 个不同的 Symbol 类型值,而 Symbol.for("name") 调用 30 次,返回的是 30 个相同的 Symbol 类型值。例如:

const ageProp = Symbol("age")
const childAgeProp = Symbol("age")
console.log(ageProp, childAgeProp, ageProp === childAgeProp) // Symbol(age) Symbol(age) false

const nameProp = Symbol.for("name")
const childNameProp = Symbol.for("name")
console.log(nameProp, childNameProp, nameProp === childNameProp) // Symbol(name) Symbol(name) true

Symbol.keyFor()方法接收一个 Symbol 类型的值,返回一个已登记的 Symbol 类型值的key,如果 Symbol 类型值是通过 Symbol() 创建的,因为没有被全局注册则返回 undefined;如果是通过 Symbol.keyFor() 创建的则返回其key(描述内容)。

console.log(Symbol.keyFor(ageProp)) // undefined
console.log(Symbol.keyFor(nameProp)) // name

Symbol 作用

因为 Symbol 类型为对象合并引起的属性名相同而被改写或覆盖,扩展对象的属性名类型,首先便是可做对象的属性名(ES6之前属性名只能是字符串类型,ES6后支持两种类型:字符串和Symbol类型)。又因为代表的是独一无二的值,可代替某些场景下的 const 定义的常量为什么说是某些场景?举例说明:定义常量 storageType 表示本地存储方式(localStorage OR sessionStorage),而后通过 storageType.getItem() 获取指定值, 这种场景下,便不可将 storagteType 定义为 Symbol 类型,因为 storageType 不仅是一个常量而且还可通过该常量去访问其值的属性或方法)。

作对象的属性名

const nameProp = Symbol("name")
const ageProp = Symbol("age")

const person = {
    [nameProp]: "露水晰123",
}
person[ageProp] = 18
console.log(person) // {Symbol(age): 18,Symbol(name): "露水晰123",__proto__: Object}

代替某些场景下的 const 常量

function getArea(shape, options) {
  let area = 0;

  switch (shape) {
    case 'Triangle': // 魔术字符串
      area = .5 * options.width * options.height;
      break;
    /* ... more code ... */
  }

  return area;
}

getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串

消除魔术字符串常用的方式是将其改为一个变量,更改上述代码如下:

const shapeType = {
  triangle: 'Triangle'
};

function getArea(shape, options) {
  let area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = .5 * options.width * options.height;
      break;
  }
  return area;
}

getArea(shapeType.triangle, { width: 100, height: 100 });

但是,仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用 Symbol 值。上面代码中,除了将shapeType.triangle的值设为一个 Symbol,其他地方都不用修改。

const shapeType = {
  triangle: Symbol()
};

Symbol 属性名的遍历

Object.getOwnPropertySymbols() 只获取一级 Symbol 类型的属性名

Symbol 类型的属性名不会出现在 for...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。即以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法(外部不能访问)。

但是可以通过 Object.getOwnPropertySymbols(obj) 获取对象 obj 里定义的 Symbol 类型的属性名,以数组的形式返回。

const nameProp = Symbol("name")
const ageProp = Symbol("age")
const studentProp = Symbol("student")
const selfProp = Symbol("self")

const person = {
    date: new Date(),
    [nameProp]: "露水晰123",
}
person[ageProp] = 18
person[studentProp] = [
    { [nameProp]: "小晰1", [ageProp]: 19 },
    { [nameProp]: "小晰2", [ageProp]: 20 },
]
person[selfProp] = {
    [selfProp]: 18,
}
console.log(person)
console.log(Object.getOwnPropertySymbols(person))

打印结果如下:

搞清楚ECMAScript 6新增的基本数据类型Symbol

Obejct.getOwnPropertySymbols(person) 返回的是对象 person 的一级属性名称中 Symbol 类型值,不包含深层的。

Reflect.ownKeys() 获取对象的所有一级属性

console.log(Reflect.ownKeys(person))

搞清楚ECMAScript 6新增的基本数据类型Symbol

内置的 Symbol 值

除了自定义的 Symbol 类型值,ES6提供了 11 个内置的 Symbol 值,指向语言内部的使用方法。

Symbol.iterator

对象的Symbol.iterator属性,指向该对象的默认遍历器方法。针对对象进行for...of循环时,便是调用Symbol.iterator方法(也可以在原型链上定义),返回该对象的默认遍历器。

ES6 新增了一个机制:遍历器(Iterator)机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历(Iterable)操作(即依次处理该数据结构的所有成员)。

基本的数据结构主要有:对象(Object)、数组(Array),还有 ES6 新增的 Set、WeakSet、Map、WeakMap 四种数据结构。实际项目中还有众多简单或复杂的数据结构,针对这部分的数据结构的遍历便需要手动设置遍历器。

遍历器(Iterator)机制 的作用有三个:

  • 一是为各种数据结构,提供一个统一的、简便的访问接口(通过 next() 方法更改指针对象依序指向下一个成员,并返回一个包含 done(是否结束遍历) 和 value(当前项的值) 属性的对象);
  • 二是使得数据结构的成员能够按某种次序排列(按顺序依次往下,可由用户手动指定该顺序;正是因为对象(Object)不确定哪个属性先遍历,哪个属性后遍历,故没有给默认的 Iterator(原生具备 Iterator 接口的数据结构:Array、 Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象),需要用户手动通过 Symbol.iterator 指定);
  • 三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

例如,给对象 obj 定义一个遍历器,依序访问该对象的属性,代码如下:

let obj = {
  data: [ 'parent', 'name', 'age', 'brother', 'children' ],
  name: '露水晰123',
  age: 18,
  parent: [{
    name: '露水晰12',
    age: 36,
  }],
  brother: [{
    name: '露水晰124',
    age: 19,
  }],
  children: [{
    name: '露水晰1231',
    age: 1,
  }],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self[self.data[index++]],
            done: false
          };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (var i of obj){
  console.log(i);
}

打印结果如下:

搞清楚ECMAScript 6新增的基本数据类型Symbol

总结

  • Symbol 表示的是独一无二的值,通过 Symbol() 函数或者 Symbol.for() 函数创建,是基本数据类型;
  • Symbol 可以做为对象的属性名(ES6之前只能是字符串);
  • Symbol 可以代替某些场景下的 const 常量;
  • Symbol 不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回,因为他们涉及的属性名是字符串;
  • 遍历对象的 Symbol 类型的属性,通过 Object.getOwnPropertySymbols(obj),但是只是一级的,不包含深层;Reflect.ownKeys() 可以获取对象的所有类型的键名(字符串和Symbol类型的);
  • Symbol.for() 全局登记特性,与 Symbol() 的区别在于调用多次前者返回的是相同的而后者返回的是不同的;
  • Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key,如果未登记则返回 undefined;

参考