ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

千古前端教程:ES6 Symbol 详解——独一无二的第七种数据类型

千古前端教程:ES6 Symbol 详解——独一无二的第七种数据类型 文档教程前端【免费下载链接】Web千古前端图文教程超详细的前端入门到进阶知识库。从零开始学前端做一名精致优雅的前端工程师。项目地址https://gitcode.com/gh_mirrors/we/Web点击查看免费下载导读本文基于「千古前端图文教程」仓库中 ES6Symbol.md 一文整理并深度扩充。你将系统掌握 ES6 引入的第七种原始数据类型 Symbol理解它为什么能解决对象属性名冲突、如何创建与传参标识、如何安全地作为对象属性使用、如何利用它定义常量并通过仓库中 迭代器和生成器、浅拷贝和深拷贝 等进阶文档的源码示例理解Symbol.iterator等内置 Symbol 值在可迭代对象、for...of遍历中的真实运行机制。读完后你将能够在实际项目中用 Symbol 写出命名更安全、语义更清晰、不易被外部意外访问或覆盖的代码。一、为什么需要 Symbol属性名冲突的背景在 ES5 时代JavaScript 对象的属性名只能是字符串。当多个模块、多个开发者或第三方库往同一个对象上挂载属性时字符串属性名极易「撞车」——同名属性会互相覆盖从而污染全局环境或共享对象。例如两个不同的库都向某个对象写入name属性后写入的一方会静默覆盖先写入的值且这种覆盖往往难以排查let obj {}; obj.name 库A的属性; obj.name 库B的属性; // 静默覆盖A 的赋值丢失正是为了解决这类命名冲突问题ES6 引入了全新的原始数据类型Symbol它表示独一无二的值。二、Symbol 是什么第七种数据类型概念Symbol 是 JavaScript 语言的第七种数据类型。前六种分别是undefined、null、布尔值Boolean、字符串String、数值Number、对象Object。在仓库的基础文档 变量的数据类型基本数据类型和引用数据类型 中Symbol 被明确归入基本数据类型值类型与 String、Boolean、Number、Undefined、Null、BigInt 并列并注明「BigInt 和 Symbol 是 ES6 中新增的类型」。也就是说Symbol 不是引用类型它按值传递、不可被修改且每次创建都是独一无二的。Symbol 的三个核心特点Symbol 属性对应的值是唯一的从根本上解决命名冲突问题Symbol 值不能与其他数据进行计算包括不能与字符串拼串for...in、for...of遍历时不会遍历 Symbol 属性——Symbol 属性天然具有「隐形」效果不会被常规遍历误伤。三、创建 Symbol它是函数不是构造函数Symbol 是函数但并不是构造函数。因此不能使用new Symbol()来创建只能直接调用Symbol()let mySymbol Symbol(); console.log(typeof mySymbol); // 打印结果symbol console.log(mySymbol); // 打印结果Symbol()关键点typeof mySymbol的结果是symbol这是一个独立于string、number、object等类型的全新类型标识直接打印mySymbol得到的是Symbol()这是 Symbol 值的字符串描述形式description 为空时的显示。3.1 两次调用 Symbol()值一定不相等即使不带任何参数每次调用Symbol()都会生成一个全新的、唯一的标识let mySymbol1 Symbol(); let mySymbol2 Symbol(); console.log(mySymbol1 mySymbol2); // 打印结果false console.log(mySymbol1); // 打印结果Symbol() console.log(mySymbol2); // 打印结果Symbol()mySymbol1 mySymbol2打印为false证明二者的值确实不相等。但最后两行打印结果肉眼看起来完全相同都是Symbol()那么该如何区分它们呢3.2 传入参数作为标识description既然 Symbol 是函数函数就可以接收参数。可以在括号中传入一个描述字符串description作为区分不同 Symbol 的标识。描述字符串不影响 Symbol 的唯一性只用于调试和阅读// 在括号里加入参数来标识不同的 Symbol let mySymbol1 Symbol(one); let mySymbol2 Symbol(two); console.log(mySymbol1 mySymbol2); // 打印结果false console.log(mySymbol1); // 打印结果Symbol(one) console.log(mySymbol2); // 打印结果Symbol(two) console.log(mySymbol2.toString()); // 打印结果Symbol(two)注意区分直接打印mySymbol2控制台输出Symbol(two)并且通常以特殊颜色如红色高亮显示 Symbol 值调用mySymbol2.toString()得到的是字符串形式的Symbol(two)以普通颜色黑色显示它返回的是字符串而非 Symbol 本身。描述参数还可以在需要时通过Symbol.prototype.description属性读取现代浏览器支持方便在日志中定位具体是哪一个 Symbol。四、将 Symbol 作为对象的属性值4.1 必须使用属性选择器方括号添加由于 Symbol 是独一无二的值它非常适合直接作为对象的属性名这样无论多少代码往对象上挂 Symbol 属性都绝不会重名、绝不互相覆盖let mySymbol Symbol(); let obj { name: smyhvae, age: 26 }; // obj.mySymbol male; // 错误不能用 . 这个符号给对象添加 Symbol 属性 obj[mySymbol] hello; // 正确通过属性选择器方括号给对象添加 Symbol 属性 console.log(obj);这里有一个非常重要的语法细节不能使用点号.给对象添加 Symbol 属性。原因在于obj.mySymbol中的mySymbol会被解析为字符串属性名而obj[mySymbol]中的mySymbol是变量方括号语法会把变量的值即独一无二的 Symbol作为属性键。这也是「计算属性名」的典型用法。4.2 for...in / for...of 遍历会跳过 Symbol 属性给上面的 obj 添加 Symbol 属性后用for...in遍历let mySymbol Symbol(); let obj { name: smyhvae, age: 26 }; obj[mySymbol] hello; console.log(obj); // 遍历 obj for (let i in obj) { console.log(i); }打印结果中只会出现name和age两个字符串属性名Symbol 属性不会出现在遍历结果中。同样的规则也适用于for...of以及Object.keys()、JSON.stringify()等基于可枚举字符串属性的操作。如何获取 Symbol 属性如果确实需要访问对象上的 Symbol 属性可以使用专门针对 Symbol 的 APIObject.getOwnPropertySymbols(obj)返回对象自身的所有 Symbol 属性键数组Reflect.ownKeys(obj)返回对象自身的全部属性键包括字符串键和 Symbol 键。4.3 妙用隐藏的内部状态正因为 Symbol 属性不被常规遍历、Object.keys()和 JSON 序列化波及它天然适合存放对象内部的「私有」状态。例如类内部缓存、标记位等外部遍历时看不到也不必担心与其他库的字符串属性冲突。五、用 Symbol 定义常量Symbol 可以用来定义常量保证常量值独一无二避免魔数或字符串常量在不同模块间撞车const MY_NAME Symbol(my_name);在实战中一个常见的模式是「Symbol 常量 switch 判断」——用 Symbol 常量作为分发的键即使不同模块恰好使用了相同的描述字符串它们的值也互不相同逻辑不会串味。这种方式与仓库基础文档中关于常量和变量的约定一致同时也比字符串常量更安全。六、内置的 Symbol 值语言内部使用的特殊属性除了定义自己使用的 Symbol 值以外ES6 还提供了 11 个内置的 Symbol 值它们指向语言内部使用的方法。这些内置 Symbol 以Symbol.xxx的形式暴露其中最常用、也最能体现 Symbol 价值的是Symbol.iterator。6.1 Symbol.iterator默认遍历器方法对象的Symbol.iterator属性指向该对象的默认遍历器方法。任何对象只要实现了[Symbol.iterator]方法就成为一个可迭代对象iterable可以被for...of、展开运算符、解构赋值等语法直接遍历。仓库的进阶文档 迭代器和生成器 给出了详细的源码级演示。首先数组本身就有默认的Symbol.iteratorconst myArr [a, b, c]; console.log(typeof myArr[Symbol.iterator]); // function console.log(myArr[Symbol.iterator]()); // 返回迭代器对象 Array Iterator6.2 将普通对象封装为可迭代对象普通对象默认不是可迭代对象直接for...of遍历会报错const myObj2 { name: qianguyihao, skill: web, }; for (const item of myObj2) { console.log(item); // 报错Uncaught TypeError: myObj2 is not iterable }解决办法就是在对象内部创建一个名为[Symbol.iterator]的迭代器函数这个函数名是固定的属于计算属性名写法函数内部返回一个符合迭代器协议的对象包含next()方法next()返回{ done, value }const myObj1 { strArr: [qian, gu, yi, hao], // 在 myObj1 的内部创建一个迭代器 [Symbol.iterator]: function () { let index 0; const strArrIterator { next: function () { if (index myObj1.strArr.length) { return { done: false, value: myObj1.strArr[index] }; } else { return { done: true }; } }, }; return strArrIterator; }, };也可以把next改写成箭头函数用this.strArr取代外层对象引用写法更简洁箭头函数不绑定自己的this会沿用外层[Symbol.iterator]函数调用时的this。封装完成后for...of就能按迭代器协议依次取出数据也可以手动调用迭代器的next()方法逐步遍历const iterator myObj1[Symbol.iterator](); console.log(iterator.next()); // {done: false, value: qian} console.log(iterator.next()); // {done: false, value: gu} console.log(iterator.next()); // {done: false, value: yi} console.log(iterator.next()); // {done: false, value: hao} console.log(iterator.next()); // {done: true}再进一步可以利用Object.entries(this)把对象自身的键值对封装进迭代器从而让普通对象能够用for...of遍历键值对const myObj2 { name: qianguyihao, skill: web, [Symbol.iterator]: function () { const entries Object.entries(this); // 获取对象的键值对 let index 0; const iterator { next: () { if (index entries.length) { return { done: false, value: entries[index] }; } else { return { done: true }; } }, }; return iterator; }, }; for (const item of myObj2) { const [key, value] item; console.log(key, value); // name qianguyihao / skill web }6.3 在类中使用 Symbol.iterator面向对象开发时如果希望自定义类具备可迭代能力只需在类中定义一个[Symbol.iterator]实例方法同样是计算属性名写法class Person { constructor(name, arr) { this.name name; this.arr arr; } // 定义一个名为 [Symbol.iterator] 的实例方法封装迭代器 [Symbol.iterator]() { let index 0; const iterator { next: () { if (index this.arr.length) { return { done: false, value: this.arr[index] }; } else { return { done: true }; } }, }; return iterator; } } const person new Person(千古壹号, [前端, 工程师]); for (const item of person) { console.log(item); // 前端 / 工程师 }6.4 中断迭代迭代器的 return() 方法如果希望在遍历到某个条件时提前停止可以用break、return、throw中断迭代器其中break用得最多。更优雅的做法是在迭代器对象中提供与next()平级的return()方法监听迭代器的中断以便做清理工作。该完整示例同样收录于 迭代器和生成器。6.5 用生成器 function* 简化 Symbol.iterator从 ES6 起还可以直接用生成器函数function*来实现[Symbol.iterator]配合yield逐段产出值代码量大幅缩减语义也更清晰const myObj2 { name: qianguyihao, skill: web, // 通过生成器 function* 的方式实现可迭代对象 [Symbol.iterator]: function* () { const entries Object.entries(this); for (let index 0; index entries.length; index) { yield entries[index]; // yield 将每个阶段的值存入 next().value } }, }; for (const item of myObj2) { const [key, value] item; console.log(key, value); // name qianguyihao / skill web }这一写法在仓库文档中被明确总结为「用生成器代替迭代器」的推荐实践详见 迭代器和生成器 的「生成器的应用」一节。七、Symbol 与深拷贝Reflect.ownKeys 的补充Symbol 属性虽然不会被常规遍历枚举但它是对象真实存在的属性因此在进行深拷贝时必须被正确处理。仓库文档 浅拷贝和深拷贝 明确指出传统拷贝方式如展开运算符、JSON.parse(JSON.stringify())的缺点之一是无法拷贝不可枚举的属性以及 Symbol 类型。改进方案中给出的第一条正是针对能够遍历对象的不可枚举属性以及 Symbol 类型可以使用Reflect.ownKeys方法。示例思路完整实现见该文档的「方式3改进版」function cloneDeep(obj, map new WeakMap()) { if (typeof obj ! object || obj null) return obj; // ... 处理循环引用等逻辑后 let target {}; // 使用 Reflect.ownKeys 获取包括 Symbol 在内的全部属性键 Reflect.ownKeys(obj).forEach((key) { target[key] cloneDeep(obj[key], map); }); return target; }Reflect.ownKeys与Object.keys的区别正在于Object.keys只能拿到字符串属性键且不可枚举的拿不到而Reflect.ownKeys能拿到字符串键 Symbol 键 不可枚举键的全部集合。这正是 Symbol 属性在真实工程状态管理、缓存、序列化工具中需要被重视的原因。八、总结与常见误区要点结论数据类型Symbol 是 ES6 新增的第七种数据类型属于基本数据类型创建方式调用Symbol()函数不能用new Symbol()唯一性每次Symbol()调用都生成独一无二的值Symbol(a) Symbol(a)恒为false标识参数Symbol(desc)中的字符串仅用于调试标识不影响唯一性作为属性键必须用obj[symbolVar]方括号写法不能写obj.symbolVar遍历行为for...in、for...of、Object.keys()、JSON 序列化均不包含 Symbol 属性获取方式Object.getOwnPropertySymbols(obj)或Reflect.ownKeys(obj)内置值Symbol.iterator等 11 个内置 Symbol 指向语言内部方法是实现可迭代对象的关键常见误区提醒不要试图用new Symbol()创建Symbol 是函数不是构造函数会直接报错不要用点号给对象添加 Symbol 属性那只会创建一个字符串属性丢失唯一性语义Symbol 值不能与字符串拼串、不能参与数值运算Symbol() 会抛 TypeErrorSymbol 虽然「隐形」但并非真正的私有属性——Reflect.ownKeys依然可以拿到它深拷贝时不要遗漏。在「千古前端图文教程」的完整学习路线中Symbol 位于 ES6 语法 系列是后续理解 Set 数据结构、迭代器与生成器、异步编程的重要前置知识。建议按顺序阅读ES6 介绍 → Symbol → 字符串、数组、对象的扩展 → Set 数据结构再到 迭代器和生成器 完成进阶闭环。赞分享文档教程前端【免费下载链接】Web千古前端图文教程超详细的前端入门到进阶知识库。从零开始学前端做一名精致优雅的前端工程师。项目地址https://gitcode.com/gh_mirrors/we/Web点击查看免费下载相关推荐千古前端图文教程JavaScript 基本数据类型 Undefined 与 Null 详解千古前端图文教程JavaScript 基本数据类型 Undefined 与 Null 详解 导读 本文是「千古前端图文教程」JavaScript 基础系列中关文档教程前端千古前端图文教程ES6 箭头函数与参数默认值详解千古前端图文教程ES6 箭头函数与参数默认值详解 导读本文出自「千古前端图文教程」的 ES6 语法专题围绕箭头函数的 定义语法、简化写法、this 指向文档教程前端驾驭GLM-5.3-Flash的思维预算reasoning_effort与clear_thinking参数调优深度指南驾驭GLM 5.3 Flash的思维预算reasoning_effort与clear_thinking参数调优深度指南 GLM 5.3 Flash智谱 32文档教程前端上一篇Browser Harness 实战锦江 WeHotelbestwehotel.com酒店数据抓取完整指南下一篇深入理解Spatie Laravel-Medialibrary中的图片转换功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表