JS中Symbol的介绍

2023-09-15 11:45:20

1、 引入Symbol类型的背景

ES5 的对象属性名都是字符串,这容易造成属性名冲突的问题

举例: 使用别人的模块/对象, 又想为之添加新的属性,这就容易使得新属性名与原有属性名冲突

2、Symbol类型简介

symbol是一种原始数据类型

  • 其余原始类型: 未定义(undefined) 、 空值(null)、布尔值(boolean)、字符串(string)、数值(number)、对象(object)
  • symbol表示独一无二的值
  • symbol类型的"真实值"无法获取,也就是说Symbol类型没有对应的字面量
  • symbol类型的意义在于区分彼此和不重复,不在于真实值

Symbol()是一种原生函数

  • 常见的原生函数有String()、Number()、Boolean()、Array()、Object()、Function()、RegExp()、Date()、Error()、Symbol()

3、基本用法

符号需要使用Symbol()函数初始化。

let sym = Symbol();
// 因为符号本身是原始类型,所以typeof操作符对符号返回symbol
console.log(typeof sym); // symbol

调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述,将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:

// Symbol的值是唯一的,不会出现相同值的常量
let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
console.log(genericSymbol == otherGenericSymbol); // false

// 可以传入一个字符串参数作为对符号的描述
let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');
console.log(fooSymbol == otherFooSymbol); // false

符号没有字面量语法。 按照规范,只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。

let genericSymbol = Symbol();
console.log(genericSymbol); // Symbol()

let fooSymbol = Symbol('foo');
console.log(fooSymbol); // Symbol(foo)

Symbol()函数不能与new关键字一起作为构造函数使用。
这样做是为了避免创建符号包装对象,像使用Boolean、String或Number那样,它们都支持构造函数且可用于初始化包含原始值的包装对象。

let myBoolean = new Boolean();
console.log(typeof myBoolean); // "object"

let myString = new String();
console.log(typeof myString); // "object"

let myNumber = new Number();
console.log(typeof myNumber); // "object"

let mySymbol = new Symbol(); // 报错,TypeError
console.log(mySymbol);

如果想使用符号包装对象,可以借用Object()函数:

let mySymbol = Symbol();
let myWarppedSymbol = Object(mySymbol);
console.log(typeof myWarppedSymbol); // "object"

4、 使用全局符号注册表

如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。
Symbol.for()方法:

let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol); // symbol

Symbol.for()对每个字符串键都执行幂等操作。

第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。
后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。

// 创建新符号
let fooGlobalSymbol = Symbol.for('foo');
// 重用已有符号
let otherFooGlobalSymbol = Symbol.for('foo');

console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true

采用相同符号,在全局注册表中定义的符号跟使用Symbol()定义的符号也不等同:

// 使用Symbol()定义
let localSymbol = Symbol('foo');
// 使用全局注册表定义
let globalSymbol = Symbol.for('foo');

console.log(localSymbol === globalSymbol); // false

全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串。

注册表中使用的键同时也会被用作符号描述。

let emptyGlobalSymbol = Symbol.for();
console.log(emptyGlobalSymbol); // Symbol(undefined)

使用Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。

// 创建全局符号
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // foo

// 创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2)); // undefined

如果传给Symbol.keyFor()的不是符号,则该方法抛出TypeError。

Symbol.keyFor(123); // TypeError: 123 is not a symbol

5、 使用符号作为属性

凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
包括对象字面量属性和 Object.defineProperty(obj, prop, descriptor) / Object.defineProperties() 定义的属性。
对象字面量只能在计算属性语法中使用符号作为属性。

let s1 = Symbol('foo'),
    s2 = Symbol('bar'),
    s3 = Symbol('baz'),
    s4 = Symbol('qux');

let o = {
    // [属性],会对属性进行读取,并且转换成字符串。[s1]是读取了Symbol的字符串键'foo'
    [s1]: 'foo val'
};
// 或 o[s1] = 'foo val';
console.log(o); // { [Symbol(foo)]: 'foo val' }

Object.defineProperty(o, s2, { value: 'bar val' });
console.log(o); // {Symbol(foo): foo val, Symbol(bar): bar val}

