带你一文读懂Javascript中ES6的Symbol
带你一文读懂Javascript中ES6的Symbol
- 前言
- 基础类型
- Symbol
- Symbol.for 与 Symbol.keyFor
- Symbol.iterator
- Symbol.search
- Symbol.split
- Symbol.toPrimitive
- 总结
前言
Symbol
这个特性对于很多同学来说,可能是在学习ES6特性的过程中,感到比较困惑的一个特性点。在大部分开发场景中,你可能根本用不到这个特性,但理解Symbol
各个属性和方法的作用和意义还是非常有必要的,在一些特定的场景中,你会发现它不可或缺。Symbol
内含的方法和属性非常多,本文仅对大概率会用到的一些讲解。
基础类型
对Javascript
属性的小伙伴都知道,Javascript
中有6大基础类型:Boolean
,BigInt
,undefined
,Number
,String
,Symbol
。可以看到,Symbol
是Javascript
中的基础类型之一。简单的去理解,Symbol
数据就是一个全局唯一的值,那全局唯一有什么用?当然是用于避免冲突啦~
我们先来看看Symbol
最简单的用法:
const symbolOne = Symbol();
console.log(symbolOne); // Symbol()
由于Symbol
是基础数据类型,所以我们不能用new的方式去创建Symbol
对象,只需要直接调用Symbol
即可。这里我们创建了一个简单的Symbol
对象,你也可以像下面这样给Symbol
传入一个description
,用来标识一个Symbol
:
const symbolOne = Symbol('foreverpx');
console.log(symbolOne); // Symbol('foreverpx')
设置这个标识在调试的时候非常有用,你可以通过不同的标识把Symbol
值区分开来。
多人协同开发中创建Symbol
的时候,description
是有可能重复的。如果出现不同地方都用同一个description
创建了Symbol
,那这两个Symbol
是不相等的,比如下面的比较:
const symbolOne = Symbol('foreverpx');
const symbolTwo = Symbol('foreverpx');
console.log(symbolOne === symbolTwo); // false
这种情况就会比较奇怪,原本只是想通过全局唯一的值解决冲突,不同的description
返回不同的唯一值。但相同的description
应该返回一样的Symbol
值才对。
其实,description
只是一个标签,用于更清晰的标识Symbol
以及用于调试。虽然传入的description
相同,但是并不意味着Symbol('foreverpx')
的结果与Symbol('foreverpx')
相同,它们是不同的对象。
Symbol
在我看来,Symbol
的主要用途,是用来标识唯一的对象属性。
怎么理解?我们已经使用字符串来标识对象属性了,为什么还需要通过Symbol
来标识呢?
我们来看下面的场景:
我们预先定义好两个const来标识用户在线与不在线的两个状态,比如:
const ONLINE = 'online';
const OFFLINE = 'offline';
接着写一个带有switch
的函数来处理两种情况的业务逻辑:
function onUserStatus(status){switch(status){case ONLINE://do somethingreturn 'user online';case OFFLINE://do somethingreturn 'user offline';default:return 'unknown status'}
}
onUserStatus(ONLINE); // 'user online'
上面的代码现在看上去没有什么问题。但某天需求加了一种connenting
的状态,在新增常量的时候,前端同学copy了上面的OFFLINE
的语句来修改,但只修改了常量名为CONNECTING
,而忘了修改值为connenting
,那代码就会变成下面这样:
const ONLINE = 'online';
const OFFLINE = 'offline';
const CONNECTING = 'offline';
在调用onUserStatus
时,结果就不对了,这显然是变量值冲突导致的:
function onUserStatus(status){switch(status){case ONLINE://do somethingreturn 'user online';case OFFLINE://do somethingreturn 'user offline';default:return 'unknown status'}
}
onUserStatus(CONNECTING); //user offline
接下来我们把上面的代码,用Symbol
来写写看:
const ONLINE = Symbol('online');
const OFFLINE = Symbol('offline');
const CONNECTING = Symbol('offline');
function onUserStatus(status){switch(status){case ONLINE://do somethingreturn 'user online';case OFFLINE://do somethingreturn 'user offline';default:return 'unknown status'}
}
onUserStatus(CONNECTING); //unknown status
通过Symbol
改写后,会发现即使在copy代码后忘了改值,也不会产生与上面同样的结果。由于每次调用Symbol
都会生成一个全局唯一的值,所以传入上方任何一个常量,都不会得到相同的结果。
我们再来看一个对象的例子:
const foreverpx = {cnName: 'px'
}
这里定义了一个叫foreverpx
的Object
,其中有一个叫cnName
的属性。如果我们像这样用字符串来作为对象属性的key,那任何一个可以访问这个对象的地方,都可以改变它的值,比如: foreverpx.cnName = 'anthor'
。在某些情况下,我们不期望这样。
用Symbol
我们可以解决这个问题:
const cnName = Symbol('cnName');
const foreverpx = {[cnName]: 'px'
}
在这种写法下,只有当调用者同时拿到了cnName
这个Symbol
,才能修改这个属性的值。
从上面两个例子可以看到,使用Symbol
在一些场景下可以让我们的程序更加健壮。
Symbol.for 与 Symbol.keyFor
前面讲了Symbol
,写下来讲讲它的两个静态方法Symbol.for
,Symbol.keyFor
。
不记得什么是静态方法了?静态方法也就是在类上定义的,不需要实例化即可调用的方法,比如:
class ForeverPx{static getName(){console.log('foreverpx')}
}
ForeverPx.getName(); // foreverpx
Symbol
与这两个静态方法的区别是啥?还记得最开始的例子吗,同一个字符串创建的两个Symbol
,它的值是不相等的,Symbol.for
可以解决这个问题。
当Symbol.for
被调用时,它首先会判断传入的key
是否有被创建过,如果没有,则创建一个新唯一值。如果有,则返回之前的唯一值。
const symbolOne = Symbol.for('foreverpx')
const symbolTwo = Symbol.for('foreverpx')
console.log(symbolOne === symbolTwo); //true
所以,对于Symbol.for
来说,key
就是Symbol
的标识。而对于Symbol
来说,key
只是个简单的描述而已。
const symbolOne = Symbol.for('foreverpx')
const symbolTwo = Symbol('foreverpx')
console.log(symbolOne === symbolTwo); //false
console.log(symbolOne === Symbol.for('foreverpx')); //true
接下来是Symbol.keyFor
,其实这个方法是比较好理解的,从名字就能看出来,它是获取对应Symbol.for
创建的唯一值的key的:
const symbolOne = Symbol.for('foreverpx')
console.log(Symbol.keyFor(symbolOne)); //foreverpx
Symbol.iterator
到目前为止,上面讲的都是Symbol
的构造函数和方法,接下来开始介绍Symbol
的一些静态属性。首先要介绍的就是Symbol.iterator
了。
Symbol.iterator
从名字上就能看出,跟我们经常接触到的迭代器有很大的关系。在讲Symbol.iterator
之前,我们先来回顾下迭代器的概念。
什么是迭代器?迭代器就是能让你遍历并操作一个集合中的每一个元素的方法。在Javascript中,每一次循环都可以被称为迭代。你可以用for循环来遍历一个数据或者对象。如果一个对象的属性可以被诸如for of
等表达式遍历,则称这个对象是可迭代的。比如:
const foreverpx = [1,2,3];
for(let i of foreverpx){console.log(num); //1,2,3
}
可以被迭代的对象,在其原型上都能找到Symbol.iterator
属性,可以在控制台看到
所以如果你想知道一个对象能否被迭代器迭代,那么可以通过图上的方式来查看。
你会发现,并不是只要是对象就能被迭代,Object本身就是不能被迭代的。可以被迭代的对象还有String
,Map
之类的。
既然Symbol.iterator
是某些对象原型上的一个属性的key值,那么如果我们调用它,会返回什么呢?
调用后返回了一个迭代器对象,里面包含了一个next
方法,是不是跟yield*
很像呢。从逻辑上来看,我们只要通过不停的调用这个对象的next
方法,就能依次迭代对象里面的属性了。我们来验证一下:
const foreverpx = [1,2,3];
const iterator = foreverpx[Symbol.iterator]();
iterator.next(); //{value: 1, done: false}
iterator.next(); //{value: 2, done: false}
iterator.next(); //{value: 3, done: false}
iterator.next(); //{value: undefined, done: true}
next
方法每次调用时,会返回一个对象,里面包含2个属性,value
表示当前被迭代的值,done
表示当前是否所有属性都已迭代完。
既然对象的Symbol.iterator
属性对应的是一个方法,那么我们改写对应的方法,重新赋值给它,就能改变该对象的迭代行为呢?同样,我们通过代码来验证一下:
const foreverpx = [1,2,3];
foreverpx[Symbol.iterator] = function(){return {obj: foreverpx,index: 0,next(){const idDone = this.index === this.obj.length;this.index ++;if(this.obj[this.index] === 2){return {value: this.obj[this.index]*2, done: isDone }}else{return {value: this.obj[this.index], done: isDone }} }}
}
for (let item of foreverpx) {console.log(item);
}
//1,4,3
刚才上面有提到,不是所有的对象都能被迭代,就比如Object
:
const foreverpx = {name: 'px',age: '18'
}
for(let item of foreverpx){console.log(item);
}
这段代码执行完毕之后,我们会发现报错了,这确实如我们所预期的那样:
但我们可以通过Symbol.iterator
让它变得可以被迭代
const foreverpx = {name: 'px',age: '18'
}
foreverpx[Symbol.iterator] = function(){return {obj: foreverpx,index: 0,next(){const idDone = this.index === this.obj.length;this.index ++;return {value: this.obj[this.index], done: isDone }}}
}
for (let item of foreverpx) {console.log(item); //px, 18
}
另外,有些同学可能会搞混for of
和for in
的概念,这里对它们的差别不做过多的赘述,如果想了解更多,可以查看这篇文章for in 和for of的区别。
Symbol.search
我们在想要匹配某个正则在字符串中的位置时,有时候会使用下面的
方法来获取:
'foreverpx'.search('px'); // 7
上面代码返回了px
在foreverpx
字符串中出现的第一个位置。
当字符串的search
方法被调用时,也即是调用了Symbol.search
方法。
按照Symbol.iterator
的思路,我们同样可以通过改写Symbol.search
来覆盖字符串调用search时候的默认行为。
const str = 'foreverpx';
const reg = '/px/'
reg[Symbol.search] = function(str){return 2020;
}
str.search(reg); //2020
接下来的几个Symbol属性也是同样的作用和用法。
Symbol.split
同理,在字符串调用split
方法的时候,也即是调用了Symbol.split
。我们同样可以改写字符串在调用split
方法时候的行为。
const str = 'foreverpx cjl';
const splitReg = / /; //空格
splitReg[Symbol.split] = function(str){return ['px', 'cjl'];
}
str.split(splitReg); // ['px', 'cjl']
Symbol.toPrimitive
就跟它的名字一样,定义如何让一个对象变得原始、简单。
比如你有一个数组,你想让他变primitive
,你可能会这么做:
const arr = ['px', 'cjl'];
const prim = `${arr}`; //"px,cjl"
可以通过Symbol.toPrimitive
改写这个默认行为:
const arr = ['px', 'cjl'];
arr[Symbol.toPrimitive] = function() {return `foreverpx`;
};
const prim = `${arr}`; //"foreverpx"
对于Object
亦可
const obj = {name: 'px'};
obj[Symbol.toPrimitive] = function() {return `foreverpx`;
};
const prim = `${obj}`; //"foreverpx"
把原本的结果[object Object]
变成了foreverpx
。
总结
总的来说,Symbol
其实更像是提供了一个工具集,这个集合既能让你去生成一个全局唯一的值,也能在运行时去修改很多原始对象的默认行为,这些是在Symbol
出现之前是难以做到的。虽然在大部分情下,你可能很少会用到Symbol
,但学习Symbol
并理解它,或许能在你设计并实现一些逻辑时,为你提供一种解决思路。
如果文章对你有帮助,顺手点个赞和关注吧~
带你一文读懂Javascript中ES6的Symbol相关推荐
- 前端面试必会 | 一文读懂 JavaScript 中的 this 关键字
this 是一个令无数 JavaScript 编程者又爱又恨的知识点.它的重要性毋庸置疑,然而真正想掌握它却并非易事.希望本文可以帮助大家理解 this. JavaScript 中的 this Jav ...
- 案例+图解带你一文读懂SVG
资料链接 案例+图解带你一文读懂SVG (2.6W+字) 简介 SVG 是 Scalable Vector Graphics 的缩写,意为可缩放矢量图形.于 2003年1月14日 SVG 1.1 被S ...
- java中date类型如何赋值_一文读懂java中的Reference和引用类型
简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...
- 【强化学习炼金术】李飞飞高徒带你一文读懂RL来龙去脉
强化学习炼金术 · 背景介绍(上) 欢迎来到<强化学习炼金术>第一讲.手摇芭蕉扇,支起八仙炉,再点上三昧真火.各位炼金术师,你们都准备好了吗? 在这一课里,我会跟大家说说强化学习的概念和目 ...
- 一文读懂机器学习中的模型偏差
一文读懂机器学习中的模型偏差 http://blog.sina.com.cn/s/blog_cfa68e330102yz2c.html 在人工智能(AI)和机器学习(ML)领域,将预测模型参与决策过程 ...
- 一文读懂密码学中的证书
一文读懂密码学中的证书 之前的文章中,我们讲到了数字签名,数字签名的作用就是防止篡改和伪装,并且能够防止否认.但是要正确运用数字签名技术还有一个非常大的前提,那就是用来验证签名的公钥必须真正的属于发送 ...
- 一文读懂Java中File类、字节流、字符流、转换流
一文读懂Java中File类.字节流.字符流.转换流 第一章 递归:File类: 1.1:概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. ...
- 一文读懂SpringBoot中的事件机制
一文读懂SpringBoot中的事件机制?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法. 要"监听"事件,我们总是 ...
- 一文读懂机器学习中奇异值分解SVD
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 目录: 矩阵分解 1.1 矩阵分解作用 1.2 矩阵分解的方法一文 ...
最新文章
- 【C++自我精讲】基础系列二 const
- 测试ModelAttribute注解
- 计算机视觉与深度学习 | 粒子群算法与遗传算法(GA)及与蚁群算法(ACO)比较
- strlen和mb_strlen的区别
- mysql半同步复制实现
- 金山吹响讨伐灰鸽子的号角
- Spring再次涵盖了您:继续进行消费者驱动的消息传递合同测试
- @Retention注解
- 思考、学习新技术的原则和方式
- 判断图像局部过暗_数字图像处理(第五章)
- Java基础学习总结(139)——Java8 Stream之Stream接口入门简介
- 超详细,Wireshark 3.6.3安装教程(Windows系统)
- AD域控exchange邮箱—powershell 程序暂停sleep 继续执行的方法
- 2021李宏毅机器学习课程笔记——Recurrent Neural Network
- Android NFC技术解析,附Demo源码
- MySQL错误ERROR 1786 (HY000)解决
- 在服务器上安装centos系统
- 自己做量化交易软件(26)小白量化事件回测之MetaTrader5自动回测
- OFBiz财务模型-金融账户
- Quartu编写D触发器
热门文章
- 上集: Android开源库大全分类汇总(Android技术资料汇总)
- ecshop 解密index.php,PHP-威盾PHP加密专家解密算法
- 04.android studio 新建一个项目
- EMC 专用名词大全~测量
- 201221笔记:制作自定义数据集1.0——三维数据变四维、数组转换
- 北京实时公交app优缺点分析
- Unable to resolve host : No address associated with hostname
- NOIP2017搞机记
- 软件测试(软件测试生命周期,描述一个bug,定义bug级别,bug生命周期,如何开始第一次测试,测试执行和bug管理,测试工作中的人际关系处理)
- 清除华为路由交换设备console登录密码