开篇

ES6之前我们都清楚JS有六种数据类型:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object),今天笔者讲的Symbol类型是ES6才加入的,它最大的特点就如标题所说“独一无二”。

本篇文章将从以下方面进行介绍:

  • 值类型和引用类型介绍

  • 如何声明一个Symbol?

  • 为什么要有Symbol?

  • Symbol的常用用法

  • 内置常用Symbol值的用法

本篇文章阅读时间预计15分钟

01

值类型和引用类型介绍

在了解Symbol之前,我们需要了解下JS的数据类型,在JS中数据类型分为两类:值类型和引用类型。

  • 值类型:数值型(Number),字符类型(String),布尔值型(Boolean),null 和 underfined

  • 引用类型:对象(Object)

所谓的值类型可以这样理解:变量之间的互相赋值,是指开辟一块新的内存空间,将变量值赋给新变量保存到新开辟的内存里面;之后两个变量的值变动互不影响。 如下段代码所示:

let weChatName ="前端达人";//开辟一块内存空间保存变量 weChatName 的值“前端达人”;let touTiao =weChatName;//给变量 touTiao 开辟一块新的内存空间,将 weChatName 的值 “前端达人” 赋值一份保存到新的内存里;//weChatName 和 touTiao 的值以后无论如何变化,都不会影响到对方的值;

一些语言,比如 C,有引用传递和值传递的概念。JS 也有类似的概念,它是根据传递的数据类型推断的。如果将值传递给函数,则重新分配该值不会修改调用位置中的值。但是,如果你修改的是引用类型,那么修改后的值也将在调用它的地方被修改。

所谓的引用类型可以这样理解:变量之间的互相赋值,只是指针的交换,而并非将对象复制一份给新的变量,对象依然还是只有一个,只是多了一个指引~~;如下段代码所示:

let weChat = { name: "前端达人", regYear:"2014" };//需要开辟内存空间保存对象,变量 weChat 的值是一个地址,这个地址指向保存对象的空间;let touTiao= weChat;// 将 weChat 的指引地址赋值给 touTiao,而并非复制一给对象且新开一块内存空间来保存;weChat.regYear="2018";console.log(touTiao);//output:{ name: '前端达人', regYear: '2018' }// 这个时候通过 weChat 来修改对象的属性,则通过 touTiao 来查看属性时对象属性已经发生改变;

那Symbol是什么数据类型呢?这里笔者先告诉大家是值类型,下面会有详细的介绍。

02

如何声明一个Symbol?

Symbol最大的特点就如本篇文章的标题一样:独一无二。这个独一无二怎么解释呢?就好比双胞胎,外表看不出差别,但是相对个体比如性格爱好还是有差异的,每个人都是独一无二。Symbol表示独一无二的值,是一种互不等价标识,声明Symbol十分简单,如下段代码所示:

const s = Symbol();

Symbol([description]) 声明方式,支持一个可选参数,只是用于描述,方便我们开发调试而已。每次执行Symbol()都会生成一个独一无二的Symbol值,如下段代码所示:

let s1 = Symbol("My Symbol");let s2 = Symbol("My Symbol");console.log(s1 === s2); // Outputs false”

由此可见,即使Symbol的描述值参数相同,它们的值也不相同,描述值仅仅是起描述的作用,不会对Symbol值本身起到任何的改变。关于描述值需要注意的一点:接受除Symbol值以外所有的值,怎么理解呢,请看下段代码所示:

const symbol = Symbol();const symbolWithString=Symbol('前端达人');//Symbol(前端达人)const SymbolWithNum=Symbol(3.14);//Symbol(3.14)const SymbolWithObj=Symbol({foo:'bar'});//Symbol([object Object])const anotherSymbol=Symbol(symbol);//TypeError: Cannot convert a Symbol value to a string

接下来笔者来详细解释下,为什么Symbol是值类型,而不是引用类型。Symbol函数并不是构造函数,因此不能使用new方法来生成一个Symbol对象,否则编译器会抛出异常,如执行下段代码所示:

new Symbol();//TypeError: Symbol is not a constructor

由此可见,Symbol是一种值类型而非引用类型,这就意味着如果将Symbol作为参数传值的话,将会是值传值而非引用传值,如下段代码所示(值的改变没有互相影响):

const symbol=Symbol('前端达人');function fn1(_symbol) {    return _symbol==symbol;}console.log(fn1(symbol));//output:true;function fn2(_symbol) {    _symbol=null;    console.log(_symbol);}fn2(symbol);//output:null;console.log(symbol);//Symbol(前端达人)

