JavaScript 之 Symbol 数据类型

2023-11-19

一、简介

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript 之 Symbol 数据类型 的相关文章

随机推荐

  • 从“玩枪”到“玩键盘”,退伍军人到程序员的华丽转型

    前言 我的生活平淡无奇 甚至可以说毫无希望 如果你了解我以前的生活 再看到我现在的境况 一定会惊讶 为什么一个人的变化会如此之大 叛逆 事情要从上学时候说起 我从小就是一个叛逆的孩子 贪玩 不爱学习 喜欢 舞枪弄棒 所以在学校的成绩一直都不
  • win10下安装vivado 2018.3之后ise14.7 的impact 无法使用

    软件版本号 操作系统win10 ise14 7 vivado 2018 3 ise14 7 在win10里面问题总结 1 ise14 7 闪退问题 比较好解决 论坛上比较多的解决方法 2 ise 14 7 windows 10 版本的ise
  • Extjs ToolBar动态更改图标

    使用setIconClass方法 Ext getCmp javaEye setIconClass javaEyeCss 其中 javaEye 是toolbar里的一个图标项 javaEyeCss 是css里定义的一个样式 形如 javaEy
  • PHP实现简易版区块链

  • 通过wireshark抓取telnet登陆密码

    笔者学校有一台设备 ip地址是 192 168 84 10 先打开wireshark捕获无线网卡 使用telnet登陆如图所示 按下回车 笔者这里输入的密码是 A603 现在回到wireshark停止抓包 并且在filter处输入如下的过滤
  • 将uboot,kernel,rootfs下载到开发板上

    1 为什么要下载 所谓下载 也称烧录 部署 1 1 什么是u boot Hi3518EV200 单板的 Bootloader 采用 U boot u boot是一种普遍用于嵌入式系统中的Bootloader Bootloader是在操作系统
  • input type=file 获取选择文件名称、路径方法及input上传按钮美化

    获取文件名 document getElementById upload files 0 name 获取文件路径 document getElementById upload value 页面效果 原生代码
  • Git使用教程总结

    第一篇 SVN VS GIT 转载 https blog csdn net daybreak1209 article details 78216798 svn 集中式 中央版本控制 n个版本 联网 局域网 同步 git 分布式 没有中央的概
  • Spring的多线程事务

    使用编程式事务手动管理多线程事务的生命周期 通过原子类 CountDown去控制多线程事务的全局提交或回滚 public class ThreadTransactionUtil 事务管理 private DataSourceTransact
  • CSS深入理解之line-height

    慕课学习 gt 前端开发 gt HTML CSS gt CSS深入理解之line height line height 一 line height的定义 行高line height 两行文字基线之间的距离 1 什么是基线 字符 x 下边缘
  • STM32中断标志:先清理后清理的区别

    先上总结 再闲聊爬坑过程 进入中断函数后 先清理中断标志 再执行其它动作 爬坑过程 各种教程中 都只解释中断的机制 使用 但对于中断标志的清理顺序 没多少官方准确的资料 今天在F429的代码里 又遇到问题 进中断后卡死跳不出来 各种排查没发
  • Gavin Wood Web3峰会最新演讲:波卡不是智能合约平台,而是平台的平台(全文)...

    在波卡上 每个平台都在用高性能 高效率和最优的方式做着自己擅长的事 而不必让它们的用户用底层平台的货币进行支付 从而将可定制性和灵活性提高了一个台阶 本文谨代表作者个人观点 不代表火星财经立场 该内容旨在传递更多市场信息 不构成任何投资建议
  • WingIDE-配色方案(个人喜好)

    依次选择 Edit gt Preferences gt User Interface gt Color Palette 然后选择自己喜欢的主题 我目前比较喜欢的是 Monokai 当然如果自己觉得不好看 可以依据自己喜好配色
  • VScode扩展商店不显示插件问题

    VScode扩展商店不显示插件问题 情况一 代理服务器异常 参考文章 https blog csdn net wodebokecsdn article details 89239769 文件 首选项 设置 应用程序 代理服务器 情况二 设备
  • rslidar-sdk安装编译以及遇到的问题和解决

    rslidar sdk的安装编译 可以参考官方提供的方法 rslidar sdk 1 1 通过Git下载 git clone https github com RoboSense LiDAR rslidar sdk git cd rslid
  • 计算机中api-ms-win-crt-runtime-l1-1-0.dll丢失怎么解决

    我们在电脑上安装软件或者游戏时 可能会遇到api ms win crt runtime l1 1 0 dll丢失 错误甚至找不到等情况 从而直接导致程序或者游戏无法启动 遇到这种问题别慌 可能有些朋友会绝对是软件安装或游戏安装失败 其实并不
  • Python制作yys彻底解放双手(代码篇)

    我看到好多人想要具体的代码 但是我希望你抱着学习的心态来做这件事情 写该脚本的意义是为了更好的学习python语言而不是进行游戏 千万不要本末倒置 文章相关的问题 1 qt界面如下 在这里只要点击开始按键就可以自动进行三张图片的对比 开始
  • 如何保持缓存和数据库中的数据一致

    背景 缓存是软件开发中一个非常有用的概念 数据库缓存更是在项目中必然会遇到的场景 而缓存一致性的保证 更是在面试中被反复问到 这里进行一下总结 针对不同的要求 选择恰到好处的一致性方案 缓存是什么 存储的速度是有区别的 缓存就是把低速存储的
  • STM32_GPIO引脚控制(库函数开发)

    目录 在学习GPIO引脚前 先介绍一些函数 库函数 stm32f10x rcc 库函数 stm32f10x gpio 这些函数怎么用呢 那如何使用 完成初始化 初始化完成后便可以进行一些GPIO的一些操作了 如 点亮共阳极LED 如 进行L
  • JavaScript 之 Symbol 数据类型

    一 简介 symbol类型是ES6新引入的一种基本数据类型 该类型具有静态属性和静态方法 其中静态属性暴露了几个内建的成员对象 静态方法暴露了全局的symbol注册 symbol类型具有以下特点 唯一性 每个symbol值都是唯一的 不可变