ES6之Symbol数据类型

1 概述

首先来看ES6引入Symbol类型的原因:ES5的对象属性名都是字符串,这容易造成属性名的冲突。例如,我们使用他人提供的对象,在位这个对象添加新方法时方法名有可能与现有方法产生冲突,而在引入Symbol类型后,原来的属性名为字符串类型,新增的则为Symbol类型,这样自然就不冲突了,这对于一个对象由多个模块构成的情况非常有用

Symbol值通过Symbol函数生成。

let s = Symbol();
typeof s //"symbol"

2 Symbol的参数使用

Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,作用:为了在控制台显示,或转为字符串时可以区分。

当Symbol的参数为一个对象时,则默认会调用对象的toString方法。

let s1 = Symbol('description');
s1 // Symbol(description)
s1.toString() // "Symbol(description)"

const sym=Symbol({});
sym // Symbol([object Object])

//重写toString方法
const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
sym // Symbol(abc)

由于参数只是表示对Symbol值的描述,相同参数的Symbol函数的返回值是不同的。也可以说Symbol值是独一无二的。

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false

我们可以借助Symbol.prototype.description属性来获取Symbol的描述:

const sym = Symbol('description');
sym.description // "description"

3 作为属性名的Symbol值

本文开头也说了一个属性名冲突的问题,那么具体如何来使用Symbol值作为对象的属性名呢?

注意:方式1不能使用点运算符(使用点运算符,会将属性名当作字符串类型),方式2必须加方括号(或叫中括号)。

let mySymbol = Symbol();
//方式1
let a= {};
a[mySymbol] = 666;
//方式2
let a = {
  [mySymbol]: 666
};
//方式3
let a = {};
Object.defineProperty(a, mySymbol, { value: 666 });

当属性值为函数时:

let s = Symbol();
let obj = {
  [s]: function (arg) { ... }
};

//匿名函数可以采用增强的对象写法
let obj = {
  [s](arg) { ... }
};
//调用函数
obj[s](参数);

注:Symbol值作为属性名时,则该属性为公开属性,不是私有属性。

Symbol作为属性名时,通过for...infor...of循环,Object.keys()Object.getOwnPropertyNames()JSON.stringify()方法都无法返回该属性。这时可以借助Object.getOwnPropertySymbols()方法来获取,该方法返回的是一个数组,可以获取所有Symbol属性名。

另外,Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和Symbol键名。

4 Symbol类型转换

Symbol值可以转换为字符串和布尔值,但不能转为数值。

//转为字符串:有两种方式
let sym = Symbol('My symbol');
String(sym) // "Symbol(My symbol)"
sym.toString() // "Symbol(My symbol)"

//转为布尔值
let sym = Symbol();
Boolean(sym) // true
!sym  // false

5 Symbol.for()与Symbol.keyFor()

我们直接举个例子吧:

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true

Symbol.for()方法接受一个字符串作为参数,然后搜索是否存在以该参数作为名称的Symbol值,若存在,则直接返回这个Symbol值,否则就会新建一个以该字符串为名称的Symbol值,即多次调用Symbol.for("字符串"),返回的是同一个Symbol值,而利用Symbol()方法每次都会生成新的Symbol值,多次调用则会生成不同的Symbol值。

另外,两者还有一个区别,Symbol.for()生成的Symbol值会被等级在全局环境中供后续搜索重复使用,而Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

进一步地,Symbol.keyFor()方法用于返回一个已登记的Symbol类型值的key。该方法也佐证了Symbol.for()方法生成的值会被登记至全局环境中。

注:Symbol.for()为Symbol值登记的名字,是全局环境的,不管是否是在全局环境运行的。

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

Symbol的应用示例-消除魔术字符串

魔术字符串指的是在代码中多次出现,与代码形成强耦合的某一个数值或字符串。下面来看一个例子:根据形状来计算面积。

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 });

分析:上面的例子中Triangle就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。

解决方法:将魔术字符串定义为一个变量,下次再修改时,直接去定义地方进行修改即可,不用再去代码中挨个修改。

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的值设置为一个Symbol:

const shapeType = {
  triangle: Symbol('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 值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值