1. symbol
Symbol 的基本用法
Symbol 能够创建独一无二的类型
1 | let s1 = Symbol('kin') |
可以作为对象的 key 使用:
1 | let obj = { |
Symbol 作为 key 时,Symbol 属性不能被 for in 枚举,能够通过 Reflect.ownKeys(obj)获取
Symbol.for
1 | let s3 = Symbol.for('kin') |
Symbol 可以做元编程
元编程即可以改写 js 语法本身
Symbol.toStringTag: 修改数据类型
1 | let obj = { |
Symbol.toPrimitive: 隐式类型转化
1 | let obj = {} |
Symbol.hasInstance: 改写 instanceof 的功能
1 | let instance = { |
2. set、map 与 weakSet、weakMap
Set
Set
对象是值的集合,Set 中的元素只会出现一次,即 Set 中的元素是唯一的。
1 | let s = new Set([1, 2, 3, 3]) |
常用方法
-
在
Set
对象尾部添加一个元素。返回该Set
对象。 -
返回一个新的迭代器对象,该对象包含
Set
对象中的按插入顺序排列的所有元素的值的[value, value]
数组。为了使这个方法和Map
对象保持相似, 每个值的键和值相等。 -
返回一个布尔值,表示该值在
Set
中存在与否。 Set.prototype.forEach(*callbackFn*[, *thisArg*])
按照插入顺序,为 Set 对象中的每一个值调用一次 callBackFn。如果提供了
thisArg
参数,回调中的this
会是这个参数。
处理交集、并集、差集
1 | Object.prototype.toString.call(new Map()) // "[object Map]" |
Map
Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。
常用方法
-
返回键对应的值,如果不存在,则返回 undefined。
-
设置 Map 对象中键的值。返回该 Map 对象。
WeakMap 与 Map
- WeakMap 的 key 只能是对象,Map 的 key 可以是任意类型
- key 被置为 null 时,WeakMap 会被垃圾回收,Map 则不会被垃圾回收。举例如下:
1 | class Test {} |
此处使用 Map,当 my 变成 null 时,newMap 中仍然保留 Test 实例,class Test 仍然在内存中,没被销毁。
1 | class Test {} |
而使用 WeakMap 时,当 my 被置为 null 后,newMap 中 Test 实例也不存在,class Test 被垃圾回收机制销毁了。
3. Reflect
Es6 后续新增的方法放 Reflect 上
Reflect.ownKeys(obj)
获取对象的属性列表
Reflect.apply
我们在源码中经常看到Function.prototype.apply.call
的写法,例如:
1 | function sum(...argvs) { |
因为直接调用函数的 apply 方法没法确保调用的是原生的 apply,可能已经被改写了。所以通过Function.prototype.apply.call
来确保调用到原生的 apply。
可以这样理解 call 的功能:1. 改变 apply 中的 this 为 sum,2. 让 apply 执行
1 | Function.prototype.apply.call(sum, null, [1, 2]) |
Reflect 上也有 apply 方法,并且写法更加简单:
1 | Reflect.apply(sum, null, [1, 2]) |
4. reduce
数组上提供的方法,能够收敛数组,把数组转化成其他类型数据
基本用法
数组不能为空,若为空则 reduce 第二个参数必须填,否则报错。
1 | [1,2,3].reduce((prev, current, index, array) => {}, '') |
reduce 实现
1 | Array.prototype.reduce = function (callback, prev) { |
用 reduce 实现 map
1 | Array.prototype.map = function (cb) { |
用 reduce 实现拍平多维数组 flat
数组有一个自带的flat
方法,能够拍平多维数组。比如:
1 | ;[1, 2, 3, [1, 2, [4]]].flat(2) // [1, 2, 3, 1, 2, 4] |
用 reduce 也能够实现拍平多维数组的功能:
1 | Array.prototype.flat = function (level) { |
用 reduce 实现 compose
compose
即组合函数,用来把多个函数组合起来,常用在中间件中。比如有 3 个函数,sum
,len
,addPrefix
,需要一层层的去调用这三个函数addPrefix(len(sum('a', 'b')))
。我们也可以通过compose(sum, len, addPrefix)
来实现这个功能。
1 | function sum(a, b) { |
通过执行compose(sum, len, addPrefix)
产生一个新函数 fn,再调用fn('a', 'b')
实现 3 个函数的组合调用。
1 | function compose(...fns) { |
为了更好的理解 compose 的实际执行结果,
1 | let fn = compose(addPrefix, len, sum) //等价于 |
5. defineProperty
作用:对 object 的每个属性用此方法定义,可以使用 get 和 set 拦截对象的取值和设置值。
1 | let obj = {}, |
可添加描述符
enumerable
和configurable
都是描述符enumerable
表示 obj 是否可以被 for in 或 Object.keys 枚举configurable
表示 obj 是否可以被删除,例如delete obj.a
使用 set 和 get 时需要借助第三方变量实现对
a
属性的监控缺点:如果要把对象的属性全部转化成 getter + setter,需要遍历所有对象,用 defineProperty 重新定义属性,性能不高
数组采用这种方式,索引很多,性能很差
对象中嵌套对象,需要递归处理
6. proxy
作用:使用 get 和 set 拦截对象属性的取值和设置值。
优点:proxy 是不用改写原对象,不用对 object 的属性进行重新定义,性能高。如果访问到的属性是对象时,再代理即可,不用递归。
缺点:proxy 是 es6 的 api, 但是兼容性差
vue3 中对数据的拦截就改用了 proxy。
常用的 api:
- get:获取属性时触发,例如 proxy.xxx
- set:设置 proxy 属性时触发,proxy.a = 1
- ownKeys:获取 proxy 的 key 触发,Object.getOwnPropertyNames(proxy)或 Reflect.ownKeys 或 Object.keys 都会触发
- deleteProperty:删除 proxy 上的属性时触发,eg. delete proxy.xxx 触发
- has:使用 in 语法时触发,eg. ‘a’ in proxy
1 | let obj = { xxx: 1 } |
7. 深拷贝和浅拷贝
… 展开运算符
… 等价于 Object.assign 都是浅拷贝
实现一个深拷贝
JSON.string + JSON.parse
1
JSON.parse(JSON.stringify({ a: 2, b: undefined }))
缺陷: 正则,函数,日期, undefined 这些类型不支持
递归对象,把对象属性都进行拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function deepClone(obj, hash = new WeakMap()) {
// 记录拷贝后的结果
if (obj == null) return obj
// 正则 日期 函数 set map
if (obj instanceof RegExp) return new RegExp(obj)
if (obj instanceof Date) return new Date(obj)
if (typeof obj !== 'object') return obj
if (hash.has(obj)) return hash.get(obj) // 返回上次拷贝的结果
// 数组 || 对象
let newObj = new obj.constructor()
hash.set(obj, newObj)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 不拷贝原型上的方法
newObj[key] = deepClone(obj[key], hash)
}
}
return newObj
}
其中增加if (hash.has(obj)) return hash.get(obj)
是为了避免循环引用。每次拷贝完对象的属性后都存储在 hash 里,在下次拷贝属性前先看一下是否有,如果有就直接返回。循环引用的测试例子如下:
1 | var obj = {} |