03

为什么要有Symbol?

介绍了这么多,Symbol存在的意义是什么?笔者先举个简单的业务场景:

在前端的JavaScript应用开发中,需要先通过渲染引擎所提供的API来获取一个DOM元素对象,并保留在JavaScript运行时中。因为业务需要,需要通过一个第三方库对这个DOM元素对象进行一些修饰和调整,即对该DOM元素对象进行一些新属性的插入。

而后来因为新需求的出现,需要再次利用另外一个第三方库对同一个DOM元素对象进行修饰。但非常不巧的是这个第三方库同样需要对该DOM元素对象进行属性插入,而恰好这个库所需要操作的属性与前一个第三方库所操作的属性相同。这种情况下就很有可能会出现两个第三方库都无法正常运行的现象,而使用这些第三方库的开发者却难以进行定位和修复。

针对上述问题, Symbol可以提供一种良好的解决方案。这是因为Symbol的实例值带有互不等价的特性,即任意两个Symbol值都不相等。在ES2015标准中,字面量对象除了可以使用字符串、数字作为属性键以外,还可以使用Symbol作为属性键,因此便可以利用Symbol值的互不等价特性来实现属性操作的互不干扰了。

04

Symbol的常用用法

1、判断是否是Symbol

如何判断一个变量是不是Symbol类型呢?目前唯一的方法就是使用typeof,如下段代码所示:

const s = Symbol();console.log(typeof s);//Outputs "symbol”

2、用作对象的属性

通常我们使用字符串定义对象的属性(Key),有了Symbol类型后,我们当然可以使用Symbol作为对象的属性,唯一不同的地方,我们需要使用[]语法定义属性,如下段代码所示:

const WECHAT_NAME = Symbol();const WECHAT_REG = Symbol();let obj = {    [WECHAT_NAME]: "前端达人";}obj[WECHAT_REG] = 2014;console.log(obj[WECHAT_NAME]); //output: 前端达人console.log(obj[WECHAT_REG]); //output:2014

还有一点需要强调的是,使用Symbol作为对象的Key值时,具有私有性,我们无法通过枚举获取Key值,如下段代码所示:

let obj = {    weChatName:'前端达人',    regYear: 2014,    [Symbol('pwd')]: 'wjqw@$#sndk9012',};console.log(Object.keys(obj));// ['weChatName', 'regYear']for (let p in obj) {    console.log(p)    // 分别会输出:'weChatName' 和 'regYear'}console.log(Object.getOwnPropertyNames(obj));// [ 'weChatName', 'regYear' ]

从上述代码中,可以看出Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。利用该特性,我们可以把一些不需要对外操作和访问的属性可以使用Symbol来定义。由于这一特性的存在,我们使用JSON.stringify()将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外,在上述代码中执行下段代码:

console.log(JSON.stringify(obj));//output:{"weChatName":"前端达人","regYear":2014}

基于这一特性,我们可以更好的去设计我们的数据对象,让“对内操作”和“对外选择性输出”变得更加灵活。

我们难道就没有办法获取Symbol方式定义的对象属性了么?私有并不是绝对的,我们可以通过一些API函数进行获取,在上述代码中执行下段代码:

// 使用Object的APIconsole.log(Object.getOwnPropertySymbols(obj)); // [Symbol(pwd)]
// 使用新增的反射APIconsole.log(Reflect.ownKeys(obj));// [Symbol(pwd), 'age', 'title']

3、定义类的私有属性/方法

我们都清楚在JS中,是没有如Java等面向对象语言的访问控制关键字private的,类上所有定义的属性或方法都是可公开访问的。上面笔者讲到作为对象属性具有私有性的特点,我们定义类的私有属性和方法才能实现,如下段代码所示:

我们先建立一个a.js的文件,如下所示:

const PASSWORD = Symbol();class Login {    constructor(username, password) {        this.username = username;        this[PASSWORD] = password;    }    checkPassword(pwd) {        return this[PASSWORD] === pwd;    }}export default Login;

我们在建立一个文件b.js,引入a.js文件,如下所示:

import  Login from './a.js';const login = new Login('admin', '123456');console.log(login.checkPassword('123456'));  // trueconsole.log(login.PASSWORD);  // undefinedconsole.log(login[PASSWORD]);// PASSWORD is not definedconsole.log(login["PASSWORD"]); // undefined

由于Symbol常量PASSWORD被定义在a.js所在的模块中,外面的模块获取不到这个Symbol,也不可能再创建一个一模一样的Symbol出来(因为Symbol是独一无二的),因此这个PASSWORD的Symbol只能被限制在a.js内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,从而实现了私有化的效果。

