4、Symbol-ES6新基础类型

news2024/9/25 3:26:13

  symbol是 ES6 新增的一种基本数据类型,它和 number、string、boolean、undefined 和 null 是同类型的,object 是引用类型。它用来表示独一无二的值,通过 Symbol 函数生成。

本小节代码都是纯JavaScript代码,建议在非TypeScript环境练习,你可以在浏览器开发者工具的控制台里练习。但是因为TypeScript也支持Symbol,所以如果需要特别说明的地方,我们会提示在TypeScript中需要注意的内容。推荐使用在线编辑器:gjk.cn/editor

1、基础示例

const s = Symbol();
typeof s; // 'symbol'

        我们使用Symbol函数生成了一个 symbol 类型的值 s。

注意:Symbol 前面不能加new关键字,直接调用即可创建一个独一无二的 symbol 类型的值。

        我们可以在使用 Symbol 方法创建 symbol 类型值的时候传入一个参数,这个参数需要是字符串的。如果传入的参数不是字符串,会先调用传入参数的 toString 方法转为字符串。先来看例子:

const s1 = Symbol("lison");
const s2 = Symbol("lison");
console.log(s1 === s2); // false
// 补充:这里第三行代码可能会报一个错误:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap.
// 这是因为编译器检测到这里的s1 === s2始终是false,所以编译器提醒你这代码写的多余,建议你优化。

        上面这个例子中使用 Symbol 方法创建了两个 symbol 值,方法中都传入了相同的字符串’lison’,但是s1 === s2却是 false,这就是我们说的,Symbol 方法会返回一个独一无二的值,这个值和任何一个值都不等,虽然我们传入的标识字符串都是"lison",但是确实两个不同的值。

        你可以理解为我们每一个人都是独一无二的,虽然可以有相同的名字,但是名字只是用来方便我们区分的,名字相同但是人还是不同的。Symbol 方法传入的这个字符串,就是方便我们在控制台或程序中用来区分 symbol 值的。我们可以调用 symbol 值的toString方法将它转为字符串:

const s1 = Symbol("lison");
console.log(s1.toString()); // 'Symbol(lison)'

        你可以简单地理解 symbol 值为字符串类型的值,但是它和字符串有很大的区别,它不可以和其他类型的值进行运算,但是可以转为字符串和布尔类型值:

let s = Symbol("lison");
console.log(s.toString()); // 'Symbol(lison)'
console.log(Boolean(s)); // true
console.log(!s); // false

2、作为属性名使用

        在 ES6 中,对象的属性名支持表达式,所以你可以使用一个变量作为属性名,这对于一些代码的简化很有用处,但是表达式必须放到方括号内:

let prop = "name";
const obj = {
  [prop]: "Lison"
};
console.log(obj.name); // 'Lison'

        了解了这个新特性后,我们接着学习。symbol 值可以作为属性名,因为 symbol 值是独一无二的,所以当它作为属性名时,不会和其他任何属性名重复:

let name = Symbol();
let name2 = Symbol();
let obj = {
  [name]: "lison",
  [name2]: "lison2"
};
console.log(obj); // { Symbol(): 'lison' }

        你可以看到,打印出来的对象有一个属性名是 symbol 值。如果我们想访问这个属性值,就只能使用 name 这个 symbol 值:

console.log(obj[name]); // 'lison'
console.log(obj[name2]); // 'lison2'
console.log(obj.name); // undefined

        通过上面的例子可以看到,我们访问属性名为 symbol 类型值的 name 时,我们不能使用点’.‘号访问,因为obj.name这的name实际上是字符串’name’,这和访问普通字符串类型的属性名一样。你必须使用方括号的形式,这样obj[name]这的 name 才是我们定义的 symbol 类型的变量name,之后我们再访问 obj 的[name]属性就必须使用变量 name。

3、属性名的遍历

        使用 Symbol 类型值作为属性名,这个属性不会被for…in遍历到,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()获取到:

const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
for (const key in obj) {
  console.log(key);
}
// => 'age'
console.log(Object.keys(obj));
// ['age']
console.log(Object.getOwnPropertyNames(obj));
// ['age']
console.log(JSON.stringify(obj));
// '{ "age": 18 }'

        虽然这么多方法都无法遍历和访问到 Symbol 类型的属性名,但是 Symbol 类型的属性并不是私有属性。我们可以使用Object.getOwnPropertySymbols方法获取对象的所有symbol类型的属性名:

