JavaScript 之 Symbol 数据类型

一、简介

symbol类型是ES6新引入的一种基本数据类型,该类型具有静态属性和静态方法。其中静态属性暴露了几个内建的成员对象,静态方法暴露了全局的symbol注册。

symbol类型具有以下特点:① 唯一性:每个symbol值都是唯一的;② 不可变性:symbol值是不可被修改的;③ 属性标识符:symbol类型的值可以作为对象属性的标识符key;④Symbol可以与其他数据类型进行运算,但不能被强制转换为其他数据类型。利用前两个特性,可以避免属性名的冲突和保证属性不会被意外覆盖。

symbol类型在许多库和框架中被广泛应用,常见的应用场景有创建私有属性、定义常量、定义事件名称以及实现各种标识符相关的功能。

二、创建

1、Symbol([description])

​ 通过Symbol([description])函数创建symbol类型的值,但并不会被添加到全局symbol表中,可选参数description,是字符串类型的数据,表示对当前symbol的描述,该参数只用来作为标识,并不会影响symbol的唯一性。

​ 但该函数并不是一个构造函数,因为该函数不支持new Symbol()的语法,如果使用该语法将会抛出TypeError错误。每个通过Symbol()函数获取的symbol类型的值都是唯一的、独一无二的,即使两个symbol拥有相同的description,它们也属于两个不同的值。

// 创建symbol数据
let s = Symbol();
console.log(s); // Symbol()
console.log(typeof s); // symbol
// 创建symbol数据并添加描述
let s1 = Symbol('one');
console.log(s1); // Symbol(one)
console.log(typeof s1); // symbol
// 创建symbol数据并添加相同的描述
let s2 = Symbol('one');
console.log(s2); // Symbol(one)
console.log(typeof s2); // symbol
// 判断symbol数据是否相等
console.log(s1 === s2); // false
console.log(s1 == s2); // false
// 使用new关键字创建symbol数据
let s3 = new Symbol(); // Uncaught TypeError: Symbol is not a constructor

2、Symbol.for(key)

​ 第四部分,常用方法章节中讲解。

三、常用属性

1、description(只读)

​ 该属性是一个只读属性,用于获取当前symbol值的描述字符串。

案例代码:
// 创建symbol数据 不添加描述
let s = Symbol();
// 创建symbol数据并添加描述
let s1 = Symbol('one');// 使用description输出symbol数据的描述
console.log(s.description); // undefined
console.log(s1.description); // one

2、hasInstance

​ 该属性是 Symbol 类型的一个内置静态属性,属性值是一个 Symbol 值,它是不可变的,用于定义对象的 @@hasInstance 方法的键,@@hasInstance 方法用于判断一个对象是否为某个构造函数的实例,我们可以利用该属性自定义instanceof操作符在某个类上的行为。

​ 当一个对象被使用 instanceof 运算符检查其原型链时,会调用该对象的 @@hasInstance 方法。换句话说,obj instanceof Constructor 实际上是调用了 Constructor[Symbol.hasInstance](obj)。因此我们可以通过自定义的 @@hasInstance 方法,来自定义判断对象是否为某个构造函数的实例。

案例代码:
class MyArray {static [Symbol.hasInstance](instance) {return Array.isArray(instance);}
}
console.log([] instanceof MyArray); // true
console.log({} instanceof MyArray); // fales

3、isConcatSpreadable

​ 该属性是 Symbol 类型的一个内置静态属性,属性值是一个 Symbol 值,它是不可变的,用于定义对象的 @@isConcatSpreadable 的键。@@isConcatSpreadable 方法是一个内部Symbol,用于确定对象在调用数组的 concat() 方法时是否展开其元素。

​ 如果@@isConcatSpreadable 返回true(默认值),则对象元素被展开合并;如果方法返回false,则将对象作为单个元素添加到数组中,构成多维数组的形式。我们可以通过设置对象的 @@isConcatSpreadable 来自定义对象在 concat() 方法中的展开行为

案例代码:
let arr1 = [1, 2, 3]
let arr2 = ['a', 'b', 'c']
// 默认为true 进行展开合并
console.log(arr1.concat(arr2)); // [1, 2, 3, 'a', 'b', 'c']
// 设置arr2 的值为false
arr2[Symbol.isConcatSpreadable] = false
// 再次合并 此时不会展开
console.log(arr1.concat(arr2)); // [1, 2, 3, ['a', 'b', 'c']]

