定义

一种新的 原始数据类型
表示独一无二

与 undefined,null, number,boolean,string,Object并称 ES 语法的原始数据类型
ES 中 一共有 7个 原始数据类型,

声明方式

普通的声明方式

  1. let s1 = Symbol()
  2. let s2 = Symbol()
  3. console.log(s1, s2); // Symbol() Symbol()
  4. console.log(s1 === s2) // false

带描述的声明方式

传入字符串

在声明时,我们可以将一个字符串当做参数传递进去,当做其 描述

let s1 = Symbol('foo');
let s2 = Symbol('bar');

console.log(s1, s2); // Symbol(foo) Symbol(bar)

传入对象

也可以传入一个对象,传入对象的时候会 自动调用 对象的 toString() 方法,

const obj = {
  name: 'imooc'
}

let s = Symbol(obj);
console.log(s) // Symbol([object Object])

这里我们没有自定义,对象下的 toString() 方法 ,所以 打印的 值 是 object

const obj = {
  name: 'imooc',
  toString() {
    return this.name
  }
}

let s = Symbol(obj);
console.log(s) // Symbol(imooc)

这里我们自定义了 对象中的 toString() 方法,返回的值就是预设好的值了

利用 for() 声明

let s1 = Symbol.for('foo')
console.log(s1); // Symbol(foo)

利用 for() 声明 与 不用for() 之间的区别

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

let S1 = Symbol('foo');
let S2 = Symbol('foo');
console.log(S1 === S2); // false

使用 for() 方法声明的 Symbol 数据 可以相等,但是 直接传入参数得到的 Symbol数据 是无法相等的

使用 Symbol.for() 声明的变量相当于是 一个 全局变量,当我们使用 Symbol.for() 声明 第一个变量 S1 的时候,会把它注册到全局环境下, 当我们声明 第二个变量 S2 的时候,会先去全局环境 中找 是否有描述相同的 变量,如果有,则是 将 S2 指向 S1, 所以他们两是相等的

只要使用 Symbol.for() 声明变量,不管是在 外层还是在内层环境中,变量都会直接注册到全局变量中

function foo() {
  return Symbol.for('foo')
}
const x = foo()
const y = Symbol.for('foo');
console.log(x === y); // true

keyFor()的作用

返回一个 已经 在全局环境中 注册的 symbol 类型

const s1 = Symbol('foo');
console.log(Symbol.keyFor(s1)); // undefined

const s2 = Symbol.for('foo');
console.log(Symbol.keyFor(s2)); // foo

因为只用 使用 Symbol.for() 声明的 变量才是注册在全局中的,才可以使用 Symbol.keyFor() 获取到,而 Symbol() 方式声明的 变量 不算是 全局下的, 所以返回值 为 undefined

实际的数据形式

Symbol 并不是一种 对象类型的 数据,我们不能使用 对象的结构去 理解 他, 他本质上是一种 特殊的字符串

let s = Symbol()
s.name = 'imooc'
console.log(s); //Symbol()

从这里可以看出, 用对象属性赋值的形式并不能给 Symbol 带来什么改变

内部属性description

let s = Symbol('foo');
console.log(s.description) // foo

该属性 存储的值就是 我们声明 Symbol 数据时,传入的 字符串

在对象中的实际应用

主要用来处理 对象中 属性值或 这 key 是字符串的 情况,保证其唯一性

const grade1 = {
  张三: {address: 'yyy', tel: '222'},
  李四: {address: 'zzz', tel: '333'},
  李四: {address: 'xxx', tel: '111'}
}
console.log('grade1', grade1);

const stu1 = '张三'
const stu2 = '李四'
const stu3 = '李四'
const grade2 = {
  [stu1]: {address: 'yyy', tel: '222'},
  [stu2]: {address: 'zzz', tel: '333'},
  [stu3]: {address: 'xxx', tel: '111'},
}
console.log('grade2', grade2)