const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
const SymbolPropNames = Object.getOwnPropertySymbols(obj);
console.log(SymbolPropNames);
// [ Symbol(name) ]
console.log(obj[SymbolPropNames[0]]);
// 'lison'
// 如果最后一行代码这里报错提示:元素隐式具有 "any" 类型,因为类型“{ [name]: string; age: number; }”没有索引签名。 那可能是在tsconfig.json里开启了noImplicitAny。因为这里我们还没有学习接口等高级类型,所以你可以先忽略这个错误,或者关闭noImplicitAny。

        除了Object.getOwnPropertySymbols这个方法,还可以用 ES6 新提供的 Reflect 对象的静态方法Reflect.ownKeys,它可以返回所有类型的属性名,所以 Symbol 类型的也会返回。

const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
console.log(Reflect.ownKeys(obj));
// [ 'age', Symbol(name) ]

4、Symbol.for()和 Symbol.keyFor()

Symbol 包含两个静态方法,for 和 keyFor

4.1、Symbol.For()

        我们使用 Symbol 方法创建的 symbol 值是独一无二的,每一个值都不和其他任何值相等,我们来看下例子:

const s1 = Symbol("lison");
const s2 = Symbol("lison");
const s3 = Symbol.for("lison");
const s4 = Symbol.for("lison");
s3 === s4; // true
s1 === s3; // false
// 这里还是会报错误:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap.还是我们说过的,因为这里的表达式始终是true和false,所以编译器会提示我们。

        直接使用 Symbol 方法,即便传入的字符串是一样的,创建的 symbol 值也是互不相等的。而使用 Symbol.for方法传入字符串,会先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值,如果有,返回该值,如果没有,则使用该字符串新创建一个。使用该方法创建 symbol 值后会在全局范围进行注册。

注意:这个注册的范围包括当前页面和页面中包含的 iframe,以及 service sorker,我们来看个例子:

const iframe = document.createElement("iframe");
iframe.src = String(window.location);
document.body.appendChild(iframe);

iframe.contentWindow.Symbol.for("lison") === Symbol.for("lison"); // true
// 注意:如果你在JavaScript环境中这段代码是没有问题的,但是如果在TypeScript开发环境中,可能会报错:类型“Window”上不存在属性“Symbol”。
// 因为这里编译器推断出iframe.contentWindow是Window类型,但是TypeScript的声明文件中,对Window的定义缺少Symbol这个字段,所以会报错,所以你可以这样写:
// (iframe.contentWindow as Window & { Symbol: SymbolConstructor }).Symbol.for("lison") === Symbol.for("lison")
// 这里用到了类型断言和交叉类型,SymbolConstructor是内置的类型。

        上面这段代码的意思是创建一个 iframe 节点并把它放到 body 中,我们通过这个iframe 对象的 contentWindow 拿到这个 iframe 的 window 对象,在iframe.contentWindow上添加一个值就相当于你在当前页面定义一个全局变量一样,我们看到,在iframe 中定义的键为’lison’的 symbol 值在和在当前页面定义的键为’lison’的 symbol 值相等,说明它们是同一个值。

4.2、Symbol.keyFor()

该方法传入一个 symbol 值,返回该值在全局注册的键名:

const sym = Symbol.for("lison");
console.log(Symbol.keyFor(sym)); // 'lison'

5、11个内置的 symbol 值

5.1、Symbol.hasInstance

        对象的 Symbol.hasInstance 指向一个内部方法,当你给一个对象设置以 Symbol.hasInstance 为属性名的方法后,当其他对象使用 instanceof 判断是否为这个对象的实例时,会调用你定义的这个方法,参数是其他的这个对象,来看例子:

const obj = {
  [Symbol.hasInstance](otherObj) {
    console.log(otherObj);
  }
};
console.log({ a: "a" } instanceof obj); // false
// 注意:在TypeScript中这会报错,"instanceof" 表达式的右侧必须属于类型 "any",或属于可分配给 "Function" 接口类型的类型。
// 是要求你instanceof操作符右侧的值只能是构造函数或者类,或者类型是any类型。这里你可以使用类型断言,将obj改为obj as any

        可以看到当我们使用 instanceof 判断{ a: ‘a’ }是否是 obj 创建的实例的时候,Symbol.hasInstance 这个方法被调用了。