4、iterator

Symbol.iterator 是 Symbol 类型的一个内置静态属性,属性值是一个 Symbol 值,用于定义对象的默认的迭代器@@iterator@@iterator 方法是一个特殊的内部方法,用于返回一个迭代器对象。

​ 当一个对象使用 for...of 循环或扩展运算符 (...)进行遍历时,会调用该对象的 @@iterator 方法来获取一个迭代器对象。迭代器对象可以通过调用其 next() 方法来依次访问对象的每个元素。我们可以通过自定义 @@iterator 方法来实现自定义的迭代器行为。自定义的 @@iterator 方法应该返回一个具有 next() 方法的迭代器对象。每次调用 next() 方法时,迭代器对象应该返回一个包含 valuedone 属性的对象,value 表示当前迭代的值,done 表示迭代是否结束。

案例代码:
let arr3 = [1, 2, 3]
// for..of..形式遍历
console.log('for..of..形式遍历');
for (const item of arr3) {console.log(item);
}
// ... 扩展运算符形式遍历
console.log('扩展运算符形式遍历');
console.log([...arr3]);
// 自定义默认迭代器
arr3[Symbol.iterator] = function () {let index = 0;return {next: function () {if (index < arr3.length) {return { value: arr3[index++] * 3, done: false };} else {return { value: undefined, done: true };}}};
};
// 输出数组本身
console.log('输出数组本身');
console.log(arr3);
// for..of..形式遍历
console.log('for..of..形式遍历');
for (const item of arr3) {console.log(item);
}
// ... 扩展运算符形式遍历
console.log('扩展运算符形式遍历');
console.log([...arr3]);
// forEach 形式遍历
console.log(' forEach 形式遍历');
arr3.forEach(item => {console.log(item);
});
// for 形式遍历
console.log('for 形式遍历');
for (let i = 0; i < arr3.length; i++) {console.log(arr3[i]);
}
执行结果:

在这里插入图片描述

5、toPrimitive

Symbol.toPrimitive 是 Symbol 类型的一个内置静态属性,用于定义对象的 @@toPrimitive 转换方法的键。 @@toPrimitive 转换方法是一个内部方法,用于将对象转换为一个原始值,它接受一个参数 hint,表示预期的转换类型。hint 参数可以是以下三个值之一:

  • "default":表示该对象可以被转换为任意类型的原始值,例如:"" + x (强制转为原始值,而不是字符串)。
  • "number":表示该对象被期望转换为数字类型的原始值,例如:算术运算(+-*/)。
  • "string":表示该对象被期望转换为字符串类型的原始值,例如模板字符串(${})、String()等。

​ 参数 hint 的值根据上下文和操作的需要等诸多复杂因素来决定,如:调用对象的运算符、隐式类型转换、valueOftoString 方法等等。

​ 当一个对象被用于需要原始值的上下文中,例如进行算术运算或字符串拼接时,JavaScript 引擎会首先查找对象的 Symbol.toPrimitive 属性。如果该属性存在并且是一个函数,引擎将调用该函数,并传入相应的 hint 参数,转换获取对象的原始值,即字符串、数字或布尔值。我们可以通过自定义 @@toPrimitive 方法来自定义对象的转换行为,完全控制原始转换过程,并返回对象的原始值。

案例代码:
// 一个没有提供 Symbol.toPrimitive 属性的对象
const obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性
const obj2 = {};
obj2[Symbol.toPrimitive] = function (hint) {// hint 参数值是 "number"if (hint === "number") {// 返回对象有多少条属性return Object.keys(obj2).length;}// hint 参数值是 "string"if (hint === "string") {// 返回对象转换成的JSON字符串return JSON.stringify(obj2);}// hint 参数值是 "default"return true;
};
console.log(+obj2); // 0  
console.log(`${obj2}`); // "{}"  
console.log(obj2 + ""); // "true"

6、match、replace、search、toStringTag等其他属性

​ 请自行了解。。。

四、常用方法

1、for()

