百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

2025 年是时候重新认识 Symbol 的八大特性了?

myzbx 2025-07-17 22:54 23 浏览

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

1. 什么是 Symbol 原始类型

在 JavaScript 中,对象的属性键只能是字符串或 Symbol,那么什么是 Symbol 呢?

Symbol 是一种原始数据类型,类似于字符串或数字。其不能通过字面量创建,只能通过使用 Symbol 包装器对象构造函数来创建,任何使用 Symbol 作为构造函数(new)来创建显式 Symbol 包装器对象都会引发 TypeError。

const myNumber = Number(2);
console.log(myNumber);
// 打印: 2
const mySymbol = Symbol();
console.log(typeof mySymbol);
// 打印: "symbol"
console.log(mySymbol);
// 打印: Symbol()

在上面的例子中,如果参数本身不是数字,Number(argument) 会自动转换为数字。但 Symbol() 并没有参数,其不会涉及任何转换。

Symbol() 会创建隐藏的、唯一的原始值,尤其适用于对象属性的键。不过,开发者也可以向 Symbol() 添加字符串参数,其表示 Symbol 的描述。但实际上没有特殊意义,只是使 Symbol() 更具描述性或可识别性,而 Symbol.description 属性则原样返回 Symbol 的该只读描述。

// Symbol 的值是唯一的
console.log(Symbol("foo") === Symbol("foo"));
// 打印: false
const myObj = {};
myObj[Symbol("foo")] = 1;
myObj[Symbol("foo")] = 2;
console.log(myObj);
// 打印: {Symbol("foo"): 1, Symbol("foo"): 2 }
// 通过. description 属性获取 Symbol 描述
const mySymbol = Symbol("some description");
console.log(mySymbol.description);
// 打印: "some description"

2.Symbol 的类型转换

Symbol 永远无法强制转换为数字,否则抛出 TypeError 错误。

// 抛出 TypeError 错误
console.log(+Symbol());
console.log(Number(Symbol()));

Symbol 也不会强制转换为字符串(否则抛出 TypeError),但可以通过使用 String(symbol) 或 symbol.toString(),但不能通过 new String(symbol) 转换为字符串。

console.log(String(Symbol("foo")));
// 打印: "Symbol(foo)"
console.log(Symbol("foo") + "bar");
// 抛出错误 TypeError: can't convert symbol to string

最后值得一提的是,Symbol 转换为布尔值总是为 true:

console.log(Boolean(Symbol()));
// 打印: true
if (Symbol()) {
  console.log("This will be logged.");
}
// 打印: "This will be logged."

3.Symbol 作为属性键

前文讲过,Symbol 特别适合创建唯一的对象属性键且不会与其他键冲突,并且能够尽量隐藏该属性。

const symbolKey = Symbol();
// 创建一个 Symbol 的属性
const someObj = {
  [symbolKey]: "Some property value",
};
console.log(someObj[symbolKey]);
// 打印: "Some property value"

在上面的例子中,对象的属性不会被其他代码覆盖。同样,保存 Symbol 值的 symbolKey 也不能被覆盖,因为其是一个常量。如果这个常量的值不是 Symbol,例如是一个字符串,则可以被覆盖:

const stringKey = "keyName";
const someObj = {
  [stringKey]: "Some property value",
};
console.log(someObj.keyName === someObj[stringKey]);
 // 打印: true
console.log(someObj[stringKey]);
// 打印: "Some property value"
someObj.keyName = "Overwritten ";
console.log(someObj[stringKey]);
// 打印: "Overwritten "

4.Symbol 属性的可枚举性

与字符串键一样,带有 Symbol 键的属性默认设置为 可写、可枚举和可配置,除非是使用 Object.defineProperty() 创建的(默认将属性设置为 false)。并且,与带有字符串键的属性一样,带有 Symbol 键的属性也可以使用 Object.defineProperty() 进行更改。

const symbolKey = Symbol();
const someObj = {
  [symbolKey]: "Some property value",
};