Object.defineProperties(o, {
    [s3]: { value: 'baz val' },
    [s4]: { value: 'qux val' }
});
console.log(o); // {Symbol(foo): foo val, Symbol(bar): baz val,
                //  Symbol(foo): foo val, Symbol(bar): qux val}

let s1 = Symbol('foo'),
    s2 = Symbol('bar');

let o = {
    [s1]: 'foo val',
    [s2]: 'bar val',
    baz: 'baz val',
    qux: 'qux val'
};

// Object.getOwnPropertySymbols()返回对象实例的符号属性数组
console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(foo), Symbol(bar) ]

// Object.getOwnPropertyNames()返回对象实例的常规属性数组
console.log(Object.getOwnPropertyNames(o)); // [ 'baz', 'qux' ]

// Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象
console.log(Object.getOwnPropertyDescriptors(o));
// {
//     baz: {
//       value: 'baz val',
//       writable: true,
//       enumerable: true,
//       configurable: true
//     },
//     qux: {
//       value: 'qux val',
//       writable: true,
//       enumerable: true,
//       configurable: true
//     },
//     [Symbol(foo)]: {
//       value: 'foo val',
//       writable: true,
//       enumerable: true,
//       configurable: true
//     },
//     [Symbol(bar)]: {
//       value: 'bar val',
//       writable: true,
//       enumerable: true,
//       configurable: true
//     }
//   }

// Reflect.ownKeys()会返回两种类型的键
console.log(Reflect.ownKeys(o)); // [ 'baz', 'qux', Symbol(foo), Symbol(bar) ]

注意:Object.getOwnPropertyNames()和Object.getOwnProperty-Symbols()两个方法的返回值彼此互斥。

因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。

但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键。

let o = {
    [Symbol('foo')]: 'foo val',
    [Symbol('bar')]: 'bar val'
};
console.log(o); // { [Symbol(foo)]: 'foo val', [Symbol(bar)]: 'bar val' }

let barSymbol = Object.getOwnPropertySymbols(o).find((Symbol) => 
				Symbol.toString().match(/bar/));
console.log(barSymbol); // Symbol(bar)

6、 所有属性

属性含义
Symbol.asyncIterator符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await…of循环。
Symbol.prototype.descriptiondescription 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。
Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。
Symbol.isConcatSpreadable用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。
Symbol.iterator为每一个对象定义了默认的迭代器。该迭代器可以被 for…of 循环使用。
Symbol.match指定了匹配的是正则表达式而不是字符串。String.prototype.match() 方法会调用此函数
Symbol.matchAll内置通用(well-known)符号指定方法返回一个迭代器,该迭代器根据字符串生成正则表达式的匹配项。此函数可以被 String.prototype.matchAll() 方法调用。
Symbol.replace这个属性指定了当一个字符串替换所匹配字符串时所调用的方法。
Symbol.search指定了一个搜索方法,这个方法接受用户输入的正则表达式,返回该正则表达式在字符串中匹配到的下标,这个方法由以下的方法来调用 String.prototype.search()。
Symbol.species知名的 Symbol.species 是个函数值属性,其被构造函数用以创建派生对象。
Symbol.split指向 一个正则表达式的索引处分割字符串的方法。这个方法通过 String.prototype.split() 调用。
Symbol.toPrimitive是内置的 symbol 属性,其指定了一种接受首选类型并返回对象原始值的表示的方法。它被所有的强类型转换制算法优先调用。
Symbol.toStringTag内置通用(well-known)symbol 是一个字符串值属性,用于创建对象的默认字符串描述。它由 Object.prototype.toString() 方法内部访问。
Symbol.unscopables指用于指定对象值,其对象自身和继承的从关联对象的 with 环境绑定中排除的属性名称。
更多推荐

通过 DevOps、CI/CD 和容器增强您的软件开发之旅...

软件行业已经在DevOps、CI/CD和容器中找到了针对开发导向问题的有效解决方案。尽管并不强制要求将这三者一起使用,但它们通常是相互补充和依赖的。DevOps促进开发和IT团队之间的协作,而CI/CD简化软件交付流程以更快地获得结果。容器化将应用程序与其依赖项结合起来,以建立一致的开发和部署环境。实施这些方法可以优化

nginx反向代理 负载均衡