Symbol.for(key)方法会根据参数key,从所有声明的全局symbol数据中寻找对应的值(不包括通过Symbol()创建的数据),如果这个值存在,则返回它;如果不存在,则新建一个以这个keydescription的全局symbol数据,并将创建的数据返回。

​ 如果先使用Symbol()创建了以这个keydescription的数据,然后再使用该方法进行查找,则也不会被查找到,因为该方法只在全局symbol数据中进行查找,而Symbol()创建的是非全局的数据。

// 第一次使用for()方法 由于之前不存在以foo为key的symbol 所以创建一个 symbol 并放入 symbol 注册表中,键为 "foo"
const a = Symbol.for("aaa");
// 第二次使用for()方法 由于之前注册过 所以直接从 symbol 注册表中读取键为"foo"的 symbol
const b = Symbol.for("aaa");
// 验证两者是否为同一个symbol
console.log(a === b); // true// 如果是通过Symbol()方法创建 则会创建两个key相同的symbol 但并不是同一个symbol
const c = Symbol("bbb");
const d = Symbol("bbb");
// 验证两者是否为同一个symbol
console.log(c === d); // false

2、keyFor()

Symbol.keyFor(symbol)方法会根据参数symbol,从所有声明的全局symbol数据中寻找该数据,并返回该数据的key。如果从全局symbol数据中查找到该 symbol,则返回该 symbolkey 值,返回值为字符串类型;如果不存在该symbol或者该symbol对应的key为空,则返回 undefined

​ 如果参数symbol是通过Symbol()创建的数据,则也不会被查找到,因为该方法创建symbol数据并非全局的,返回值为undefined

// 创建一个全局 Symbol 且有key
const a = Symbol.for("aaa");
console.log(Symbol.keyFor(a)); // "aaa"
// 创建一个全局 Symbol 但没有key
const b = Symbol.for();
console.log(Symbol.keyFor(b)); // undefined
// 创建一个非全局 Symbol 且有key
const c = Symbol("ccc");
// 创建一个全局的 Symbol 且有相同的key
const c2 = Symbol.for("ccc");
console.log(Symbol.keyFor(c)); // undefined
console.log(Symbol.keyFor(c2)); // "ccc"
// 验证两者是否为同一个symbol
console.log(c === c2); // false
// 下面的 原生Symbol 没有保存在全局 Symbol 注册表中
console.log(Symbol.keyFor(Symbol.iterator)); // undefined

3、toString()

Symbol对象拥有自己的toString()方法,覆盖了原型链上的Object.prototype.toString()方法。

​ symbol数据不能隐式转换为字符串,因此需要toString()方法,将数据转换成字符串。

// 创建一个symbol
const a = Symbol("aaa");
console.log(a.toString()); // Symbol(aaa)
// 创建一个全局symbol
const b = Symbol.for("bbb");
console.log(b.toString()); // Symbol(bbb)

4、valueOf()等其他方法

​ 请自行了解。。。

五、相关应用

1、作为对象唯一的属性键

Symbol可以用作对象属性的键,确保属性key的唯一性,避免属性被意外覆盖或冲突。

// 声明一个 symbol 数据
const a = Symbol('aaa');
// 声明一个全局 symbol 数据
const b = Symbol.for('bbb')
// 声明对象 利用 symbol 数据作为 key
const obj = {[a]: '11111111',a: '22222222',[b]: '33333333'
};
// 再次声明一个 symbol 数据 与 a 有相同的description
// 以该symbol作为key 修改数据 虽然声明时的 description 相同 
// 但是是两个不同的 symbol 数据 所以obj[Symbol('aaa')] 与 obj[a] 是两个不同的属性
// 修改其中一个不会影响另一个
obj[Symbol('aaa')] = '44444444';
// 输出属性数据 
console.log(obj[a]); // 11111111
// 此处相当于又声明了一个symbol
console.log(obj[Symbol('aaa')]); // undefined
// 再次输出对象的数据
console.log(obj);// 可通过 symbol 数据作为 key 修改数据
// obj[a] = '44444444';
执行结果:

在这里插入图片描述

2、声明唯一的常量

​ 借助Symbol数据的唯一性,我们可以声明常量,并确保常量值的唯一性,而且不会被意外修改或覆盖。

