带你一文读懂Javascript中ES6的Symbol

  • 前言
  • 基础类型
  • Symbol
  • Symbol.for 与 Symbol.keyFor
  • Symbol.iterator
  • Symbol.search
  • Symbol.split
  • Symbol.toPrimitive
  • 总结

前言

Symbol这个特性对于很多同学来说,可能是在学习ES6特性的过程中,感到比较困惑的一个特性点。在大部分开发场景中,你可能根本用不到这个特性,但理解Symbol各个属性和方法的作用和意义还是非常有必要的,在一些特定的场景中,你会发现它不可或缺。Symbol内含的方法和属性非常多,本文仅对大概率会用到的一些讲解。

基础类型

Javascript属性的小伙伴都知道,Javascript中有6大基础类型:BooleanBigIntundefinedNumberStringSymbol。可以看到,SymbolJavascript中的基础类型之一。简单的去理解,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'
}

这里定义了一个叫foreverpxObject,其中有一个叫cnName的属性。如果我们像这样用字符串来作为对象属性的key,那任何一个可以访问这个对象的地方,都可以改变它的值,比如: foreverpx.cnName = 'anthor'。在某些情况下,我们不期望这样。

Symbol我们可以解决这个问题:

const cnName = Symbol('cnName');
const foreverpx = {[cnName]: 'px'
}

在这种写法下,只有当调用者同时拿到了cnName这个Symbol,才能修改这个属性的值。

从上面两个例子可以看到,使用Symbol在一些场景下可以让我们的程序更加健壮。

Symbol.for 与 Symbol.keyFor

前面讲了Symbol,写下来讲讲它的两个静态方法Symbol.forSymbol.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本身就是不能被迭代的。可以被迭代的对象还有StringMap之类的。

既然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 offor in的概念,这里对它们的差别不做过多的赘述,如果想了解更多,可以查看这篇文章for in 和for of的区别。

Symbol.search

我们在想要匹配某个正则在字符串中的位置时,有时候会使用下面的
方法来获取:

'foreverpx'.search('px'); // 7

上面代码返回了pxforeverpx字符串中出现的第一个位置。

当字符串的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相关推荐

  1. 前端面试必会 | 一文读懂 JavaScript 中的 this 关键字

    this 是一个令无数 JavaScript 编程者又爱又恨的知识点.它的重要性毋庸置疑,然而真正想掌握它却并非易事.希望本文可以帮助大家理解 this. JavaScript 中的 this Jav ...

  2. 案例+图解带你一文读懂SVG

    资料链接 案例+图解带你一文读懂SVG (2.6W+字) 简介 SVG 是 Scalable Vector Graphics 的缩写,意为可缩放矢量图形.于 2003年1月14日 SVG 1.1 被S ...

  3. java中date类型如何赋值_一文读懂java中的Reference和引用类型

    简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...

  4. 【强化学习炼金术】李飞飞高徒带你一文读懂RL来龙去脉

    强化学习炼金术 · 背景介绍(上) 欢迎来到<强化学习炼金术>第一讲.手摇芭蕉扇,支起八仙炉,再点上三昧真火.各位炼金术师,你们都准备好了吗? 在这一课里,我会跟大家说说强化学习的概念和目 ...

  5. 一文读懂机器学习中的模型偏差

    一文读懂机器学习中的模型偏差 http://blog.sina.com.cn/s/blog_cfa68e330102yz2c.html 在人工智能(AI)和机器学习(ML)领域,将预测模型参与决策过程 ...

  6. 一文读懂密码学中的证书

    一文读懂密码学中的证书 之前的文章中,我们讲到了数字签名,数字签名的作用就是防止篡改和伪装,并且能够防止否认.但是要正确运用数字签名技术还有一个非常大的前提,那就是用来验证签名的公钥必须真正的属于发送 ...

  7. 一文读懂Java中File类、字节流、字符流、转换流

    一文读懂Java中File类.字节流.字符流.转换流 第一章 递归:File类: 1.1:概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. ...

  8. 一文读懂SpringBoot中的事件机制

    一文读懂SpringBoot中的事件机制?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法. 要"监听"事件,我们总是 ...

  9. 一文读懂机器学习中奇异值分解SVD

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 目录: 矩阵分解 1.1 矩阵分解作用 1.2 矩阵分解的方法一文 ...

最新文章

  1. 【C++自我精讲】基础系列二 const
  2. 测试ModelAttribute注解
  3. 计算机视觉与深度学习 | 粒子群算法与遗传算法(GA)及与蚁群算法(ACO)比较
  4. strlen和mb_strlen的区别
  5. mysql半同步复制实现
  6. 金山吹响讨伐灰鸽子的号角
  7. Spring再次涵盖了您:继续进行消费者驱动的消息传递合同测试
  8. @Retention注解
  9. 思考、学习新技术的原则和方式
  10. 判断图像局部过暗_数字图像处理(第五章)
  11. Java基础学习总结(139)——Java8 Stream之Stream接口入门简介
  12. 超详细,Wireshark 3.6.3安装教程(Windows系统)
  13. AD域控exchange邮箱—powershell 程序暂停sleep 继续执行的方法
  14. 2021李宏毅机器学习课程笔记——Recurrent Neural Network
  15. Android NFC技术解析,附Demo源码
  16. MySQL错误ERROR 1786 (HY000)解决
  17. 在服务器上安装centos系统
  18. 自己做量化交易软件(26)小白量化事件回测之MetaTrader5自动回测
  19. OFBiz财务模型-金融账户
  20. Quartu编写D触发器

热门文章

  1. 上集: Android开源库大全分类汇总(Android技术资料汇总)
  2. ecshop 解密index.php,PHP-威盾PHP加密专家解密算法
  3. 04.android studio 新建一个项目
  4. EMC 专用名词大全~测量
  5. 201221笔记:制作自定义数据集1.0——三维数据变四维、数组转换
  6. 北京实时公交app优缺点分析
  7. Unable to resolve host : No address associated with hostname
  8. NOIP2017搞机记
  9. 软件测试(软件测试生命周期,描述一个bug,定义bug级别,bug生命周期,如何开始第一次测试,测试执行和bug管理,测试工作中的人际关系处理)
  10. 清除华为路由交换设备console登录密码