😁 作者简介:一名大三的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏:JavaScript进阶指南
👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气
🔥前言:
这里是关于Symbol数据类型的使用,但是关于其一些原型方法会在下篇介绍,大家可以先理解一下这篇的使用细节,这是我自己的学习JavaScript的笔记,希望可以帮助到大家,欢迎大家的补充和纠正
文章目录
第10章 Symbol
10.1 概述
ES6引入了一种新的原始数据类型 Symbol 其表示独一无二的值
引入这个数据类型的原因是:
ES5的对象属性都是字符串,这很容易造成属性名冲突,比如我们使用它们提供的对象,但是又想为这个对象添加新方法,新的方法名就有可能与现有方法产生冲突。如果有一种机制,能够保证每个属性的名字都独一无二就好了,这样就能从根本上防止属性的冲突
Symbol值通过Symbol函数生成的,现在对象的属性名可以有两种类型:
- 原来的字符串
- Symbol类型
let s=Symbol()
typeof s
//symbol
❗️ 注意:
Symbol函数前不能使用new命令,否则会报错,这时因为生成Symbol是一个原始类型的值,不是对象
Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了控制台可以显示,或者转为字符串是比较容易区分
var s1=Symbol('foo')
var s2=Symbol('bar')
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() //"Symbol(foo)"
s2.toString() //"Symbol(bar)"
❗️ 注意:
Symbol函数的参数只表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的。
var s1=Symbol()
var s2=Symbol()
s1===s2 //false
var s3=Symbol('foo')
var s4=Symbol('foo')
s3===s4 //fasle
使用细节:
- Symbol值不能与其他类型进行运算,否则会报错
- Symbol值可以显示转为字符串
- Symbol值也可以转为布尔值,但是不能转为数值
10.2 转为属性名的Symbol
由于每一个Symbol值都不相等,这意味着Symbol值可以作为标识符用于对象的属性名,保证不会出现同名的属性,这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改成或覆盖
var mySymbol=Symbol()
//第一种写法
var a={}
a[mySymbol] ='Hello'
//第二种写法
var a={
[mySymbol]:'Hello'
}
//第三种写法
var a={}
Object.defineProperty(a,mySymbol,{value:'Hello'})
//以上的写法都得到同样结果
a[mySymbol] // 'Hello'
❗️ 注意
Symbol值作为对象属性名时不能使用点运算符
var mySymbol=Symbol()
var a={}
a.mySymbol='Hello!'
a[mySymbol] //undefined
a['mySymbol']//'Hello!'
上面的代码是因为使用点运算符,后面跟的总是字符串,不会取读取mySymbol作为标识名所指代的值,导致a的属性名实际上是一个字符串,而不是一个Symbol值
用法:
-
Symol类型还可用于定义一组常量,保证这组常量的值都不相等
const levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn') }; const log = (level, message) => { // 检查传入的日志级别是否有效 if (!Object.values(levels).includes(level)) { console.error('无效的日志级别'); return; } // 根据日志级别输出消息 switch (level) { case levels.DEBUG: console.debug(`[DEBUG] ${message}`); break; case levels.INFO: console.info(`[INFO] ${message}`); break; case levels.WARN: console.warn(`[WARN] ${message}`); break; default: console.error('未知的日志级别'); } }; // 使用示例 log(levels.DEBUG, '这是一个调试消息'); log(levels.INFO, '这是一个信息消息'); log(levels.WARN, '这是一个警告消息');
Symbol值作为属性名时,该属性还是公开属性,不是私有属性
10.3 实例:消除魔术字符串
魔术字符串指的是,在代码之后多次出现,与代码形成强耦合的某一个具体的字符串或数值 风格良好的代码,应该尽量消除魔术字符串,而由含义清晰的变量代替
function getArea(shape,options){
var area=0
switch(shape){
case 'Triangle':
area=5*options.width*options.height;
break;
/*...more code ... */
}
return area;
}
getArea('Triangle',{width:100,height:100})
上面的代码中,字符串 ‘Triangle’ 就是一个魔术字符串,它多次出现,与代码形成“强偶合” 不利于将来的修改和维护
常用的消除魔术字符串的方法,就是把它写成一个变量。
var shapeType={
triangle:'Triangle'
}
function getArea(shape,options){
var area=0
switch(shape){
case shapeType.triangle:
area=5*options.width*options.height;
break;
/*...more code ... */
}
return area;
}
getArea('Triangle',{ width:100 , height:100 })
上面的代码中,我们把’Triangle’ 写成 shapeType对象的triangle属性,这样就消除了强耦合
如果仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会和其他shapType属性的值冲突即可,因此,这里就很适合改用Symbol值
const shapeType={
triangle:Symbol()
}
上面的代码中,除了将shapeType.triangle 的值设为一个Symbol,其他地方都不用修改
10.4 属性名遍历
1. 我们可以使用,Object.getOWnPropertySymbols方法可以获取指定对象的所有Symbol属性名。
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。
var obj={}
var a=Symbol('a')
var b=Symbol('b')
obj[a]='Hello';
obj[b]='World';
var objectSymbols=Object.getOwnPropertySymbol(obj)
objectSymbols // [Symbol(a),Symbol(b)]
2. Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名
let obj={
[Symbol('my_key')]:1,
enum:2,
nonEnum:3
}
Reflect.ownKeys(obj)
//['enum','nonEnum','Symbol(my_key)']
⭐️ 运用:
以Symbol值作为名称的属性不会被常规方法遍历得到,我们可以利用这个特性为对象定义一些非私有但又希望只用于内部的方法
var size=Symbol('size')
class Collection{
constructor(){
this[size]=0;
}
add(item){
this[this[size]]=item;
this[size]++;
}
static sizeOf(instance){
return instance[size]
}
}
var x=new Collection()
console.log(Collection.sizeOf(x)) //0
x.add('foo')
console.log(Collection.sizeOf(x)) //1
console.log(Object.keys(x)) //['0']
console.log(Object.getOwnPropertyNames(x)) //['0']
console.log(Object.getOwnPropertySymbols(x)) //[Symbol(size)]
10.5 Symbol.for(),Symbol.keyFor()
10.5.1 Symbol.for()
有时,我们希望重新使用同一个Symbol值,Symbol.for可以做到这一点,它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值,会有以下两种处理
- 有的话,就返回这个Symbol值
- 没有的话,就新建并返回一个以该字符为名称的Symbol值
var s1=Symbol.for('foo')
var s2=Symbol.for('foo')
s1 === s2 //true
以上的代码中,第一步是查询发现没有,于是就创建出Symbol值,而第二步就是查询到了,于是就返回同一个Symbol值
⭐️ Symbol.for() 和 Symbol() 在生成新的Symbol的区别
- Symbol.for()会被登记在全局环境中供搜索
- Symbol() 生成的不会在全局环境中,Symbol.for()方法是搜索不到Symbol()创建的Symbol的值
10.5.2 Symbol.keyFor()
**其会返回一个已登记的Symbol类型值的 key **
var s1=Symbol.for('foo')
Sybmol.keyFor(s1) //'foo'
var s2=Symbol('foo')
Symbol.keyFor(s2) // undefined
上面的代码中,s2属于未登记的Symbol的值
❗️ 注意: Symbol.for为Symbol值登记的名字是全局环境