4、创建共享Symbol

虽然Symbol是独一无二的,但是有些业务场景,我们需要共享一个Symbol,我们如何实现呢?这种情况下,我们就需要使用另一个API来创建或获取Symbol,那就是Symbol.for(),它可以注册或获取一个全局的Symbol实例,如下段代码所示:

let obj = {};(function(){    let s1 = Symbol("name");    obj[s1] = "Eden";})();console.log(obj[s1]);//SyntaxError: Unexpected identifier cannot be accessed here(function(){    let s2 = Symbol.for("age");    obj[s2] = 27;})();console.log(obj[Symbol.for("age")]); //Output "27”

从上述代码可以看出,Symbol.for()会注册一个全局作用域的Symbol值,如果这个Key值从未使用则会进行创建注册,如果已被注册,则会返回一个与第一次使用创建的Symbol值等价的Symbol,如下段代码所示:

const symbol=Symbol.for('foo');const obj={};obj[symbol]='bar';const anotherSymbol=Symbol.for('foo');console.log(symbol===anotherSymbol);//output:trueconsole.log(obj[anotherSymbol]);//output:bar

05

常用Symbol值及意义

我们除了可以自行创建Symbol值以外,ES6还将其应用到了ECMAScript引擎的各个角落,我们可以运用这些常用值对底层代码的实现逻辑进行修改,以实现更高级的定制化的需求。

以下表格进行了常用Symbol值的总结:

定义项

描述

含义

@@iterator

"Symbol.iterator"

用于为对象定义一个方法并返回一个属于所对应对象的迭代器。该迭代器会被for-of循环使用。

@@hasInstance

"Symbol.hasInStance"

用于为类定义一个方法。该方法会因为instanceof语句的使用而被调用,来检查一个对象是否是某一个类的实例。

@@match

"Symobol.match"

用于正则表达式定义一个可被String.prototype.match()方法使用的方法,检查对应字符串与当前正则表达式是否匹配

@@replace

"Symbol.replace"

用于正则表达式会对象定义一个方法。该方法会因为String.prototype.replace()方法的使用而被调用,用于处理当前字符串使用该正则表达式或对象作为替换标志时的内部处理逻辑

@@search

"Symbol.search"

用于正则表达式会对象定义一个方法。该方法会因为String.prototype.search()方法的使用而被调用,用于处理当前字符串使用该正则表达式或对象作为位置检索标志时的内部处理逻辑

@@split

"Symbol.split"

用于正则表达式会对象定义一个方法。该方法会因为String.prototype.split()方法的使用而被调用,用于处理当前字符串使用该正则表达式或对象作为分割标志时的内部处理逻辑

@@unscopables

"Symbol.unscopables"

用于为对象定义一个属性。该属性用于描述该对象中哪些属性是可以被with语句所使用的。

@@isConcatSpreadable

"Symbol.isConcatSpreadable"

用于为对象定义一个属性。该属性用于决定该对象作为Array.prototype.concat()方法参数时,是否被展开。

@@species

"Symbol.species"

用于为类定义一个静态属性,该属性用于决定该类的默认构建函数。

@@toPrimitive

"Symbol.toPrimitive"

用于为对象定义一个方法。该方法会在该对象需要转换为值类型的时候被调用,可以根据程序的行为决定该对象需要被转换成的值。

@@toStringTag

"Symbol.toStringTag"

用于为类定义一个属性。该属性可以决定这个类的实例在调用toString()方法时,其中标签的内容。

由于常用Symbol值比较多,笔者只对其中最常用的几个进行解释。

1、Symbol.iterator

我们可以使用Symbol.iterator来自定义一个可以迭代的对象,我们可以使用Symbol.iterator作为方法名的方法属性,该方法返回一个迭代器(Iterator)。虽然JS中没有协议(Protocal)的概念,我们可以将迭代器看做成一个协议,即迭代器协议(Iterator Protocal),该协议定义了一个方法next(),含义是进入下一次迭代的迭代状态,第一次执行即返回第一次的迭代状态,该迭代状态有两个属性,如表格所示:

定义项

描述

含义

done

Boolean

该迭代器是否已经迭代结束

value

Any

当前迭代状态值

以下是我们使用Symbol.iterator带迭代的方法,如下段代码所示:

let obj = {    array: [1, 2, 3, 4, 5],    nextIndex: 0,    [Symbol.iterator]: function(){        return {            array: this.array,            nextIndex: this.nextIndex,            next: function(){                return this.nextIndex < this.array.length ?                    {value: this.array[this.nextIndex++], done: false} :                    {done: true};            }        }    }};let iterable = obj[Symbol.iterator]();console.log(iterable.next().value);console.log(iterable.next().value);console.log(iterable.next().value);console.log(iterable.next().value);console.log(iterable.next().value);console.log(iterable.next().done);

以上代码将会输出:

12345true

除了可以自定义迭代的逻辑,我们也可以使用引擎默认的迭代,从而节省了我们的代码量,如下段代码所示:

const arr = [1, 2];const iterator = arr[Symbol.iterator](); // returns you an iteratorconsole.log(iterator.next());console.log(iterator.next());console.log(iterator.next());

以上代码将会输出:

{ value: 1, done: false }{ value: 2, done: false }{ value: undefined, done: true }

2、Symbol.hasInstance

用于为类定义一个方法。该方法会因为instanceof语句的使用而被调用,来检查一个对象是否是某一个类的实例, 用于扩展instanceof的内部逻辑,我们可以用于为一个类定一个静态方法,该方法的第一个形参便是被检测的对象,而自定义的方法内容决定了instanceof语句的返回结果,代码如下:

class Foo{    static [Symbol.hasInstance](obj){        console.log(obj);        return true;    }}console.log( {} instanceof  Foo);

以上代码将会输出:

{}true

3、Symbol.match

Symbol.match 在字符串使用match()方法时,为其实现自定义的逻辑。如下段代码所示:

没自定义前:

const re=/foo/console.log('bar'.match(re));//nullconsole.log('foo'.match(re));//[ 'foo', index: 0, input: 'foo', groups: undefined ]

使用Symbol.match后:

const re=/foo/;re[Symbol.match]=function (str) {    const regexp=this;    console.log(str);    return true;};console.log('bar'.match(re));console.log('foo'.match(re));

上段代码将会输出:

bartruefootrue

4、Symbol.toPrimitive

在JS开发中,我们会利用其中的隐式转换规则,其中就包括将引用类型转换成值类型,然而有时隐式转换的结果并不是我们所期望的。虽然我们可以重写toString()方法来自定义对象在隐式转换成字符串的处理,但是如果出现需要转换成数字时变得无从入手。我们可以使用Symbol.toPrimitive来定义更灵活处理方式,如下段代码所示(仅为演示,可结合自己的业务自行修改):

const obj={};console.log(+obj);console.log(`${obj}`);console.log(obj+"");//output://NaN//[object Object]//[object Object]const transTen={    [Symbol.toPrimitive](hint){        switch (hint) {            case 'number':                return 10;            case 'string':                return 'Ten';            default:                return true;        }    }}console.log(+transTen);console.log(`${transTen}`);console.log(transTen+"");//output://10//Ten//true

5、Symbol.toStringTag

前面的表格提到过,Symbol.toStringTag的作用就是自定义这个类的实例在调用toString()时的标签内容。比如我们在开发中定义的类,就可以通过Symbol.toStringTag来修改toString()中的内容,利用它做为属性键为类型定一个Getter。如下段代码所示:

class Foo{    get [Symbol.toStringTag](){return 'Bar'}}const obj=new Foo();console.log(obj.toString());//output:[object Bar]

05

小节

今天的内容有些多,需要慢慢理解,我们清楚了Symbol值是独一无二的,Symbol的一些使用场景,以及使用Symbol常用值改写更底层的方法,让我们写出更灵活的处理逻辑。Symbol虽然强大,但是用好它还需要在实践中结合业务场景进行掌握。

精彩推荐

ES6基础丨let和作用域

ES6基础丨const介绍

ES6基础丨默认参数值

ES6基础丨展开语法(Spread syntax)

ES6基础丨解构赋值(destructuring assignment)

ES6基础丨箭头函数(Arrow functions)

ES6基础丨模板字符串(Template String)

ES6基础丨Set与WeakSet

ES6基础丨Map与WeakMap

JavaScript基础丨前端不懂它,会再多框架也不过只是会用而已!

JavaScript基础丨你真的了解JavaScript吗?

JavaScript基础丨回调(callback)是什么?

JavaScript基础丨Promise使用指南

JavaScript基础丨深入学习async/await

JS加载慢?谷歌大神带你飞!(文末送电子书)

19年你应该关注这50款前端热门工具(上)

19年你应该关注这50款前端热门工具(中)

19年你应该关注这50款前端热门工具(下)