const descriptor = Object.getOwnPropertyDescriptor(someObj, symbolKey);
console.log(descriptor);
// 打印结果为:
// {value: "Some property value", writable: true, enumerable: true, configurable: true}

带有字符串键的可枚举属性在 for...in 和 Object.keys 枚举中会被访问。然而,Symbol 键属性即使可枚举也会被跳过,正如在 MDN 文章 “属性的可枚举性和所有权” 的表格中所见,只有
Object.getOwnPropertySymbols 可以遍历 Symbol 键属性。

同时少数方法,例如:
Object.getOwnPropertyDescriptors 和 Object.assign ,也可以同时遍历字符串键和 Symbol 键属性。

const someObj = {
  prop1: 3,
  prop2: "Hello",
  [Symbol("symbol1")]: "symbol one",
  [Symbol("symbol2")]: "symbol two",
};
const objectStrings = Object.keys(someObj);
console.log(objectStrings);
// 打印结果: ["prop1", "prop2"]
const objectSymbols = Object.getOwnPropertySymbols(someObj);
console.log(objectSymbols);
// 打印结果: [Symbol("symbol1"), Symbol("symbol2") ]
objectSymbols.forEach((symbolKey) => console.log(someObj[symbolKey]));
// 打印结果:"symbol one", "symbol two"

值得注意的是,Object.assign 以及扩展语法 (...) 可用于克隆或合并对象,且包括 Symbol 键属性。

5. 聊聊全局 Symbol

Symbol.for(key) 会创建一个 全局 Symbol,可以通过使用相同的键重复执行来检索该 Symbol。

Symbol.for(key) 方法使用给定的键在 “全局 Symbol 注册表” 中搜索现有 Symbol,如果找到则返回该 Symbol,否则使用此键在全局 Symbol 注册表中创建一个新 Symbol。只有使用 Symbol.for() 创建的 Symbol 才会保留在全局 Symbol 注册表中,其被称为全局 Symbol 或共享 Symbol。

console.log(Symbol.for("foo") === Symbol.for("foo"));
// 打印: true
console.log(String(Symbol.for("foo")));
// 打印: "Symbol(foo)"

需要注意的是,全局 Symbol 也是唯一的值,键也是唯一的。全局 Symbol 不能从全局 Symbol 注册表中删除,也不能被覆盖。

与局部 Symbol 不同,全局 Symbol 可以跨文件和跨域使用。全局 Symbol 可以用作属性名称,以便将其在常见的遍历方法中隐藏。但是,使用全局 Symbol 作为属性键可以覆盖当前属性值。

const myObj = {
  [Symbol.for("foo")]: "some property value",
};
console.log(myObj[Symbol.for("foo")]);
// 打印: "some property value"
myObj[Symbol.for("foo")] = "Overwritten ";
console.log(myObj[Symbol.for("foo")]);
// 打印: "Overwritten ",且已经被覆盖

6. 全局 Symbol 注册

“全局 Symbol 注册表” 只是一个概念,用于描述仅可通过 Symbol.for() 和 Symbol.keyFor() 方法获取的全局 Symbol 的记录。

Symbol.keyFor() 方法从全局 Symbol 注册表中检索给定全局 Symbol 的全局 Symbol 键。

const globalSym = Symbol.for("someKey");
// 设置一个全局 Symbol
console.log(Symbol.keyFor(globalSym));
// 打印: "someKey"

全局 Symbol 注册表与全局对象不同,或者说其不是全局对象的一部分,全局 Symbol 注册表对所有关联的域 (realms) 都是全局的。本质上,全局 Symbol 是跨文件和跨域可用的,但每个文件和域都有各自的全局作用域。

关于域的概念下面在做一个深入的剖析:

一个应用程序可能由多个 JavaScript 环境组成,每个环境都有各自的全局作用域和全局对象,而环境被称为一个域 (Realm)。从代码内部打开的窗口、网页中的 <iframe> 以及 Web Worker 都是域。一个域中的代码可以访问其他关联域中的代码,但并不共享同一个全局作用域。

