2025 年是时候重新认识 Symbol 的八大特性了?
itomcoil 2025-06-13 14:09 5 浏览
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
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
相关推荐
- 麦克斯韦方程高斯-博内-陈定理嘉当外微分斯托克斯方程之间的联系
-
一、麦克斯韦方程的外微分形式1.电磁场张量的几何表示在微分几何中,电磁场由2-形式F描述,称为电磁场强形式:其中E为电场,B为磁场,dt为时间坐标。2.麦克斯韦方程的微分形式第二式(无...
- 机器视觉(四):空域图像增强(常见的空域图像增强处理方法有哪些)
-
一、图像处理技术概述1.定义对原始获取图像进行一系列的运算处理,称为图像处理。图像处理是机器视觉技术的方法基础,包括图像增强、边缘提取、图像分割、形态学处理、图像投影、配准定位和图像特征提取等方法。2...
- 基于机器视觉的安全气囊检测系统研究
-
21世纪以来,汽车行业发展迅猛。安全气囊作为汽车的安全辅助工具,它的出现大大降低了死亡率,因而必须确保其尺寸精确,并能正常使用。对安全气囊传统的检测方法主要是借助千分表、轮廓仪等工具进行人工抽检[...
- 高斯泼溅综合指南(高斯pp)
-
高斯泼溅(GaussianSplatting)是一种表示3D场景和渲染新视图的方法,在“实时辐射场渲染的3D高斯泼溅”中引入。它可以被认为是NeRF类模型的替代品,就像当年的NeRF...
- 基于DSP和ARM的电气化铁路接触线覆冰监测系统设计
-
张学武(中铁第一勘察设计院集团有限公司,陕西西安710043)摘要:为预防接触线上的冰害事故以及为除冰融冰提供支持,设计了集成视频传输、覆冰厚度测量、导线温度测量、现场微气象采集的接触线覆冰监测系...
- OpenCV 线性滤波(opencv 线性拟合)
-
OpenCV线性滤波线性滤波图像滤波与滤波器图像滤波,指的是在尽量保留图像特征的条件下对目标图像得噪声进行抑制,是图像处理当中不可缺少的部分。平滑录播室低频增强的空间域滤波技术,它的目的有两类:一类...
- 几种典型的图像去噪算法总结(图像去噪的定义)
-
(一)高斯低通滤波去噪高斯低通滤波器(GaussianLowPassFilter)是一类传递函数为高斯函数的线性平滑滤波器。又由于高斯函数是正态分布的密度函数。因此高斯低通滤波器对于去除服从正态...
- 深度解析卷积:从原理到应用(卷积到底是什么)
-
一、卷积的基本概念卷积是一种在数学领域尤其是分析数学中占据重要地位的运算方式。它通过两个函数和来生成第三个函数。设和是上的两个可积函数,卷积的积分表达式为:。在离散情况下,卷积定义为。从...
- 基于混合高斯模型的物体成分拟合方法
-
郎波,樊一娜,黄静,王鹏(北京师范大学珠海分校信息技术学院,广东珠海519087)为了寻求代价更小、效率更高、适应性更强的图像原型表征方法,借鉴成分识别理论的观点,设计出一种更符合人类认知原...
- 30天Python 入门到精通(python入门到精通书籍)
-
以下是一个为期30天的Python入门到精通学习课程,专为零基础新手设计。课程从基础语法开始,逐步深入到面向对象编程、数据处理,最后实现运行简单的大语言模型(如基于HuggingFace...
- Python 最好用的 8 个 VS Code 扩展
-
1.PythonextensionforVisualStudioCode这个扩展是由微软官方提供的,支持但不仅限于以下功能:通过Pylint或Flake8支持代码检查在VSCo...
- Textual:为 Python 增加漂亮的文本用户界面(TUI)
-
导读:如果你的代码是用Python编写的,你应该使用Textual来帮助你编写TUI(文本用户界面)。快速入门使用TextualPython在Linux上有像TkInterdocs...
- pytest框架进阶自学系列 | 常用插件的使用
-
书籍来源:房荔枝梁丽丽《pytest框架与自动化测试应用》一边学习一边整理老师的课程内容及实验笔记,并与大家分享,侵权即删,谢谢支持!附上汇总贴:pytest框架进阶自学系列|汇总_热爱编程的通...
- ShapeNet数据集及dataset代码分析
-
1.数据集简介ShpaeNet是点云中一个比较常见的数据集,它能够完成部件分割任务,即部件知道这个点云数据大的分割,还要将它的小部件进行分割。它总共包括十六个大的类别,每个大的类别有可以分成若干个小类...
- 「教程」5 分钟带你入门 kivy(怎么学kp)
-
原创:星安果AirPythonkivy语言通过编写界面UI,然后利用Python定义一些业务逻辑,可以移植很多功能模块到移动端直接执行。下面对kivy常见用法做一个汇总。1、什么是...
- 一周热门
- 最近发表
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)