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。
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导出,外界就永远无法访问。在框架里面,或者多模块开发的时候,如果你想在当前模块里面去修改原型方法,又不是改变全局,就比较实用了。但是我们业务开发几乎用不到。
let status = { AWAIT_SP : Symbol.for("1") , //待审批 PASS : Symbol.for("2") , // 审批通过 REJECT : Symbol.for("3") , // 审批退回 } console.log(Symbol.keyFor(status.AWAIT_SP)) // 13) 实现一个可遍历的对象
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属性,这些都是约定好的。结果如下: