ES6 Symbol

ES6规范引入了原始数据类型:Symbol

基本(原始)、复杂与全局数据类型

  • 基本数据类型(原始值)(记忆:NNUSB=>纳尼USB):
    Number,Null,Undefined,String,Boolean。
  • es6添加了一种新的原始数据类型:Symbol。所以现在 js的原始数据类型有6种。
  • 复杂数据类型(对象值):Object,Array,Function,RegExp,Date,Error
  • 全局数据类型:Math

Symbol(原始类型)

  • Symbol 生成一个全局唯一的值。
  • Symbol 可以创建一个 独一无二 的值(但并不是字符串
  • 可参考知乎,上面的例子生动易懂。
  • 详细语法等信息可参考MDN

属性 方法

  • Symbol.for(): 创建以参数作为描述的Symbol值,如存在此参数则返回原有的Symbol值(先搜索后创建,登记在全局环境)
  • 注意区分Symbol.for()Symbol():
    • Symbol.for()使得传入相同描述的 Symbol 在全局范围内共享一个引用,而不是每次都创建一个新的 Symbol
    • Symbol("xxx") 每次调用都会创建一个新的独立 Symbol,因此它们的引用是不同的,即使描述相同
      1
      2
      console.log(Symbol("es") === Symbol("es")); // false
      console.log(Symbol.for("es") === Symbol.for("es")); // true
  • Symbol.keyFor():返回已登记的Symbol值的描述 (只能返回Symbol.for()的key)
  • description属性: 只读属性,它会返回 Symbol 对象的可选描述的字符串
  • Object.getOwnPropertySymbols() :返回一个包含给定对象所有自有 Symbol 属性的数组
  • Object.getOwnPropertyNames() 本身不包含对象的 Symbol 属性,只包含字符串属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 声明
let a = Symbol();
let b = Symbol();
console.log(a === b); // false

//Symbol.for()
let c = Symbol.for("domesy");
let d = Symbol.for("domesy");
console.log(c === d); // true

//Symbol.keyFor()
const e = Symbol.for("1");
console.log(Symbol.keyFor(e)); // 1

//Symbol.description
let symbol = Symbol("es");
console.log(symbol.description); // es

使用场景

可参考菜鸟

作为属性名

保证属性不重名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let sy = Symbol("key1");

// 写法1
let syObject = {};
syObject[sy] = "kk";
console.log(syObject); // {Symbol(key1): "kk"}

// 写法2
let syObject = {
[sy]: "kk"
};
console.log(syObject); // {Symbol(key1): "kk"}

// 写法3
let syObject = {};
Object.defineProperty(syObject, sy, {value: "kk"});
console.log(syObject); // {Symbol(key1): "kk"}

Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性

1
2
3
4
5
let syObject = {};
syObject[sy] = "kk";

syObject[sy]; // "kk"
syObject.sy; // undefined

注意: Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到

1
2
3
4
5
6
7
8
9
10
11
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);

for (let i in syObject) {
console.log(i);
} // 无输出

Object.keys(syObject); // []
Object.getOwnPropertySymbols(syObject); // [Symbol(key1)]
Reflect.ownKeys(syObject); // [Symbol(key1)]

定义常量

  • Symbol 的值是唯一的,所以可以保证常量唯一,解决字符串作为常量有可能引发的问题
  • 当使用字符串作为常量时,可能会引发以下问题:
    1. 命名冲突:如果多个模块或代码段使用相同的字符串常量,可能会导致命名冲突,因为字符串是全局可见的。这可能会导致不同部分的代码无意中修改了彼此的常量,造成不可预测的结果。
    2. 拼写错误:使用字符串常量可能会导致拼写错误,因为没有办法在编译时检测错误。一个拼写错误可能会导致代码无法正常工作,而且很难发现问题的根本原因。
    3. 不可靠的比较:使用字符串常量进行比较时,可能会遇到因为拼写错误或大小写问题而导致的错误比较。
  • 使用 Symbol 来定义不同的订单状态常量。这样做的好处是:
    1. 唯一性:每个 Symbol 都是唯一的,不会有命名冲突或拼写错误。
    2. 可靠的比较:我们可以使用 === 运算符来比较 Symbol,确保比较是准确的,不会受到拼写错误或大小写问题的影响。
  • 例子:
    • 我们经常定义一组常量来代表一种业务逻辑下的几个不同类型,我们通常希望这几个常量之间是唯一的关系,为了保证这一点,我们需要为常量赋一个唯一的值(比如这里的’AUDIO’、’VIDEO’、 ‘IMAGE’),常量少的时候还算好,但是常量一多,你可能还得花点脑子好好为他们取个好点的名字
      1
      2
      3
      const TYPE_AUDIO = 'AUDIO'
      const TYPE_VIDEO = 'VIDEO'
      const TYPE_IMAGE = 'IMAGE'
    • 有了Symbol,大可不必这么麻烦了
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      const TYPE_AUDIO = Symbol()
      const TYPE_VIDEO = Symbol()
      const TYPE_IMAGE = Symbol()

      function handleFileResource(resource) {
      switch(resource.type) {
      case TYPE_AUDIO:
      console.log('1')
      break
      case TYPE_VIDEO:
      console.log('2')
      break
      case TYPE_IMAGE:
      console.log('3')
      break
      default:
      throw new Error('Unknown type of resource')
      }
      }
      handleFileResource({ type: TYPE_VIDEO }) // 2

,