// 声明一个常量 且常量值为 symbol 数据
const a = Symbol("aaa");
// 在函数中匹配常量值
function myFunction(value) {// 判断传入的值是否与常量值相等if (value === a) {console.log("常量值被匹配");} else {console.log("常量值未匹配");}
}
// 传入定义的变量
myFunction(a); // 输出 "常量值被匹配"
// 传入一个新的 symbol 数据 且 description 与常量值相同
myFunction(Symbol("aaa")); // 输出 "常量值未匹配"

3、改写对象的内置方法

​ 通过利用Symbol和内置属性,我们可以改写对象的内置方法,以适应特定的业务场景,并自定义对象的行为。但在改写内置方法时不能破坏原有的语言规范和其他代码的预期行为。

// 声明一个对象 并改写其内置toString方法的返回值
const myObj = {// 该Symbol的内置属性 决定了toString方法的返回值[Symbol.toStringTag]: "MyObject",
};
// 输出对象的toString方法的返回值
console.log(Object.prototype.toString.call(myObj)); // 输出 "[object MyObject]"

4、定义类的私有成员

Symbol可以在类中作为私有成员的标识符。

// 定义一个symbol数据
const _privateMember = Symbol("private");
// 定义一个类
class MyClass {constructor() {// 定义类的私有成员 以symbol数据作为标识符this[_privateMember] = "私有成员";}// 私有成员的get方法getPrivateMember() {return this[_privateMember];}
}// new 一个实体对象
const instance = new MyClass();
// 通过get方法正常访问
console.log(instance.getPrivateMember()); // 输出 "私有成员"
// 直接访问 无法访问
console.log(instance[_privateMember]); // 输出 undefined

5、其他用法。。。

六、参考资料

Symbol

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/71601.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript设计模式(四)——策略模式、代理模式、观察者模式

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

【MySQL基础】事务隔离03

目录 隔离性与隔离级别事务隔离的实现事务的启动方式MySQL事务代码示例 在MySQL中&#xff0c;事务支持是在引擎层实现的。MySQL是一个支持多引擎的系统&#xff0c;但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务&#xff0c;这也是 MyISAM 被 Inn…

29 | 聊聊性能测试的基本方法与应用领域

并发用户数、响应时间、系统吞吐量之间的关系 当系统并发用户数较少时&#xff0c;系统的吞吐量也低&#xff0c;系统处于空闲状态&#xff0c;这个阶段被称为 “空闲区间”。 并发用户数进一步增长&#xff0c;系统的处理能力逐渐趋于饱和&#xff0c;因此每个用户的响应时间会…

Java 日志技术

所以说&#xff0c;要学Logback&#xff01; 配置文件 Logback提供了一个核心配置文件logback.xml&#xff0c;日志框架在记录日志时会读取配置文件中的配置信息&#xff0c;从而记录日志的形式。 可以配置日志输出的位置是文件还是控制台可以配置日志输出的格式还可以配置日…

55、基于 WebFlux 开发 WebSocKet

★ 基于Web Flux开发WebSocket 两步&#xff1a; &#xff08;1&#xff09;实现WebSocketHandler开发WebSocket处理类。 实现该接口时只需要实现Mono handle(WebSocketSession webSocketSession)方法即可。 &#xff08;2&#xff09;使用HandlerMapping和WebSocketHandler…

【Arduino27】DHT11温湿度传感器模拟值实验

硬件准备 DHT11温湿度&#xff1a;1个 面包板&#xff1a;1个 杜邦线&#xff1a;3根 硬件连线 VDD引脚接 5V 电源 DATE引脚接 4号 接口 GND引脚接 GND 接口 软件程序 #include<DHT.h>#define DHT11_pin 4 //温湿度传感器引脚DHT dht(DHT11_pin,DHT11);float tem…

常用echart图总结

柱状图 - category-work,grid直角坐标,legend,series-bar柱状图,tooltip提示框 - makeapie echarts社区图表可视化案例

华为OD机试 - 最多颜色的车辆 - 数据结构map(Java 2022Q4 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路1、核心思想2、题做多了&#xff0c;你就会发现&#xff0c;这道题属于送分题&#xff0c;为什么这样说&#xff1f;3、具体解题思路&#xff1a; 五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B…

C++ 模板

模板&#xff1a; 模板&#xff0c;即数据是灵魂&#xff0c;其余为肉身&#xff0c;正所谓有趣的灵魂万里挑一&#xff0c;所以想要模板变得完美&#xff0c;关键在于数据&#xff1b;其余不过是抄作业的框架。 模板函数&#xff1a; 模板函数可以自动推导出你传给他的数据类型…

便捷高效的电能管理:利用PLC远程控制网关实时监控配电箱

配电箱是工业生产过程中必不可少的设备&#xff0c;其中的PLC&#xff08;可编程逻辑控制器&#xff09;是实现自动化控制的重要组成部分。为了实时监控配电箱的数据&#xff0c;并进行远程调试上下载与管理控制&#xff0c;我们可以使用PLC数据采集与远程控制网关。 PLC数据采…

2023年全国大学生数学建模B题

多波束测线问题 1.问题提出 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播&#xff0c;在不同界面上产生反射&#xff0c;利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信号&#xff0c;并记录从声波发射到信号接…

CVE-2023-3836:大华智慧园区综合管理平台任意文件上传漏洞复现

文章目录 CVE-2023-3836&#xff1a;大华智慧园区综合管理平台任意文件上传漏洞复现0x01 前言0x02 漏洞描述0x03 影响范围0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 CVE-2023-3836&#xff1a;大华智慧园区综合管理平台任意文件上传漏洞复现 0x01 前言 免责声…

0012Java程序设计-springboot基于微信小程序的校园智慧帮系统的设计与实现

摘要目录相关技术2.1 MySQL数据库2.2 SpringBoot框架2.3 uniapp框架2.4 B/S架构 系统设计系统实现开发环境 摘要 随着移动互联网高速发展&#xff0c;手机、移动智能终端设备在生活中有着越来越重要的地位。在高校推崇以人为本的今天&#xff0c;也逐渐重视“移动互联网”技术…

Java“牵手”1688商品详情数据,1688商品详情API接口,1688API接口申请指南

1688平台商品详情接口是开放平台提供的一种API接口&#xff0c;通过调用API接口&#xff0c;开发者可以获取1688商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片等详细信息 。 获取商品详情接口API是一种用于获取电商平台上商品详情数据的接口&#xff0c;通过…

一套成熟的实验室信息管理系统(云LIS源码)ASP.NET CORE

一套成熟的实验室信息管理系统&#xff0c;集前处理、检验、报告、质控、统计分析、两癌等模块为一体的网络管理系统。它的开发和应用将加快检验科管理的统一化、网络化、标准化的进程。 LIS把检验、检疫、放免、细菌微生物及科研使用的各类分析仪器&#xff0c;通过计算机联…

CSS中如何实现文字渐变色效果(Text Gradient Color)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 文字渐变色效果&#xff08;Text Gradient Color&#xff09;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这…

2023国赛数学建模C题思路模型 - 蔬菜类商品的自动定价与补货决策

# 1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…

前端面试的话术集锦第 8 篇:高频考点(JS性能优化 性能优化琐碎事)

这是记录前端面试的话术集锦第八篇博文——高频考点(JS性能优化 & 性能优化琐碎事),我会不断更新该博文。❗❗❗ 1. 从V8中看JS性能优化 注意:该知识点属于性能优化领域。 1.1 测试性能⼯具 Chrome已经提供了⼀个⼤⽽全的性能测试⼯具Audits。 点我们点击Audits后,可…

【Git】01-Git基础

文章目录 Git基础1. 简述1.1 版本管理演变1.2 Git的特点 2. Git安装2.1 安装文档2.1 配置user信息 3. 创建仓库3.1 场景3.2 暂存区和工作区 4. 重命名5. 常用git log版本历史5.1 查看当前分支日志5.2 简洁查看日志5.3 查看最近指定条数的日志 6. 通过图形界面工具查看版本7. 探…

数据库-DQL

DQL&#xff1a;用来查询数据库表中的记录 关键字&#xff1a;SELECT 语法&#xff1a; select&#xff1a;字段列表 from&#xff1a;表名列表 where&#xff1a;条件列表 group by&#xff1a;分组列表 having&#xff1a;分组后条件列表 order by&#xff1a;排序字段列表…