前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[译] JavaScript与TypeScript中的Symbol

[译] JavaScript与TypeScript中的Symbol

作者头像
腾讯IVWEB团队
发布2020-06-28 10:43:14
1.8K0
发布2020-06-28 10:43:14
举报

原文链接 https://fettblog.eu/symbols-in-javascript-and-typescript/

Symbol是一个JavaScript与TypeScript内建的数据类型. Symbol与其他数据类型相比, 能够作为对象的属性键值来使用. 与numberstring相比, symbol具备一些使它别具一格的特性.

JavaScript中的Symbols

Symbol可以通过Symbol()工厂函数来创建:

代码语言:javascript
复制
const TITLE = Symbol('title');

Symbol本身没有构建函数. 可选参数是一个用于描述Symbol的字符串.

通过调用工厂函数, 新鲜出炉刚刚被创建的Symbol的唯一的值被赋给了我们的常量TITLE.

这个Symbol现在是全局唯一的, 与其他所有的Symbol都不相等, 即使它们拥有相同的description作为创建时的参数.

代码语言:javascript
复制
const ACADEMIC_TITLE = Symbol('title');
const ARTICLE_TITLE = Symbol('title');

ACADEMIC_TITLE === ARTICLE_TITLE; // 永远为false

Description仅仅是用来帮助开发者在开发阶段获取Symbol相关信息的

代码语言:javascript
复制
console.log(ACADEMIC_TITLE.description) // title
console.log(ACADEMIC_TITLE.toString()) // Symbol(title)

在需要比较专有,唯一的值时, Symbol是非常合适的. 对于运行时的switch或者mode comparisons:

代码语言:javascript
复制
// 一个很丢人的Log框架
const LEVEL_INFO = Symbol('INFO')
const LEVEL_DEBUG = Symbol('DEBUG')
const LEVEL_WARN = Symbol('WARN')
const LEVEL_ERROR = Symbol('ERROR')

function log(msg, level) {
  switch(level) {
    case LEVEL_WARN: 
      console.warn(msg); break
    case LEVEL_ERROR: 
      console.error(msg); break;
    case LEVEL_DEBUG: 
      console.log(msg); 
      debugger; break;
    case LEVEL_INFO:
      console.log(msg);
  }
}

Symbols也能作为属性键值来使用, 但需要注意, 作为键值使用时, 并不是iterable的. 这对序列化是需要注意的地方

代码语言:javascript
复制
const print = Symbol('print')

const user = {
  name: 'Stefan',
  age: 37,
  [print]: 'print out',
}

JSON.stringify(user) // { name: 'Stefan', age: 37 }
user[print] // print out

全局Symbol注册

通过全局注册Symbol, 可以在整个应用中访问到Symbol

代码语言:javascript
复制
Symbol.for('print') // 创建一个全局的Symbol

const user = {
  name: 'Stefan',
  age: 37,
  // 使用全局Symbol
  [Symbol.for('print')]: function() {
    console.log(`${this.name} is ${this.age} years old`)
  }
}

第一次调用Symbol.for会创建一个symbol, 第二次调用会访问到这个symbol. 如果symbol的值是个变量, 可以通过Symbol.keyFor()来查询到这个值的键

代码语言:javascript
复制
const usedSymbolKeys = []

function extendObject(obj, symbol, value) {
  //嗯...这个Symbol是什么的来着?
  const key = Symbol.keyFor(symbol)
  //行吧, 最好把它存下来
  if(!usedSymbolKeys.includes(key)) {
    usedSymbolKeys.push(key)
  }
  obj[symnbol] = value
}

//现在是时候来看看我们都有什么Symbol了
function printAllValues(obj) {
  usedSymbolKeys.forEach(key => {
    console.log(obj[Symbol.for(key)])
  })
}

漂亮!

TypeScript中的Symbols

TypeScript对Symbols有着完备的支持, 并且symbol在TypeScript的类型系统中也是重要的组成成员. symbol本身是一个数据类型注解. 参考这个之前出现过的extendObject的function例子. 我们可以通过使用symbol类型来允许symbols去extend我们的对象:

代码语言:javascript
复制
const sym = Symbol('foo')

function extendObject(obj: any, sym: symbol, value: any) {
  obj[sym] = value
}

extendObject({}, sym, 42) 

与此同时我们也拥有unique symbol这一子类型. unique symbol与声明紧密绑定. 只有明确的"这一个"symbol能够符合类型注解的要求.

此外, 要获取到unique symbol需要通过typeof操作符来获取

代码语言:javascript
复制
const PROD: unique symbol = Symbol('Production mode')
const DEV: unique symbol = Symbol('Development mode')

function showWarning(msg: string, mode: typeof DEV | typeof PROD) {
 // ...
}

Symbols的处于TS与JS的名词性(Nominal)与不透明(Opaque)类型之间的交集. 并且是在runtime时, 最接近名词性类型校验的东西. 也是一个很好的重建结构, 比如enums, 的方法.

