你用过Symbol吗?你知道魔术字符串吗?

1,068 阅读3分钟

魔术字符串

魔术字符串(magic string), 相信你在浏览大佬英文文章时一定见过. 魔术字符串指的是在代码之中多次出现、与代码强耦合的某个具体的字符串。

与之相类似的还有一个魔术数字(magic number)。

魔术数字

在eslint中可以使用规则no-magic-numbers来辅助检查魔术数字, 通常我们普遍认为:

// Bad
var dutyFreePrice = 100
var finalPrice = dutyFreePrice + (dutyFreePrice * 0.25)
// Good
var TAX = 0.25
var dutyFreePrice = 100
var finalPrice = dutyFreePrice + (dutyFreePrice * TAX)

并不是所有的魔术数字都是不好的, 下面的代码示例展示了这一点:

// Bad
const HOURS_PER_DAY = 24;
const MINUTES_PER_HOUR = 60;
const SECONDs_PER_MINUTE = 60;
const MS_PER_SECOND = 1000;
if (type === 'day') {
   MS = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDs_PER_MINUTE * MS_PER_SECOND
}
// 变量命令不是越长越好或越具体越好,而是根据具体的限定范围
// Good
MS = 0
if (type === 'day') {
  MS = 24 * 60 * 60 * 1000
}

魔术字符串

魔术字符串与之类似, 应尽量减少:

// Bad
if (code === '200') {} 
else if (code === '400') {}
// Good
const SUCCESS = '200'
if (code === SUCCESS) {}

同样, 事无绝对:

// Bad
name = 'jack'
const PRFIX = 'hello, '
console.log(PRFIX + name)
// Good
name = 'jack'
console.log("hello, " + name)
// Good
name = 'jack'
console.log(`hello, ${name}`)

Symbol

简单介绍

Symbol是js的原始数据类型,它表示独一无二的值。通常,对象的键以字符串的形式存在,这极易引发键名冲突问题,Symbol正是为此出现的。

应用场景

设想一段求面积的代码:

function getArea(model, size) {
  switch (model) {
     case 'rectangle':
          return size.width * size.height
     case 'triangle':
          return size.width * size.height / 2
    }
}

上面代码出现了魔术字符串, 我们把优化掉:

const judge = {
  rectangle:'rectangle',
  triangle:'triangle'
}
function getArea(model, size) {
  switch (model) {
     case judge.rectangle:
          return size.width * size.height
     case judge.triangle:
          return size.width * size.height / 2
    }
}

接着需要支持计算正方形面积, 我们可以在judeg里增加一个square属性, 但这时我们疏忽了, 既然正方形也是矩形, 那就这么写吧:

const judge = {
  rectangle:'rectangle',
  triangle:'triangle',
  square: 'rectangle'
}
function getArea(model, size) {
  switch (model) {
     case judge.rectangle:
          return size.width * size.height
     case judge.triangle:
          return size.width * size.height / 2
     case judge.square:
          return '计算square时, 我不会被执行'
    }
}

我们新添加的计算逻辑不会被执行, 因为和rectangle重复了, 使用symbol可以避免这些问题:

const judge = {
  rectangle:Symbol('rectangle'),
  triangle:Symbol('triangle'),
  square: Symbol('rectangle')
}
function getArea(model, size) {
  switch (model) {
     case judge.rectangle:
          return size.width * size.height
     case judge.triangle:
          return size.width * size.height / 2
     case judge.square:
          return '计算square时, 我会被执行'
    }
}

vue中使用

在vue使用symbol的一个常见常见是在provide/inject中, 当我们的上层组件组件希望为某些下层组件注入数据时, 使用symbol作为InjectionKey避免潜在的冲突:

export const myInjectionKey = Symbol()
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'

provide(myInjectionKey, { /*
  要提供的数据
*/ });
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)

由于provide/inject支持使用Symbol作为key, 我们可以provide/inject来封装一个多组件共享的上下文:link.juejin.cn/?target=htt….

链接中本质就是provide(Symbol("context"), 共享的数据对象), 这不会影响组件继续provide("context", 123), 因而创建的上下文环境与组件的provide/inject实现了隔离.

参考文章:

为什么eslint没有 no-magic-string? - 知乎 (zhihu.com)

学长突然问我用过Symbol吗,我哽咽住了(准备挨骂) - 掘金 (juejin.cn)