JavaScript ES6 奇妙的Symbol类型
一、Symbol
Symbol 中文释义符号、象征,有时也被用来做变量名,在 ES6 中将其变为了一种基本数据类型。每个从 Symbol()
返回的 symbol 值都是唯一的,这就意味着symbol能做为对象属性的标识符,我想这也是将这种数据类型命名为 Symbol 的原因之一。
使用 Symbol 的基本语法为 Symbol([description])
,description
参数为对symbol的描述,可用于调试但不是访问symbol本身。
// 声明一个 symbol 类型
const demo = Symbol("demo")
// 每一个symbol都是唯一的,即使描述值相等
console.log(Symbol("demo") === Symbol("demo"))
二、Symbol类型判断与转换
-
使用抽象(宽松)相等时,对象类型与 Symbol类型相等,字符串类型与 Symbol类型不相等。
const demo = Symbol("demo"); console.log(demo == Object(demo)) // true console.log(demo == String(demo)) // false
-
将一个 Symbol 数据类型转换为一个 Number 值时,会抛出一个
TypeError
错误。 -
symbol会阻止隐式地创建一个新的 string 类型并抛出 TypeError 异常。
const demo = Symbol("demo"); console.log({"name": "张三"} + "罗翔") // [object Object]罗翔 console.log(new Map([["name", "张三"]]) + "罗翔") // [object Map]罗翔 console.log([1, 2, 3] + "罗翔") // 1,2,3罗翔 console.log(demo + "bar") // 抛出 TypeError 异常
-
将 Symbol 数据类型转换为字符串类型要分为两种,一种是直接的字符串转换,另外一种则是取出对symbol的描述值,也就是在创建 Symbol 数据类型时传入的参数,第二种使用更多些。
直接转换
const demo = Symbol("demo"); console.log(demo.toString()) // Symbol(demo) console.log(String(demo)) // Symbol(demo)
描述值提取
const demo = Symbol("demo"); console.log(demo.description) // demo
三、Symbol方法
1)、Symbol.for(key)
给定 key 搜索现有的 symbol,找到则返回上次存储的那个 symbol,否则根据key在 全局symbol注册表 中创建一个新的symbol对象。
const demo = Symbol.for("demo");
console.log(demo === Symbol.for("demo"))
// true
console.log(Symbol.for("demo") === Symbol.for("demo"))
// true
console.log(demo.toString())
//Symbol(demo)
与 Symbol()
不同的是,使用 Symbol.for()
方法创建的 symbol 会放入全局 symbol 注册表中,而 Symbol()
不会。
这就意味着使用 Symbol()
创建的 symbol 与使用 Symbol.for()
方法返回的不是同一个。
const sym = Symbol("hello");
const sym_f1 = Symbol.for("hello")
const sym_f2 = Symbol.for("hello")
console.log(sym === sym_f1)
// false
console.log(sym === sym_f2)
// false
console.log(sym_f1 === sym_f2)
// true
console.log(Symbol.keyFor(sym))
// undefined
2)、Symbol.keyFor(sym)
与 Symbol.for()
方法一样,Symbol.keyFor(sym)
方法也涉及到 全局symbol注册表。Symbol.keyFor(sym)
方法用于获取全局 symbol 注册表中与某个 symbol 关联的键,Symbol()
所创建的 symbol 不能查找。
const sym = Symbol("hello");
const demo = Symbol.for("demo");
console.log(Symbol.keyFor(sym));
// undefined
console.log(Symbol.keyFor(demo));
// demo
{
console.log(Symbol.keyFor(demo))
// demo
}
四、Symbol原型
实例属性
1)、Symbol.prototype.description
只读属性,返回 Symbol 对象的描述的字符串
const sym = Symbol("hello");
console.log(sym.description)
// hello
实例方法
1)、Symbol.prototype.toString
返回当前 symbol 对象的字符串表示
// 直接拼接会抛出数据类型异常
Symbol("a") + "b";
// 直接拼接会抛出数据类型异常:TypeError: Can't convert symbol to string
Symbol("a").toString() + "b"
// "Symbol(a)b"
Object(Symbol("foo")).toString() + "bar"
// 使用对象中toString方法也是可以的:"Symbol(foo)bar"
注意:此方法重写了原型链上的
Object.prototype.toString()
方法
2)、Symbol.prototype.valueOf
返回当前 symbol 对象所包含的 symbol 原始值
const sym = Symbol("hello");
console.log(sym);
console.log(sym.valueOf());
console.log(sym === sym.valueOf());
Symbol(hello)
Symbol(hello)
true
五、Symbol简单的应用场景
1)、手撕函数调用方式 call、apply、bind
根据 Symbol 值都是不相等的特性,将其作为标识符,用于对象的属性名,保证不会出现同名的属性,有效防止某一个键被不小心改写或覆盖。
Function.prototype._call = function (fn, ...args) {
// 声明临时变量
const func = Symbol("func");
// 将目标方法拷贝到this执行的对象上
fn[func] = this;
// 执行方法并获取返回结果
const result = fn[func](...args);
// 删除拷贝方法
delete fn[func];
// 返回结果
return result;
}
const obj1 = {
func: function (...args) {
console.log(this.name)
console.log(args)
}
}
const obj2 = {
name: "张三"
}
obj1.func._call(obj2, 1, 2, 3, 4, 5)
张三
[1, 2, 3, 4, 5]
2)、消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
使用 Symbol 类型是一个很好的方案。
const Tags = {
hot: Symbol("hot"),
news: Symbol("news"),
likes: Symbol("likes")
}
function getTags(tab) {
switch (tab) {
case Tags.hot:
return `<h1>热门</h1>`
case Tags.news:
return `<h1>最新</h1>`
case Tags.likes:
return `<h1>最受欢迎</h1>`
}
}
document.write(getTags(Tags.hot))
参考资料💕
- 官方手册