5.2、Symbol.isConcatSpreadable

        这个值是一个可读写布尔值,当一个数组的 Symbol.isConcatSpreadable 设为 true 时,这个数组在数组的 concat 方法中不会被扁平化。我们来看下例子:

let arr = [1, 2];
console.log([].concat(arr, [3, 4])); // 打印结果为[1, 2, 3, 4],length为4
let arr1 = ["a", "b"];
console.log(arr1[Symbol.isConcatSpreadable]); // undefined
arr1[Symbol.isConcatSpreadable] = false;
console.log(arr1[Symbol.isConcatSpreadable]); // false
console.log([].concat(arr1, [3, 4])); // 打印结果如下:
/*
 [ ["a", "b", Symbol(Symbol.isConcatSpreadable): false], 3, 4 ]
 最外层这个数组有三个元素,第一个是一个数组,因为我们设置了arr1[Symbol.isConcatSpreadable] = false
 所以第一个这个数组没有被扁平化,第一个元素这个数组看似是有三个元素,但你在控制台可以看到这个数组的length为2
 Symbol(Symbol.isConcatSpreadable): false不是他的元素,而是他的属性,我们知道数组也是对象,所以我们可以给数组设置属性
 你可以试试如下代码,然后看下打印出来的效果:
  let arr = [1, 2]
  arr.props = 'value'
  console.log(arr)
 */

5.3、Symbol.species

       这个知识你需要在纯JavaScript的开发环境中才能看出效果,你可以在浏览器开发者工具的控制台尝试。在TypeScript中,下面两个例子都是一样的会报a.getName is not a function错误。

        首先我们使用 class 定义一个类 C,使用 extends 继承原生构造函数 Array,那么类 C 创建的实例就能继承所有 Array 原型对象上的方法,比如 map、filter 等。我们先来看代码:

class C extends Array {
  getName() {
    return "lison";
  }
}
const c = new C(1, 2, 3);
const a = c.map(item => item + 1);
console.log(a); // [2, 3, 4]
console.log(a instanceof C); // true
console.log(a instanceof Array); // true
console.log(a.getName()); // "lison"

        这个例子中,a 是由 c 通过 map 方法衍生出来的,我们也看到了,a 既是 C 的实例,也是 Array 的实例。但是如果我们想只让衍生的数组是 Array 的实例,就需要用 Symbol.species,我们来看下怎么使用:

class C extends Array {
  static get [Symbol.species]() {
    return Array;
  }
  getName() {
    return "lison";
  }
}
const c = new C(1, 2, 3);
const a = c.map(item => item + 1);
console.log(a); // [2, 3, 4]
console.log(a instanceof C); // false
console.log(a instanceof Array); // true
console.log(a.getName()); // error a.getName is not a function

        就是给类 C 定义一个静态 get 存取器方法,方法名为 Symbol.species,然后在这个方法中返回要构造衍生数组的构造函数。所以最后我们看到,a instanceof C为 false,也就是 a 不再是 C 的实例,也无法调用继承自 C 的方法。

5.4、Symbol.match、Symbol.replace、Symbol.search 和 Symbol.split

        这个 Symbol.match 值指向一个内部方法,当在字符串 str 上调用 match 方法时,会调用这个方法,来看下例子:

let obj = {
  [Symbol.match](string) {
    return string.length;
  }
};
console.log("abcde".match(obj)); // 5

        相同的还有 Symbol.replace、Symbol.search 和 Symbol.split,使用方法和 Symbol.match 是一样的。

5.5、Symbol.iterator

        数组的 Symbol.iterator 属性指向该数组的默认遍历器方法:

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

        这个 Symbol.iterator 方法是可写的,我们可以自定义遍历器方法。

5.6、Symbol.toPrimitive

        对象的这个属性指向一个方法,当这个对象被转为原始类型值时会调用这个方法,这个方法有一个参数,是这个对象被转为的类型,我们来看下:

let obj = {
  [Symbol.toPrimitive](type) {
    console.log(type);
  }
};
// const b = obj++ // number
const a = `abc${obj}`; // string

5.7、Symbol.toStringTag

        Symbol.toStringTag 和 Symbol.toPrimitive 相似,对象的这个属性的值可以是一个字符串,也可以是一个存取器 get 方法,当在对象上调用 toString 方法时调用这个方法,返回值将作为"[object xxx]"中 xxx 这个值:

let obj = {
  [Symbol.toStringTag]: "lison"
};
obj.toString(); // "[object lison]"
let obj2 = {
  get [Symbol.toStringTag]() {
    return "haha";
  }
};
obj2.toString(); // "[object haha]"

5.8、Symbol.unscopables

这个值和 with 命令有关,我们先来看下 with 怎么使用:

const obj = {
  a: "a",
  b: "b"
};
with (obj) {
  console.log(a); // "a"
  console.log(b); // "b"
}
// 如果是在TypeScript开发环境中,这段代码可能with会报错:不支持 "with" 语句,这是因为在严格模式下,是不允许使用with的。

        可以看到,使用 with 传入一个对象后,在代码块中访问对象的属性就不需要写对象了,直接就可以用它的属性。对象的 Symbol.unscopables 属性指向一个对象,该对象包含了当使用 with 关键字时,哪些属性被 with 环境过滤掉:

console.log(Array.prototype[Symbol.unscopables]);
/*
{
    copyWithin: true
    entries: true
    fill: true
    find: true
    findIndex: true
    includes: true
    keys: true
    values: true
}
*/

6、在TypeScript中使用symbol类型

6.1、基础

        学习完ES6标准中Symbol的所有内容后,我们来看下在TypeScript中使用symbol类型值,很简单。就是制定一个值的类型为symbol类型:

let sym: symbol = Symbol()

6.2、unique symbol

        TypeScript在2.7版本对Symbol做了补充,增加了unique symbol这种类型,他是symbols的子类型,这种类型的值只能由Symbol()或Symbol.for()创建,或者通过指定类型来指定一个值是这种类型。这种类型的值仅可用于常量的定义和用于属性名。另外还有一点要注意,定义unique symbol类型的值,必须用const不能用let。我们来看个在TypeScript中使用Symbol值作为属性名的例子:

const key1: unique symbol = Symbol()
let key2: symbol = Symbol()
const obj = {
    [key1]: 'value1',
    [key2]: 'value2'
}
console.log(obj[key1])
console.log(obj[key2]) // error 类型“symbol”不能作为索引类型使用。

 

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

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

相关文章

Rasa实现百度UNIT智能客服教学机器人

背景 上一篇文章提到了百度UNIT智能客服教学机器人,下面用Rasa实现同样的效果。环境如下 Rasa Version : 3.1.0 Minimum Compatible Version: 3.0.0 Rasa SDK Version : 3.1.1 Rasa X Version : 1.1.0 Python Version : …

【LeetCode: 354. 俄罗斯套娃信封问题 | 暴力递归=>记忆化搜索=>动态规划+二分】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

2023年4月份上新的目标检测系列论文(附下载链接)

来源:投稿 作者:王老师 编辑:学姐 目标检测-预训练相关 论文标题:DetCLIPv2: Scalable Open-Vocabulary Object Detection Pre-training via Word-Region Alignment 论文链接: https://arxiv.org/abs/2304.04514代码链…

Auto-GPT免费尝鲜之初体验-使用攻略和总结

Auto-GPT免费尝鲜之初体验-使用攻略和总结 写在前面的废话一、部署 Auto-GPT二、试运行 Auto-GPT三、我踩过的坑四、后续探索 写在前面的废话 ChatGPT 的交互模式,是和一个 “人” 对话聊天。 如果你想了解更多ChatGPT和AI绘画的相关知识,请参考&#…

【代码调试】《Frustratingly Simple Few-Shot Object Detection》

更多问题可参考: https://blog.csdn.net/qiankendeNMY/article/details/128450196 论文地址:https://arxiv.org/abs/2003.06957 论文代码:https://github.com/ucbdrive/few-shot-object-detection 我的配置: Python &#xff1a…

从零开始,详解亚马逊店铺注册流程及技巧指南

近几年跨境电商的势头越来越猛,所以很多新手都想去闯荡一番。很多人的第一选择都是亚马逊,毕竟亚马逊是世界上最大的电商平台之一,因此今天东哥就跟大家分享亚马逊店铺的注册方法,想在亚马逊开店的朋友不要错过! 亚马逊…

设计模式 -- 工厂方法模式以及抽象工厂模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

Mysql MVCC实现