<iframe srcdoc="<script>window.parent.someFunction(['foo','bar'])</script>"></iframe>
<script>
function someFunction(arg) {
  console.log(arg instanceof Array); // logs: false
}
</script>

在上面的例子中,参数数组 ['foo', 'bar'] 是在 <iframe> 窗口中通过 <iframe> 域中的 Array 构造函数构造的,并且属于该全局作用域的一部分。因此,该数组不是父窗口(父窗口位于另一个域)中 Array 的实例。

7. 有那些常见的 JS 内置 Symbol

Symbol 构造函数提供了许多 “内置” 属性,这些属性本身都是 Symbol,被称为内置 Symbol。这些内置 Symbol 用作属性名或方法名,JavaScript 可以 “识别” 这些属性名并在内部使用属性名来标识某些操作的 “协议”。

开发者可以通过属性值自定义这些操作,从而自定义对象的行为:

const iterable1 = {};
iterable1[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};
console.log([...iterable1]);
// 打印 Array [1, 2, 3]
console.log(Symbol.keyFor(Symbol.iterator));
// 打印 undefined

那么第二个日志为什么输出 undefined?这是因为 Symbol.iterator 是 JavaScript 内置的一个 Symbol 值,用于定义对象的迭代器协议。其是一个静态的、不可变的 Symbol ,且是通过 Symbol(description) 创建的,而不是通过 Symbol.for(key) 创建的。

注意:通过 Symbol() 创建能保证属性不冲突。

当然常见的 Symbol 还包括 Symbol.toPrimitive,所有类型强制转换算法都会在对象上查找此 Symbol,查找接受首选类型并返回对象原始表示的方法,最后默认使用对象的 valueOf() 和 toString() 。

const object1 = {
  [Symbol.toPrimitive](hint) {
    if (hint === "number") {
      return 42;
    }
    return null;
  },
};
console.log(+object1);
// 预期输出: 42

8. 非全局 Symbol 可以正常垃圾回收

前端开发者都知道 WeakMap 和 WeakSet 只能存储对象或 Symbol,这是因为只有对象会被垃圾回收,原始值可以被伪造 (forged),即: 1 === 1 但 {} !== {},从而会使得原始值永远存在于集合中。

注意:forged 意味着可以被模仿、复制或重新创建。换句话说,原始值由于其简单性和不可变性,很容易被重新生成或伪造出相同的值。

例如下面是原始值伪造的示例:

const original = 42;
const forged = 42;
console.log(original === forged);
// true

同时,通过 Symbol.for("key") 创建的全局 Symbol 也可以被伪造,因此无法被垃圾回收。

const sym1 = Symbol.for("myKey");
let sym1Ref = sym1;
sym1Ref = null;
// 注意:原始值是直接拷贝,而非引用赋值
// 再次通过相同的 key 获取这个 Symbol
const sym2 = Symbol.for("myKey");
console.log(sym1 === sym2);
// true,证明 sym1 仍然存在,未被垃圾回收

但使用 Symbol("key") 创建的 Symbol 是可以被垃圾回收的,因为 Symbol 是唯一具有引用标识的原始数据类型 ,即开发者不能两次创建相同的 Symbol,因此其在某种程度上表现得非常像对象。

也正是因为此,非全局 Symbol 可以存储在 WeakMap、WeakSet、WeakRef 和 FinalizationRegistry 对象中。

参考资料

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Memory_management

https://library.fridoverweij.com/docs/jstutorial/symbol.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

https://www.youtube.com/watch?v=6R82DEqrelw

相关推荐

OPPO Find X9手机曝料:6.6英寸屏幕、天玑9500芯片

IT之家8月27日消息,科技媒体xpertpick今天(8月27日)发布博文,报道称OPPO计划于今年10月推出FindX9系列旗舰手机,其中包括FindX9和...

OPPO Find X9系列搭载影像新硬件,支持Ultra级画质和色彩还原

IT之家8月27日消息,OPPOFindX9系列手机发布时间逐渐临近,目前官方已开启新机的前瞻预热。OPPOFind系列产品负责人周意保今日发文解释了厂商为什么现在都喜欢跨界合作这一...

