一念之差,错失良机!浅谈基础类型之Symbol,给你的笔试多加一分~

1,802 阅读5分钟

前言

12月初的时候,我打算将阮大大的《ECMAScript6 入门》做一个笔记,顺带作为参考的,但是这个文章已经在11号左右的时候流产了。为什么呢?一个是因为最近主管给分配了任务,没啥时间做;还有一个原因就是:尼玛看不懂了啊!!!

最近事情少了,回过头继续学习,看到一个有意思的玩意:Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。

原始数据类型??这让我想起来之前的一道笔试题:JavaScript数据类型有哪些?

当时我写的是:undefined、null、number、string、boolean、Object。面试官问了我一句,数据类型就是这些了吗?我心里有个Symbol的印象,但是当面试官继续问作用的时候我哑然了,因为我的确没了解过具体这东西是干嘛的呀?于是面试官给我讲起了Symbol的相关知识:

概述

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。

基本用法

let s = Symbol();

typeof s // => "symbol" 

这好像和以前生成对象的写法不太一样,没有用new命令,你问我为什么?废话用new命令那不就是对象了吗?Symbol是原始数据类型呀!和对象是并列关系~

有的童鞋会问,用Symbol()生成的话,很不利于阅读呀,谁知道这个Symbol是干嘛的呢?没关系,Symbol已经帮我们想好了解决办法:

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

let s1 = Symbol('s1'); // => Symbol(s1)
let s2 = Symbol('s2'); // => Symbol(s2)
let s3 = Symbol('s1'); // => Symbol(s1)

这时候又有好奇宝宝要问了,加上了描述,这两个Symbol值是不是就变成一样的了?

s1 == s3 // => false
s1 === s3 // => false

不管是==还是===,返回值都是false,这说明:

Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

类型转换

Symbol 值不能与其他类型的值进行运算,会报错。

let s = Symbol('s');
s + ' is a Symbol!' // => index.js:13 Uncaught TypeError: Cannot convert a Symbol value to a string

Symbol 值可以显式转为字符串。

let s = Symbol('s');
s.toString(); // => Symbol(s)
s.toString() + ' is a Symbol!' // => Symbol(s) is a Symbol!

可以看到这时候可以和字符串相加了。

Symbol 值也可以转为布尔值,但是不能转为数值。

let s = Symbol();
Boolean(s); // => true
!s // => false

Number(s); // => TypeError: Cannot convert a Symbol value to a number
Number(s) + 2; // => TypeError: Cannot convert a Symbol value to a number
  • 上面给Symbol添加的描述,要想获取的话只能通过toString()显式转换,比较不方便

ES2019 提供了一个实例属性description,直接返回 Symbol 的描述。

let s = Symbol('this is a Symbol');
s.description // => this is a Symbol

Symbol用法

Symbol 值可以作为标识符,用于对象的属性名。

有人问了,我用字符串做属性名,又快又方便,为什么要用Symbol呢?

这个问题问的好,很多学习不细心(可能只有我一个)的童鞋就有这样的疑问!

我们来看看上面对Symbol的介绍:

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。

是的,独一无二,你有没有在开发中遇到变量名类似的情况,苦于不知道怎么区分?你是否在操作其他同事编写的对象的时候,害怕自己添加的变量名将其他变量覆盖,或者被其他变量覆盖呢?这时候你就需要一个Symbol值来续命(+ 1s)了!

作为属性名使用

let s = Symbol();
// 第一种写法
let obj = {
    [s]: 'hello Symbol'
}

// 第二种写法
let obj = {};
obj[s] = 'hello Symbol'

// 第三种写法
let obj = {};
Object.defineProperty(obj,s,{value:'hello Symbol'})

// 以上三种写法的结果都是
obj[s]; // => hello Symbol

注意

  • Symbol值作为属性名,读取时不能使用点运算符
  • 在对象内部使用Symbol值作为属性名时,必须放在方括号中

作为属性名时的遍历

Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。

for大法都无法遍历?那是不是私有属性?回答是:NO,对象提供了一个Object.getOwnPropertySymbols()方法用于获取对象内所有Symbol属性名,返回一个数组

实例:消除魔术字符串

消除魔术字符串

Symbol.for()

有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。

let s1 = Symbol.for('s');
let s2 = Symbol.for('s');
s1 === s2; // => true

那么问题来了:SymbolSymbol.for的区别是什么呢?

前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。

面试结束

到这个时候面试已经结束

但我感觉事情可能并没有想象中的那么简单

内置的Symbol值

回看一下阮大大的教程最后:

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。

Symbol.hasInstance

对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。

function fn(){
    
}
let ins = new fn();
ins instanceof fn; // => true
fn[Symbol.hasInstance](ins); // => true

Symbol.isConcatSpreadable

对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。

let arr1 = ['a','b'];
let arr2 = ['c','d'];
arr1.concat(arr2); // => ['a','b','c','d']

arr2[Symbol.isConcatSpreadable] = false;
arr1.concat(arr2); // => ['a','b',['c','d']]

Symbol.species、Symbol.match、Symbol.relpace、Symbol.search、Symbol.split.....

这些咱也不知道有啥用,咱也看不懂,等后面有闲工夫再去研究吧~