image.png
这样的 对象 声明方式 存在一种缺陷就是 ,出现两个 李四的 时候,后一个会覆盖前一个

这里就需要使用 Symbol 进行处理字符串相同的问题

const stu1 = Symbol('张三')
const stu2 = Symbol('李四')
const stu3 = Symbol('李四')
const grade3 = {
  [stu1]: {address: 'yyy', tel: '222'},
  [stu2]: {address: 'zzz', tel: '333'},
  [stu3]: {address: 'xxx', tel: '111'},
}
console.log('grade3', grade3);

image.png

取值的方式,则是 用中括号 加上 声明 symbol 的 变量

const stu1 = Symbol('张三')
const stu2 = Symbol('李四')
const stu3 = Symbol('李四')
const grade3 = {
  [stu1]: {address: 'yyy', tel: '222'},
  [stu2]: {address: 'zzz', tel: '333'},
  [stu3]: {address: 'xxx', tel: '111'},
}
console.log('stu2',grade3[stu2]);
console.log('stu3',grade3[stu3]);

image.png

在类中的实际应用

保护对象中的某些属性

const sym = Symbol('imooc');

class User {
  constructor(name) {
    this.name = name
    this[sym] = '.imooc.com'
  }

  getName() {
    return this.name + this[sym]
  }
}

const user = new User('潜龙');
console.log(user.getName()); // 潜龙.imooc.com

可能在这里我们看不出,保护属性的作用体现在何处
这里 使用 for in 对对象的属性进行遍历,就可以明白了

console.log(user);
for (let key in user) {
  console.log(key); // name
}

image.png
这里只打印了 user 对象下的 name 属性, 通过 Symbol 声明的 imooc 属性则没有被遍历出来,这就是一种保护

下面继续使用 三种 对象的 属性遍历方法,进行测试

for (let key of Object.keys(user)) {
  console.log('object.keys',key);
}

console.log('--------')

for (let key of Object.getOwnPropertySymbols(user)) {
  console.log('getOwnPropertySymbols', key);
}
console.log('--------')

for (let key of Reflect.ownKeys(user)) {
  console.log('Reflect.ownKeys', key);
}

image.png
从 打印结果可以看出, Object.getOwnPropertySymbols() 是只能 拿到 对象中 Symbol 属性,
而 Reflect.ownKeys() 则可以拿到 对象中的 所有属性,包括 通过 Symbol 进行声明的属性值

降低 魔术字符串 的耦合度

function getArea(shape) {
  let area = 0
  switch (shape) {
    case 'Triangle':
      area = 1
      break
    case 'Circle':
      area = 2
      break
  }
  return area
}
console.log(getArea('Triangle')) // 1

这里的 Triangle 以及 Circle 就是 所谓的魔术字符串,为了避免写错,我们需要将他们封装起来,方便 重复调用

优化一

利用对象 存储 字符串

const shapeType = {
  triangle: 'Triangle',
  circle: 'Circle'
}

function getArea(shape) {
  let area = 0
  switch (shape) {
    case shapeType.triangle:
      area = 1
      break
    case shapeType.circle:
      area = 2
      break
  }
  return area
}
console.log(getArea(shapeType.triangle)) // 1

优化二

即使 使用 对象的方式,可以减少 字符串的书写,但是魔术字符串仍然存在
这里可以使用 Symbol 进行改进, 将 魔术字符串消除

因为 shapeType 对象下的 属性值,我们是不关注的,只要他们不一样即可,因为后面的判断都不会直接调用到 这些字符串

const shapeType = {
  triangle: Symbol(),
  circle: Symbol()
}

function getArea(shape) {
  let area = 0
  switch (shape) {
    case shapeType.triangle:
      area = 1
      break
    case shapeType.circle:
      area = 2
      break
  }
  return area
}
console.log(getArea(shapeType.triangle)) // 1

这里可以看出,我们消除了 魔法字符串,但是并不影响 结果的输出,以及其中的 判断