文章目录 背景MVCC定义快照读和当前读当前读快照读 MVCC实现原理隐式字段undo log版本链1.插入一条记录2.修改记录3.修改记录 Read View读视图属性:Read View可见性算法 隔离级别长事务为什么要避免长事务 背景 并发事务可能产生的问题: 读读&#xff…

zk111111111111111111

Zookeeper 1 zookeeper(作为 dubbo 的注册中心): 概述: zookeper 是 一个分布式的、开源的分布式应用程序的协调服务,管理分布式应 用 作用: 配置管理,分布式锁,集群管理 2 zookeeper 的安装 (dubbo 的资料中已经整理) 3 zookeeper 的数据模型 zookeeper 是一个树形的服…

微信小程序php+vue 校园租房指南房屋租赁系统

本着诚信的原则,平台必须要掌握出租方必要的真实可信的信息,这样就可以防止欺诈事件的发生,事后也可以联系找到出租方。并且租金等各方面规范标准化,在这易租房诚信可信的平台让承租方与出租方充分有效对接,既方便了承…

扫清盲点:带你学习 树状数组 这种数据结构

什么是树状数组 树状数组是一种用于维护数列前缀和的数据结构,它可以在 O(logn) 的时间复杂度内修改单个元素的值,以及查询某个区间的元素和。 树状数组的特点是什么? 树状数组的特点其实就是,在单点修改 ,和区间查询…

rancher2.7丢失集群信息

使用Docker 单节点安装rancher,然后在rancher中创建了一个k8s的集群。重启rancher所在的虚拟机后,登录rancher发现这是新的实例,集群信息丢失了。但是k8s集群还是好好的。 检查k8s的日志,api server日志会报错 time"2023-0…

11 - 多平台适配

多平台适配 11-1:开篇 在上一章中,我们知道了,当【慕课热搜】运行到 h5 端的时候,那么会出现一些问题,这些问题具体有: hot 列表滚动,tabs 置顶效果消失在火狐浏览器中,横线出现非…

kafka-kafka集群配置、kafka集群启动创建kafka主题、获取主题数据

本文章使用三台主机,分别为:master、slave1、slave2 一、解压kafka安装包至目录下 tar -zxvf kafka_2.12-2.4.1.tgz -C /需要放置的路径/ 二、修改配置文件 server.properties 该配置在kafka目录的config目录下 #修改文件中id数值 broker.id0 kafka集群…

Linux -- Web服务器-Apache 快速安装及主配置文件分析

目录 快速安装 Apache : 预处理 : 关闭安全上下文检测 : 关闭防火墙 : 启动 Apache 服务 ( 启动 httpd ): 测试 : 主配置文件分析 : 常见配置文件所在位置 : 目录文件结构 :…

购物车--订单模块,练习完成

目标: 在购物车页面,增加一个创建订单的超链接。通过创建订单,在Order表里新增一条数据,同时把session中的订单条目都保存到数据库中。 1、创建两个表,orders用来具体存储每一个订单的细节,order_用来存储…

基于MobileNetV2的Transfer Learning模型,实现物体检测(附源码)

文章目录 一、MobileNet1. 深度可分离卷积(Depthwise separable convolution)2. MobileNet V13. MobileNet V2 二、物体检测源码(基于MobileNetV2) 一、MobileNet 1. 深度可分离卷积(Depthwise separable convolution…

智慧园区综合管理平台开发基本功能有哪些?

随着智慧城市建设与信息化发展,园区管理也需要更加智能便捷化,于是智慧园区管理系统开发应运而生。智慧园区综合管理系统就是利用物联网、大数据等技术工具,顺应产业园区升级发展需求,实现园区环境、设备、安全、基础管理、资源服…

【Linux】进程间通信——命名管道

文章目录 命名管道1. 见一见管道文件mkfifo函数管道文件的使用 2. 命名管道原理如何保证两个毫不相关的进程,看到的是同一个文件,并打开? 3. 用命名管道实现server&client通信如何使用makefile连续生成可执行程序comm.hpp文件server.cc 服…

如何通过 Baklib 平台实现知识共享(内含案例介绍)

在这个信息时代,知识被认为是最重要的资源之一。企业要想保持发展,就必须善于利用和管理知识。而知识管理则是一种涵盖人、过程和技术的活动,它通过收集、整理、传递和应用知识,使组织获得更高的效率、创造力和竞争力。本文将以知…