1.反向代理介绍:反向代理:reverseproxy,指的是代理外网用户的请求到内部的指定的服务器,并将数据返回给用户的一种方式,这是用的比较多的一种方式。Nginx除了可以在企业提供高性能的web服务之外,另外还可以将nginx本身不具备的请求通过某种预定义的协议转发至其它服务器处理,不同的协议就是Nginx服务器与

基于Hadoop的网上购物行为分析设计与实现

有需要本项目的可以私信博主,提供部署和讲解服务!!!!!本研究基于淘宝用户行为的开源数据展开大数据分析研究,通过Hadoop大数据分析平台对阿里天池公开的开源数据集进行多维度的用户行为分析,为电商销售提供可行性决策。本次研究选取了2021年12月1日-18号的数据,其中每一行数据集包含用户的每一次的行为。首先我们将数据

HarmonyOS开发:解决DevEco Studio低版本导入高版本项目运行失败问题

前言基于DevEcoStudio4.0Beta2,hvigorVersion为3.0.2,开发了一个项目,上传到了远程仓库,当同事下载后,却始终无法运行,频繁报错,由于API都是使用的9,第一感觉就是开发环境不同,于是,让其发来了他的开发环境,DevEcoStudio3.1.1Release,hvigorVersion

java版工程管理系统Spring Cloud+Spring Boot+Mybatis实现工程管理系统源码

工程项目管理软件(工程项目管理系统)对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营,全过程、全方位的对项目进行综合管理工程项目各模块及其功能点清单一、系统管理1、数据字典:实现对数据字典标签的增删改查操作2、编码管理:实现对系统编码的增删改查操作3、用户管理:管理和查看用户角

MYSQL04高级_逻辑架构剖析、查询缓存、解析器、优化器、执行器、存储引擎

文章目录①.逻辑架构剖析②.服务层-查询缓存③.服务层-解析器④.服务层-优化器⑤.服务层-执行器⑥.MySQL8执行原理①.逻辑架构剖析①.服务器处理客户端请求②.连接层系统(客户端)访问MySQL服务器前,做的第一件事就是建立TCP连接经过三次握手建立连接成功后,MySQL服务器对TCP传输过来的账号密码做身份认证

Windows/Linux(命令、安装包和源码安装)平台各个版本QT详细安装教程

前言本文章主要介绍了Windows/Linux平台下,QT4,QT5,QT6的安装步骤。为什么要把QT版本分开介绍呢,因为这三个版本,安装步骤都不一样。Windows平台,QT4的QtCreator,QT库和编译器是分开的,需要分别单独下载安装。QT5将QtCreator,QT库和编译器都集成到了一起,只需要下载安装包

如何在 Excel 中计算日期之间的天数

计算两个日期之间的天数是Excel中的常见操作。无论您是规划项目时间表、跟踪时间还是分析一段时间内的趋势,了解如何在Excel中查找日期之间的天数都可以提供强大的日期计算功能。幸运的是,Excel提供了多种简单的方法来获取两个日期之间的天数。继续阅读以了解在Excel中计算日期差异的不同公式和函数。为什么在Excel中

Java实现添加文字水印、图片水印功能实战

java实现给图片添加水印实现步骤:获取原图片对象信息(本地图片或网络图片)添加水印(设置水印颜色、字体、坐标等)处理输出目标图片java实现给图片添加文字水印获取原图片对象信息第一步:获取需要处理的图片获取图片的方式,通常由两种:一种是通过下载到本地,从本地读取(本地图片);另外一种是通过网络地址进行读取(网络图片)

Ribbon负载均衡

文章目录1.Ribbon2.负载均衡原理1.服务端负载均衡2.客户端负载均衡3.负载均衡流程4.负载均衡策略5.自定义负载均衡策略1.代码方式2.配置文件方式6.饥饿加载1.RibbonSpringCloudRibbon是一套基于NetflixRibbon实现的客户端负载均衡和服务调用工具。Ribbon是一个基于HTT

SpringBoot的学习要点

黑马程序员SpringBoot2全套视频教程,springboot零基础到项目实战(springboot2完整版)_哔哩哔哩_bilibili博客阅读:Microservices中文文档:SpringBoot中文文档spring官网:https://start.spring.io/thymeleaf官方文档阅读(英文版

热文推荐