专注分享当下最实用的前端技术。关注前端达人,与达人一起学习进步!

长按关注"前端达人"

android log 如何获取double类型后小数点的值_【ES6基础】Symbol介绍:独一无二的值...相关推荐

  1. java获取double类型区间随机数

    获得0.68-6.88的随机数 前提:java获取double类型区间随机数 *** 获取0.68-6.88之间的随机数* @return*/ public static Double queryHo ...

  2. Android之如何获取网络类型并判断是否可用

    ConnectivityManager主要管理和网络连接相关的操作,通过getSystemService(Context.CONNECTIVITY_SERVICE)获 取网络连接的服务.因此我们可以通 ...

  3. Android开发之获取网络类型(WIFI、2G、3G、4G)和运营商名称

    在Android开发中,常常使用到网络,可能需要针对不同的网络(WIFI或者流量),需要做不同的处理,那么怎么获取当前网络呢?为此写了个工具类,以后需要使用,直接拿来用就行(就喜欢拿来主义,哈哈). ...

  4. java中double类型占几个字节_面试官:Java 中有几种基本数据类型是什么?各自占用多少字节?...

    认识基本数据类型 在学习基本数据类型之前,我们先认识一下这两个单词:1.bit --位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为"0"或"1&quo ...

  5. C++11获取double类型的最大最小值

    #include <iostream> #include <locale>using namespace std;int main() {//使用template <cl ...

  6. python中编完类后到实例编写_[零基础学python]编写类之一创建实例

    虽然已经对类有了一点点模糊概念,但是,阅读前面一讲的内容的确感到累呀,都是文字,连代码都没有. 本讲就要简单多了,尝试走一个类的流程. 说明:关于类的这部分,我参考了<Learning Pyth ...

  7. Java通过字节流获取图片类型

    注意,getPicType方法获取图片类型后,会重置流,而不是关闭流. package com.hztuen.util;import java.io.IOException; import java. ...

  8. c语言使用double时 %,c语言double类型的输入

    c语言double类型数据四舍五入 借助math库的round函数 #include double ext_round(double data, int precision) { , precisio ...

  9. android float类型保留两位小数_你知道MySQL中Decimal类型和Float Double的区别吗?

    出处:cnblogs.com/panchanggui/p/10766607.html MySQL中存在float,double等非标准数据类型,也有decimal这种标准数据类型. 其区别在于,flo ...

最新文章

  1. 【PAT (Basic Level) 】1025 反转链表 (25 分)
  2. 训练指南 UVALive - 3713 (2-SAT)
  3. 关于Android 中 一个错误的解决办法 “Do not request Window.FEATURE_ACTION_BAR.....
  4. python递归查询并列出目录
  5. centos 环境变量配置
  6. android使用 注解框架,Android实践 | 注解框架ButterKnife基本使用
  7. 接口测试用例——测试用例评审
  8. 前端开发如何独立解决跨域问题
  9. 2018顺丰视觉岗笔试几个知识点
  10. evaluatejavascript不起作用_android4.4 evaluateJavascript 到android2.X上不能调用的问题
  11. [回帖整理] 创业难
  12. linux+qt经典教程+pdf下载,Qt入门教程 详细讲解版.pdf
  13. 蔽月山房---作者,王阳明
  14. 项目管理如何建立有效的团队沟通机制
  15. 软件测试中的心理学效应
  16. zabbix使用SNMP监控思科无线控制器WLC
  17. Python原生服务端签名生成请求订单信息「orderString」
  18. 为什么要使用ELK-----EKL的原理 ---以及ELK的配置 详细! 小白都能懂
  19. 专门为某种用途设计的计算机称为,专门为某种用途而设计的计算机,称为( )计算机。...
  20. Openstack Train版搭建

热门文章

  1. 静态定义的receiver接收broadcast intent
  2. 在Azure上的Ubuntu Docker运行SQL Server
  3. 贝索斯前妻再捐2.81亿美元
  4. 潜藏中国30年,营收远超老干妈6倍,它才是真正的隐形辣酱冠军
  5. 外媒:台积电准备明年下半年采用3nm工艺为苹果生产芯片
  6. 外媒:下代iPad Pro将同时具备无线充电和反向无线充电功能
  7. “听话”的苏宁少东家
  8. 京东数科科创板IPO获受理,刘强东为实际控制人
  9. 英伟达或正与软银就收购ARM深入谈判,交易价值超过320亿
  10. 腾讯阿里达成“共识”!马化腾称“用链量”“用云量”或成数字时代的重要指标...