刷新你对基础类型Symbol的认知,来看看

846 阅读7分钟

前言

最近在读红宝书第四版读完了Symbol这一块其实以前在读其他书籍第三版红宝书和javascript权威指南的的时候也有研究过,每次读到这里都刷新了自己对这个类型的认知,今天做个总结一来对这一块知识的梳理二来对学习的内容做个记录

Symbol官方

先介绍一下symbol的定义是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的

认知

说一下我自己对Symbol的理解首先我认为出现一个类型肯定是因为有些问题需要这个类型去解决,这个就像react出现fiber架构一样,那么Symbol他可以做那些事呢?可能对其研究不深的只会认为可以创建一个唯一的值保证唯一性但是这只是它的一部分,其实还有很多像我们在工作中有些常用的方法内置的原理都是通过Symbol实现的例如判断或者迭代和转换类型等方法只是在平常开发中如果不研究可能就不会了解,不过不要紧看完这篇文章就了解了

解答

问题1:Symbol为什么不能当做构造函数实例化?

1.如果你直接使用new Symbol当做构造函数进行实例化的时候,你就会在控制台中看到Sybmol is not a constructor

image-20211111165331847.png

2.像其他构造函数如String、Object、Boolean都可以被实例化,但是为什么Symbol不可以呢?因为大家都清楚Symbol是后期Es6新增的一个类型,那么当新增这个类型的时候他会考虑到是不是用户在实际应用中经常会有进行实例化的操作

3.这个在以前比较老的构造函数就可以看出来,当我们使用一些基础类型的时候大部分情况都会使用字面量的方式去创建也就是下面这个样子,而不会使用new String这种繁琐的方式,所以官方考虑到实例化这种情况并不是很多所以也就没有必要扩展可以实例化的方式

let a2 = '风清扬' //字面量方式
let a3 = new String('风清扬') //构造函数方式

4.那么背后的是怎么禁止进行new 实例化的呢?主要使用instancof来进行判断 ,简单实现了一下可以看一下代码

function _Symbol() {
  if (this instanceof _Symbol) {
    throw new Error('Uncaught TypeError: _Symbol is not a constructor')
  }
  return this
}
console.log(_Symbol()) //Window
console.log(new _Symbol()) //_Symbol is not a constructor

5.其实这里还隐藏一个问题就是当我们直接去声明一个字面量的方式如果用typeof去检测比如下面这种,他会返回一个string类型,但是虽然返回string类型但是你也可以把它当对象的方式去调用上面常用的方法,那么之所以有这种能力,背后的原理其实是在你定义string类型的时候,他实际自己会调用new String的方法生成一个实例,那么也就是说当你调用split、slice等方法的时候调用的是String实例中的方法

let a4 = '东方不败' 
console.log(typeof a4) //string

问题2:Symbol出现解决了什么问题?

1.首先Symbol的出现有两个方面一个是提供了唯一的属性名字,这样从根本上就防止了命名冲突的问题例如下面这个样子即使一样还是false

let a1 = Symbol('令狐冲')
let a2 = Symbol('令狐冲')
console.log(a1 === a1) // false

2.第二个方面就是Symbol上面提供了很多内置的方法,这些方法就像我上面所说是很多我们常用的方法内部要调用的,比如当使用for of进行遍历的时候他背后的原理是对Symbol.iterator的调用,还有类似的 instanceof背后也是对Symbol.hasInstance的调用,在我看来这些可能是Symbol比较大的用处像上面那种创建唯一性的变量可能在现在的主流框架开发中用的并不是很多

实操

我们举几个🌰来进行实操看一下Symbol对我们常用的方法背后的支持

Symbol.iterator

探究for of,我们可以看到在arr上是可以获取到Symbol.iterator它返回了一个迭代器,然后我们直接遍历迭代器他返回了数组里面的值,直接遍历arr也可以返回里面的值,那么怎么证明他背后的原理是遍历的Symbol.iterator

let arr = [1,2,3]   
​
console.log(arr[Symbol.iterator]()) // Array Iterator {} 迭代器for (let item of arr[Symbol.iterator]()){
  console.log(item) //1,2,3
}
​
for (let item of arr){
   console.log(item) //1,2,3
}
​
  • 做个实验将arr上的Symbol.iterator 设置为null,可以看到返回了arr is not iterable其实这就可以说明,任何可以使用for of进行遍历的数据类型背后都是遍历的Symbol.iterator
 let arr = [1,2,3]   
​
 arr[Symbol.iterator] = null //在这里设置迭代器为nullfor (let item of arr){
  console.log(item) //arr is not iterable
}
  • 现在拿对象去测试一下,可以看到他直接返回了obj is not iterable,因为对象本身并没有Symbol.iterator所以并不支持for of遍历
let obj = { name: '令狐冲', from: '华山派' }
​
for (let item of obj) {
  console.log(item) //obj is not iterable
}
  • 看下面的例子通过Symbol.iterator实现一个可以使用for of遍历的数据,那面可以得出结果Symbol.iterator赋予了for of可以遍历的特性
let _Iterable = {}
​
_Iterable[Symbol.iterator] = function* () {
  yield '宁中则';
  yield '岳不群';
  yield '岳灵珊';
}
​
for (let item of _Iterable) {
  console.log(item) //宁中则、岳不群、岳灵珊
}

可能大家还有其他问题想问比如原本已经有for循环了为什么还会出现for of呢,那其实在遍历新增的数据类型的时候比如MapSet等我们可以使用for of进行遍历但是不可以使用普通for循环

Symbol.toPrimitive

再来看一下另外一个属性Symbol.toPrimitive,官方对它的介绍是一个内置的Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数,看下面的例子

let obj = { a: 1 }
​
console.log(+obj) //直接进行原始类型转换返回NaN
  • 实际上我们可以通过这个属性在转化的类型时候进行劫持做一些其他操作,可以看到这个时候返回了2,说明我们拦截成功了并且更改了返回的值
let obj = { a: 1 }
​
obj[Symbol.toPrimitive] = function (type){
  if (type === 'number'){
    return 2
  }
  return null
}
​
console.log(+obj) //2
  • 讲一个有意思的东西,可能在以往的经历中有人问过你怎么实现a ==1 && a == 2 && a == 3返回true,或许你当时很懵逼但是确实可以通过这个属性实现
let a = { val: 1 }
​
a[Symbol.toPrimitive] = function () {
  return a.val++
}
​
console.log(a == 1 && a == 2 && a == 3) //true

Symbol.hasInstance

坚持一下最后一个例子,Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此你可以用它自定义instanceof操作符在某个类上的行为,看下面的例子

class _Array {
  //在类上通过hasInstance监听
  static [Symbol.hasInstance](instance) {
    console.log(instance) //判断的内容
    return Array.isArray(instance);
  }
}
​
console.log([] instanceof _Array) // true
console.log({} instanceof _Array) // false

总结

Symbol提供的内置方法还有很多我们没有说但是也都是比较类似,比如Symbol.asyncIterator扩展了for await方法还有Symbol.split、Symbol.replace等方法,可以说Symbol上的内置的属性方法隐藏在了我们这些常用的方法中,可能你不会直接用到它,但是它却一直在。以上这些知识点大部分来自于MDN和高级程序设计第四版