我回来了!聊聊屏幕对续航的影响_屏幕耗电吗

时隔一周终于回国,让大家久等了本来上周日就能到家,结果在旧金山转机的时候把护照弄丢了…幸好后来被一位黑人大姐找到了,才能顺利回国,感谢勤劳朴实的美利坚人民。出差途中笔记本的续航是很重要的,刚好联想的产...

J人福音、P人救星,Lumix Flow如何重塑专业视频拍摄工作流

“等一下,刚才那个中景拍了没有?”“A机位的素材是哪一场的?”“完了,我忘了记哪一条是最好的了!”“今晚加个班,先把能用的素材挑出来……”作为经常一个人拍视频的内容创作者,这种崩溃称得上习以为常。如果...

realme史上最窄边框和下巴 realme GT Neo3正式发布

中关村在线消息:今天下午14点,realme召开真我GTNeo3发布会。realmeGTNeo3搭载6.7英寸2412×1080OLED直屏,其支持120Hz刷新率,360Hz触控采样率,智能...

用酒精擦屏幕,对屏幕的伤害有多大?

天府新青年你触手可及的朋友圈附录:1.不是所有电脑的屏幕都不能用酒精来擦,通常来说只有镜面屏屏幕才有涂层,这种不能用酒精擦;而雾面屏用的是另外一种抗反射技术,这种一般擦了没事。镜面屏和雾面屏特别好认...

windows11截屏快捷键是哪个?windows11快捷键设置大全

windows11键盘快捷方式就是键盘快捷方式就是按键或按键组合,可提供一种替代方式来执行通常使用鼠标执行的操作。下面就来分享下windows11截屏快捷键是哪个和windows11快捷键设置大全。一...

三星Galaxy S25 Slim配置曝光 6.7英寸屏幕搭配2亿像素主摄

【CNMO科技新闻】三星GalaxyS25系列将于北京时间1月23日正式发布,CNMO注意到有博主爆出了即将亮相GalaxyS25Slim的配置信息。据悉,GalaxyS25Slim将配备一...

两种手机屏幕到底有什么不一样?哪种手机屏幕更好?

一般来说,我们的手机屏幕只分为两种OLED和LCD,LCD是大火的一种手机屏幕,是千元机以及高端机的标配,OLED算是后起之秀,是近几年才渐渐兴起的一种类型的手机屏幕,那么这两种手机屏幕到底有什么不一...

有强芯才好用 这三款高性价比旗舰芯热机最低仅需1799元

在选购手机时,相信大家肯定都会把性能作为考虑的重点之一。而如果希望拥有出色的性能表现,一颗旗舰处理器是必不可少的。今天我就为大家汇总了几款采用旗舰处理器的底价新机,感兴趣的朋友千万不要错过。moto...

一文搞定FastDFS的搭建和使用_fastdfs怎么样

1.FastDFS概述FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文...

涨姿势!超级计算机用啥文件系统呢?

2015-10-1705:58:00作者:赵为民在计算机中,文件系统(filesystem)是一个非常重要的组件,你可以将他看做是操作系统的子系统,其实质就是一种软件的组件,通过文件系统我们可以...

Window as a VM:Chrome OS 现可窗口化运行其它 Linux 分支

这世上纵然有多种办法可以在Chromebook上安装运行ChromeOS和其它Linux分支多系统,但如果无需重启通过引导切换,确实是个很酷的改进。Google布道师Francois...

Win10新预览版19577开始推送:新图标+多项新功能

今日凌晨,微软正式向Windows10Insider快速通道用户推送了全新版本Windows10——Windows10InsiderPreviewBuild19577。19577版本是...

微软Windows升级密钥(例如家庭版升级为企业版)

下面的密钥,是微软官方提供的,仅能用于Windows10系统版本的升级,比如从家庭版升级为专业版、专业版升级为企业版等。升级密钥不能用于激活系统,激活需要KMS或者数字权利,由于涉及到版权问题,在此不...