谈谈对 Symbol 的理解

111 阅读2分钟

《用得上的前端知识》系列 - 你我都很忙,能用100字说清楚,绝不写万字长文

简介

Symbol 是ES6中引入了一种新的基础数据类型,其特点有:

  • 每个从Symbol() 返回的 symbol 值都是唯一的;
  • symbol 值不可枚举,需使用特定的方法才能查看,如:
    • Object.getOwnPropertySymbols
    • Reflect.ownKeys

使用

let s1 = Symbol();
let s2 = Symbol();
let s3 = Symbol('name'); // 'name'只是一个描述信息,并不会影响 Symbol 的值
let s4 = Symbol('name');

console.log( s1 === s2 ); // false
console.log( s2 === s3 ); // false
console.log( s3 === s4 ); // false

属性遍历

let obj = {
   [Symbol('name')]: '一斤代码',
   age: 18,
   title: 'Engineer'
}

// 使用Object的API
console.log( Object.getOwnPropertySymbols(obj) ); // [Symbol(name)]

// 使用新增的反射API
console.log( Reflect.ownKeys(obj) ); // [Symbol(name), "age", "title"]

应用场景

应用场景1:使用Symbol来作为对象属性名(key)

const PROP_NAME = Symbol()
const PROP_AGE = Symbol()

let obj = {
  [PROP_NAME]: "一斤代码"
}
obj[PROP_AGE] = 18

obj[PROP_NAME] // '一斤代码'
obj[PROP_AGE] // 18

应用场景2:使用Symbol来替代常量

使用常量不好的地方在于,为常量赋一个唯一的值(比如这里的'AUDIO'、'VIDEO'、 'IMAGE'),常量少的时候还算好,但是常量一多,取名字就比较费神了,同时,常量一多,也容易出现重复。

const TYPE_AUDIO = 'AUDIO'
const TYPE_VIDEO = 'VIDEO'
const TYPE_IMAGE = 'IMAGE'

function handleFileResource(resource) {
  switch(resource.type) {
    case TYPE_AUDIO:
      playAudio(resource)
      break
    case TYPE_VIDEO:
      playVideo(resource)
      break
    case TYPE_IMAGE:
      previewImage(resource)
      break
    default:
      throw new Error('Unknown type of resource')
  }
}

应用场景3:使用Symbol定义类的私有属性/方法

// a.js
const PASSWORD = Symbol();

class Login {
  constructor(username, password) {
    this.username = username
    this[PASSWORD] = password
  }

  checkPassword(pwd) {
      return this[PASSWORD] === pwd;
  }
}

export default Login;


// b.js
import Login from './a'

const login = new Login('admin', '123456')

login.checkPassword('admin')  // true

// 以下方式均无法访问 PASSWORD
login.PASSWORD;
login[PASSWORD]; 
login["PASSWORD"];

由于Symbol常量 PASSWORD 被定义在a.js所在的模块中,外面的模块获取不到这个Symbol,也不可能再创建一个一模一样的Symbol出来(因为Symbol是唯一的),因此这个 PASSWORD 的Symbol只能被限制在a.js内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。

注意事项

Symbol类型的key是不能通过 Object.keys() 或者 for...in 来枚举的,它未被包含在对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。

let obj = {
   [Symbol('name')]: '一斤代码',
   age: 18,
   title: 'Engineer'
}

Object.keys(obj)   // ['age', 'title']

for (let p in obj) {
   console.log(p)   // 分别会输出:'age' 和 'title'
}

Object.getOwnPropertyNames(obj)   // ['age', 'title']

注册和获取全局的 Symbol

适用于涉及到多个window(最典型的就是页面中使用了)的场景:

let gs1 = Symbol.for('global_symbol_1')  //注册一个全局Symbol
let gs2 = Symbol.for('global_symbol_1')  //获取全局Symbol

gs1 === gs2  // true

参考资料