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() 方法时,迭代器对象应该返回一个包含 value 和 done 属性的对象,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 的值根据上下文和操作的需要等诸多复杂因素来决定,如:调用对象的运算符、隐式类型转换、valueOf 和 toString 方法等等。
当一个对象被用于需要原始值的上下文中,例如进行算术运算或字符串拼接时,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()创建的数据),如果这个值存在,则返回它;如果不存在,则新建一个以这个key为description的全局symbol数据,并将创建的数据返回。
如果先使用Symbol()创建了以这个key为description的数据,然后再使用该方法进行查找,则也不会被查找到,因为该方法只在全局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,则返回该 symbol 的 key 值,返回值为字符串类型;如果不存在该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
相关文章:
JavaScript 之 Symbol 数据类型
一、简介 symbol类型是ES6新引入的一种基本数据类型,该类型具有静态属性和静态方法。其中静态属性暴露了几个内建的成员对象,静态方法暴露了全局的symbol注册。 symbol类型具有以下特点:① 唯一性:每个symbol值都是唯一的…...
在Docker中运行PostgreSQL数据库
1.下载Docker 2.设置DockerHub账号 3.运行Docker并下载Image 4.启动PostgreSQL Image 5.连接到数据库运行SQL docker run --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORDmysecretpassword -d postgres 开放端口从Docker容器到主操作系统,这将允许我们…...
实现Spring Boot集成MyBatis
引言 在Java开发中,Spring Boot和MyBatis是非常常用的框架。Spring Boot是一个快速开发应用程序的框架,而MyBatis是一个持久化框架,可以方便地操作数据库。本文将介绍如何使用Idea集成Spring Boot和MyBatis,并创建一个简单的示例…...
关于算法的时间复杂度(度量算法执行时间的两种方法、渐进时间复杂度、时间复杂度的几个性质、渐进估算、常见的渐进时间复杂度排序)
目录 度量算法执行时间的两种方法 事后统计法(Post Hoc Analysis): 事前统计法(Pre Hoc Analysis): 渐进时间复杂度 时间复杂度的几个性质 渐进估算 常见的渐进时间复杂度排序 度量算法执行时间的两…...
SpringBoot项目--电脑商城【显示商品详情功能】
1.持久层[Mapper] 1规划需要执行的SQL语句 根据商品id显示商品详情的SQL语句 SELECT * FROM t_product WHERE id?2 设计接口和抽象方法 在ProductMapper接口中添加抽象方法 /*** 根据商品id查询商品详情* param id 商品id* return 匹配的商品详情,如果没有匹配…...
VLAN笔记
虚拟VLAN 什么是VLAN VLAN的作用 VLAN的优缺点 VLAN的配置方法 VLAN有哪些接口模式 access与trunk接口的区别 Hybrid接口 拓扑实验enspCiscoH3C 什么是VLAN VLAN(Virtual Local Area Network)又称虚拟局域网,是指在交换局域网的基础上&a…...
分类算法系列⑤:决策树
目录 1、认识决策树 2、决策树的概念 3、决策树分类原理 基本原理 数学公式 4、信息熵的作用 5、决策树的划分依据之一:信息增益 5.1、定义与公式 5.2、⭐手动计算案例 5.3、log值逼近 6、决策树的三种算法实现 7、API 8、⭐两个代码案例 8.1、决策树…...
前端面试(基础)
一、CSS 1.说一下CSS的盒模型。 在HTML页面中的所有元素都可以看成是一个盒子 盒子的组成:内容content、内边距padding、边框border、外边距margin 盒模型的类型: 标准盒模型 margin border padding content IE盒模型 margin content(border…...
element-ui switch开关组件二次封装,添加loading效果,点击时调用接口后改变状态
先看效果: element-ui中的switch开关无loading属性(在element-plus时加入了),而且点击时开关状态就会切换,这使得在需要调用接口后再改变开关状态变得比较麻烦。 思路:switch开关外包一层div,给…...
【GAN小白入门】Semi-Supervised GAN 理论与实战
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊🚀 文章来源:K同学的学习圈子论文原文:Semi-Supervised Learning with Generative Adversarial Networks.pdf在学习GAN的时候你有没有想过这样一个问题呢,如果我们生成的图像是带有标签的,例如数…...
Python自动化测试(1)-自动化测试及基本技术手段概述
生产力概述 在如今以google为首的互联网时代,软件的开发和生产模式都已经发生了变化, 在《参与感》一书提到:某位从微软出来的工程师很困惑,微软在google还有facebook这些公司发展的时候,为何为感觉没法有效还击&…...
小程序中如何查看会员的余额和变更记录
通过查看会员的余额和变更记录,可以帮助商家更好地管理会员资金,提供更好的服务和用户体验。下面将介绍小程序中如何查看会员的余额以及余额的变更记录。 1. 找到指定的会员卡。在管理员后台->会员管理处,找到需要查看余额和记录的会员…...
【项目经验】elementui--table表格自定义表头及bug
一.思路 首先我们肯定得循环表头,我们原生js封装的表格的实现原理就是这样。其次我们要把自己循环的label显示出来,对应的prop也要和表格数据相对应。用div标签循环都会出现错误(div里面套column),大家不要踩坑。第一…...
flink实现kafka、doris精准一次说明
前言说明:本文档只讨论数据源为kafka的情况实现kafka和doris的精准一次写入 flink的kafka连接器已经实现了自动提交偏移量到kafka,当flink中的数据写入成功后,flink会将这批次数据的offset提交到kafka,程序重启时,kafka中记录了当前groupId消费的offset位置,开始消费时将…...
【git】git commit、push之前自动执行脚本
可以使用 Git 的钩子(hooks)功能。Git 钩子是在特定事件发生时执行自定义脚本的方式。 下面是一个使用 pre-commit 钩子的例子,用于在执行 git commit 之前自动执行脚本: 进入你的 Git 仓库的根目录。进入 .git/hooks 目录&…...
基于springboot+vue的加盟店管理系统(前后端分离)
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...
Gin中的Cookie和Session的用法
Gin中的Cookie和Session的用法 文章目录 Gin中的Cookie和Session的用法介绍Cookie代码演示 Session代码展示 介绍 cookie 和 session 是 Web 开发中常用的两种技术,主要用于跟踪用户的状态信息。 Cookie func (c *Context) Cookie(name string, value string, max…...
【算法】反悔贪心
文章目录 反悔贪心力扣题目列表630. 课程表 III871. 最低加油次数LCP 30. 魔塔游戏2813. 子序列最大优雅度 洛谷题目列表P2949 [USACO09OPEN] Work Scheduling GP1209 [USACO1.3] 修理牛棚 Barn RepairP2123 皇后游戏(🚹省选/NOI− TODO) 相关…...
Hadoop的安装和使用,Windows使用shell命令简单操作HDFS
1,Hadoop简介 Hadoop是一个能够对大量数据进行分布式处理的软件框架,并且是以一种可靠、高效、可伸缩的方式进行处理的,它具有以下几个方面的特性。 高可靠性。 高效性。 高可扩展性。 高容错性。 成本低。 运行在Linux平台上。 支持多种编程…...
ubuntu上ffmpeg使用framebuffer显示video
这个主题是想验证使用fbdev(Linux framebuffer device),将video直接显示到Linux framebuffer上,在FFmpeg中对应的FFOutputFormat 就是ff_fbdev_muxer。 const FFOutputFormat ff_fbdev_muxer {.p.name "fbdev",.p.long_…...
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...
Java中HashMap底层原理深度解析:从数据结构到红黑树优化
一、HashMap概述与核心特性 HashMap作为Java集合框架中最常用的数据结构之一,是基于哈希表的Map接口非同步实现。它允许使用null键和null值(但只能有一个null键),并且不保证映射顺序的恒久不变。与Hashtable相比,Hash…...
性能优化中,多面体模型基本原理
1)多面体编译技术是一种基于多面体模型的程序分析和优化技术,它将程序 中的语句实例、访问关系、依赖关系和调度等信息映射到多维空间中的几何对 象,通过对这些几何对象进行几何操作和线性代数计算来进行程序的分析和优 化。 其中࿰…...
盲盒一番赏小程序:引领盲盒新潮流
在盲盒市场日益火爆的今天,如何才能在众多盲盒产品中脱颖而出?盲盒一番赏小程序给出了答案,它以创新的玩法和优质的服务,引领着盲盒新潮流。 一番赏小程序的最大特色在于其独特的赏品分级制度。赏品分为多个等级,从普…...
