• Symbol类型的妙用
  • 发布于 2个月前
  • 277 热度
    0 评论
关于Symbol
每一种新的类型引入都是为了解决某些具体的问题,Symbol的引入则是为了避免对象属性被意外覆盖和修改,它用起来有点像枚举,是一种唯一值。 比如,我有用户对象。
let user = {
  id:'1',
  name:'Jack'
}

// 假设有个第三方库里面的方法
function foo(obj) {
  obj.id = '2'
}

foo(user);

console.log(user)
这种情况,user的id就被意外修改了,因为对象的属性的key默认都是字符串,是很容易被覆盖的。 为了避免这种情况,可以用Symbol。代码如下:
let userId = Symbol();
let user = {
  [userId]:'1',
  name:'Jack'
}
// 堆代码 duidaima.com
// 假设有个第三方库里面的方法
function foo(obj) {
  obj.id = '2'
}

foo(user);

console.log(user[userId]) // 还是1
我们定义了一个Symbol对象,作为user的属性。由于Symbol是不可变的,这样就保证了对象属性的安全性。另外,Symbol属性不能用点号去访问,只能用中括号。 但是,如果这个对象被传到其他组件里面去了,并且访问不到userId这个指针,该怎么办?
function getUser() {
      let userId = Symbol();
      let user = {
          [userId]:'1',
          name:'Jack'
      }
      return user;
  }
function foo(user) {
   //怎么在这里拿到id?
    console.log(user[Symbol()]) //这样拿到的是undefined
}

foo(getUser());
解决:Symbol.for 可以获取同一个Symbol对象。
function getUser() {
  let user = {
      [Symbol.for("userId")]:'1',
      name:'Jack'
  }
  return user;
}
function foo(user) {
    //怎么在这里拿到id?
    console.log(user[Symbol.for('userId')]) //这样拿到的是1
}
foo(getUser());
Symbol.for()会有一个登记机制,使用for只会对通过for创建的symbol进行检查,不会对Symbol()创建的进行检查。 也就是说,下面这两个是不相等的。
let a = Symbol('a');
let a1 = Symbol.for('a')
console.log(a === a1) // false
如果我想根据Symbol.for创建的变量,拿到里面的描述信息,可以用Synbol.keyfor ,注意Symbol创建的对象,还是拿不到描述信息的。
console.log(Symbol.keyFor(a)) // undefined
console.log(Symbol.keyFor(a1)) // a
总结:Symbol是一个唯一值,如果你不想复用同一个Symbol,就用Symbol创建,如果想复用,就用Symbol.for,如果你还想拿到Symbol.for的描述信息,就用Symbol.keyFor。

Symbol的其他用法
1)作为私有属性(依赖注入)
以前我们很少有私有熟悉的概念,比如:
class Person {
    constructor(name) {
        this.name = name;
    }
}
let p = new Person('Jack');
console.log(p.name)
name是实例属性,外头可以直接访问到,不能像强类型语言那样有private。但是我们可以利用Symbol唯一的特性,模拟私有属性。
const nameSymbol = Symbol('name')
class Person {
    constructor(name) {
        this[nameSymbol] = name;
        this.age = 18; //为了演示实例属性
    }

    getName() {
        return this[nameSymbol]
    }
}

// 假设在其他地方用到了person,但是访问不到name
let p = new Person('Jack');
console.log(p.getName()) // Jack
在外头你无法用for in和Object.getOwnPropertyNames(),甚至JSON.stringify来获取nameSymbol。
// 假设在其他地方用到了person,但是访问不到name
let p = new Person('Jack');
for (const pKey in p) {
    console.log(pKey) //只拿到一个age
}
console.log(Object.getOwnPropertyNames(p)) // ['age']
console.log(JSON.stringify(p)) // {"age":18}

//但是,你依然可以用
let ownPropertySymbols = Object.getOwnPropertySymbols(p);
let symbol = ownPropertySymbols[0];
console.log(p[symbol]) // Jack
类似的,在egg.js源码中,也有这样的用法:
const CALL = Symbol('BaseContextLogger#call');
class BaseContextLogger {
  [CALL](method, args) {
    // ...
    this.ctx.logger[method](...args);
  }
}
即在当前作用域内是有效的,但是最后export出去的肯定不包含CALL,所以就相当于是private了。 这种用法其实就是用到了Symbol的唯一性,只有当你拿到同一个Symbol对象的时候,才能取到对象里面这个属性的值。只要你不主动把Symbol导出,外界就永远无法访问。在框架里面,或者多模块开发的时候,如果你想在当前模块里面去修改原型方法,又不是改变全局,就比较实用了。但是我们业务开发几乎用不到。

2) 当枚举用,在同一个作用域中消除魔法值
let status = {
    AWAIT_SP : Symbol.for("1") , //待审批
    PASS : Symbol.for("2") ,     // 审批通过
    REJECT : Symbol.for("3") ,   // 审批退回
}
console.log(Symbol.keyFor(status.AWAIT_SP)) // 1
3) 实现一个可遍历的对象
所谓可遍历的对象(也叫可迭代),就是能被for of遍历的对象。Object默认是不行的,但是Array,Set,Map可以,因为他们有Symbol.iterator属性。 Symbol.iterator是一个常量,可以直接访问图片我们通过定义这个属性,得知每次迭代的结果。 沿用上面的例子:
class Person {

    constructor(name) {
        this[nameSymbol] = name;
        this.age = 18; //为了演示实例属性
        this.sex = '男' //为了演示实例属性
    }

    getName() {
        return this[nameSymbol]
    }
}
let p = new Person('Jack');
for (let item of p) {
    console.log(item)
}
因为没有Symbol.iterator,所以会报错图片改造一下:
class Person {
    constructor(name) {
        this[nameSymbol] = name;
        this.age = 18; //为了演示实例属性
        this.sex = '男' //为了演示实例属性
        this.hobbies = ['吃饭','睡觉','打豆豆']
        this.index = 0;
    }

    [Symbol.iterator] () {
        return {
            next: () => {
                if(this.index > this.hobbies.length - 1) {
                    return { done: true, value: undefined };
                }
                return { done: false, value: this.hobbies[this.index++] };
            }
        }
    }

    getName() {
        return this[nameSymbol]
    }

}
let p = new Person('Jack');
for (let item of p) {
    console.log(item) 
}

Symbol.iterator方法需要返回一个包含next方法的对象,然后next方法必须返回一个特定的对象,里面有done和value属性,这些都是约定好的。结果如下:

用户评论