译者注: 此处提到的Nominal与Opaque的翻译确实存在一些问题, 实际上举一个例子就能明白 Nominal类型是意义简单的, 能够从字面意义明白其意义的类型 const astr: string = 'test'; 这里面的string实际上就是一个Nominal的type Opaque类型是不透明的, 不明晰其结构和逻辑的 const asomeStr: SomeStr = astr; type SomeStr = string; 这里的SomeStr实际上就是一个Opaque的type, 此外asomeStr的赋值语句会报错. 重在理解, wiki上的定义着实有点不明不白

运行时Enums

Symbols有一个很有趣的应用环境 -- 重建enum (re-create enum). 就如同JavaScript在运行时的行为那样.

enums在TypeScript中是不透明的. 这意味着不能给enum变量赋予字符串的值, TypeScript将这些enum看做独一无二的存在.

代码语言:javascript
复制
enum Colors {
  Red = 'Red',
  Green = 'Green',
  Blue = 'Blue',
}

const c1: Colors = Colors.Red;
const c2: Colors = 'Red'; // Error会被抛出

此外还有下面这个有点意思的情况

代码语言:javascript
复制
enum Moods {
  Happy = 'Happy',
  Blue = 'Blue'
}

Moods.Blue === Colors.Blue; //will always be false

即使具有相同的值, 因为处在不同的enum之下, TypeScript认为他们是不相同的, 各自独一的, 也因此是不可比较的. 所以其中需要作出一些处理

在JavaScript中可以通过Symbol来定义enum从而达到类似的效果

代码语言:javascript
复制
// All Color symbols
const COLOR_RED: unique symbol = Symbol('RED')
const COLOR_ORANGE: unique symbol = Symbol('ORANGE')
const COLOR_YELLOW: unique symbol = Symbol('YELLOW')
const COLOR_GREEN: unique symbol = Symbol('GREEN')
const COLOR_BLUE: unique symbol = Symbol('BLUE')
const COLOR_INDIGO: unique symbol = Symbol('INDIGO')
const COLOR_VIOLET: unique symbol = Symbol('VIOLET')
const COLOR_BLACK: unique symbol = Symbol('BLACK')

// All colors except Black
const Colors = {
  COLOR_RED,
  COLOR_ORANGE,
  COLOR_YELLOW,
  COLOR_GREEN,
  COLOR_BLUE,
  COLOR_INDIGO,
  COLOR_VIOLET
} as const;

我们可以像TypeScript中使用Enums那样来使用这些symbols, 同时, 这些Symbols之间也是不可比较的.

代码语言:javascript
复制
function getHexValue(color) {
  switch(color) {
    case Colors.COLOR_RED: return '#ff0000'
    //...
  }
}

const MOOD_HAPPY: unique symbol = Symbol('HAPPY')
const MOOD_BLUE: unique symbol = Symbol('BLUE')

// All colors except Black
const Moods = {
  MOOD_HAPPY,
  MOOD_BLUE
} as const;

Moods.MOOD_BLUE === Colors.COLOR_BLUE // will always be false

这里有一些我们想要补充的TypeScript的注解

  1. 把所有的symbol键声明为unique symbol意味着我们给其赋予const值不能被改变的
  2. 把"enum"对象声明为const, TypeScript将不再让所有的symbol能够作为值被赋予到其中, 而是只有精确的"那一个"symbol可以.

这允许我们在为函数声明定义symbol enums时能够保证更好的类型安全. 为了获得一个对象的所有属性的类型, 我们定义一个辅助类型

代码语言:javascript
复制
type ValuesWithKeys<T, K extends keyof T> = T[K];
type Values<T> = ValuesWithKeys<T, keyof T>

注意需要使用as const, 这使得有效值的范围被限制在一个严格的范围之内

随后, 一个函数的声明可以像这样:

代码语言:javascript
复制
function getHexValue(color: Values<typeof Colors>) {
  switch(color) {
    case COLOR_RED:
      // Good
    case Colors.COLOR_BLUE:
      // Good
      break;
    case COLOR_BLACK: 
      // TypeScript会报错, 因为COLOR_BLACK并没有被声明
      break;
  }
}

当同时使用symbol作为键与键值时, 可以跳过之前的辅助类型直接使用

代码语言:javascript
复制
const ColorEnum = {
  [COLOR_RED]: COLOR_RED,
  [COLOR_YELLOW]: COLOR_YELLOW,
  [COLOR_ORANGE]: COLOR_ORANGE,
  [COLOR_GREEN]: COLOR_GREEN,
  [COLOR_BLUE]: COLOR_BLUE,
  [COLOR_INDIGO]: COLOR_INDIGO,
  [COLOR_VIOLET]: COLOR_VIOLET,
}

function getHexValueWithSymbolKeys(color: keyof typeof ColorEnum) {
  switch(color) {
    case ColorEnum[COLOR_BLUE]:
      // ?
      break;
    case COLOR_RED:
      // ?
      break;
    case COLOR_BLACK: 
      // ?
      break;
  }
}

这使得我们能够在编译时与运行时都能够获得类型安全性. 前者通过TypeScript的unique symbol, 后者通过JavaScript的Symbol的独一性.

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JavaScript中的Symbols
  • 全局Symbol注册
  • TypeScript中的Symbols
  • 运行时Enums
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档