上一篇文章 《JavaScript 面试题外的【this】》中的承诺如期而至。
在聊 WeakSet
之前,我们还是先温习一下 Set
这个相对简单的数据结构。
它提供了如下的接口:
Set.prototype.add
在集合中增加一个元素Set.prototype.delete
在集合中删除一个元素Set.prototype.has
判断一个元素是否存在于集合中Set.prototype.clear
清空集合Set.prototype.entries/forEach/values
以各种不同的方式遍历这个集合
再拎出来 WeakSet 对比一下
首先,WeakSet 里面的引用是弱引用。对于这点,mdn 内也有详尽的描述。
也正由于不可枚举,所以在 Set
中涉及到遍历的方法在 WeakSet
均不存在。于是, WeakSet 的接口也就设下这么几个
WeakSet.prototype.add
在集合中增加一个元素WeakSet.prototype.delete
在集合中删除一个元素WeakSet.prototype.has
判断一个元素是否存在于集合中
add
和 delete
都是对 WeakSet
内部进行修改的接口,真正有用的接口实质上就只有判断元素是否存在于集合中 —— has
这一个。
这就让 WeakSet
显得很鸡肋。 其实我们用集合类型的数据结构,其实最多的操作还是用来遍历,无论是 Array
还是 Set
。
我们用 Set
而不用 Array
,是因为场景需要一个无序且值唯一的集合。而我们本以为 WeakSet
可以用在解决需要一个无序、值为一且弱引用的集合的场景。然而结果却大相径庭,这货根本无法遍历,怎么能当作集合来用呢。
如果有心翻 mdn 的话,其实能够看到一个小插曲 —— 一个被废弃的接口 WeakSet.prototype.clear
很明显类似于 Set.prototype.clear
这是一个用来清空的接口,没什么特别的。被废弃了也没啥影响,因为所有你可以用到 clear
的情况,都可以通过 new WeakSet()
以创建一个新的实例的方式进行代替。
但是有意思的是这句
没错,这个接口原本包含在 ES6 中,并且也已经被实现了,却还是被遗弃了。至于原因,其实上面对弱引用的描述已经初现端倪了。
连存储当前对象的列表都没有当然也就没有办法“清空”了。同时也印证了我们之前的判断 —— 这个东西被设计出来根本不是如同 Set 一样,它并不是被当作一个特殊场景下的集合来用的。
在继续讨论 WeakSet 到底是用来做啥之前,再讨论一个挺有意思的事情。
有些人看到弱引用,就想到垃圾回收,进而开始研究起 js 引擎来,想到要实现这么厉害的功能,必定是改了很多底层的架构啥的……
然而有趣的是,在 WeakSet 刚出的时候就已经有 polyfill 能够模拟这个功能了。
写几行代码表示一下原理……
export class WeakSet {
#id = ''
constructor() {
this.#id = Symbol()
}
add(obj) {
Object.defineProperty(obj, this.#id, {
enumable: false,
configurable: true,
})
}
delete(obj) {
if (obj.hasOwnProperty(this.#id)) {
delete obj[this.#id]
}
}
has(obj) {
return obj.hasOwnProperty(this.#id)
}
clear(){
this.#id = Symbol()
}
}
简而言之,就直接在对象上创建一个无法枚举的属性表示这个元素是否存在于当前 WeakSet
内。
如果是 WeakMap
的话,就直接把对应的值写入到这个属性内就可以了。
这个 polyfill 让事情变得更有意思了。它意味着 WeakMap
WeakSet
拥有一个等价的写法。
// 很简单的一个类,拥有一个 val 的私有属性,和对应的 set/get
class Foo {
#val = null
setVal(v) { this.#val = v }
getVal(v) { return this.#val }
}
// 通过 WeakMap,讲 val 从对象内部拆分出来,对应的 set/get 也变成了静态方法
class Bar {
static map = new WeakMap()
static getVal(obj) {
if( Bar.map.has(obj)){
return Bar.map.get(obj)
}else{
return null
}
}
static setVal(obj,v){
Bar.map.set(obj,v)
}
}
这个等价写法说明我们用到某个 WeakMap
的地方,可以直接把其当作一个属性写到这个对象上面去,反之也意味着对于一个对象上的某个属性,都可以通过 WeakMap
拆分到外部。
而这就是 WeakMap
WeakSet
的用途。
值写在对象上,代码又不是不能跑,何必费力气拆分到外部,还要专门整一个特殊的数据结构实现这种写法?
能跑不代表写得好!!!
写业务的时候或许遇到的比较少,其实如果写一些架构模块不难发现这种场景:
我需要对一些对象进行一些拓展,譬如记录一些 log 信息,你可以在原来封装好的类里面加属性,加接口。但带来的麻烦是,类会变得越来越臃肿,你也没法保证其他模块以后不会用到这些东西,影响你这个功能的重构。更何况有些对象来自于第三方库,也没法改。
当然,不去修改类,直接加字段也不是不行,但这种破坏封装性的代码有时候会带来意想不到的情况,比如传来一个被 freeze 的对象。
使用 WeakMap
WeakSet
,既能不破坏原有代码的封装,又能控制这些数据的可见性,何乐而不为呢?
上一篇文章讨论的广为人知的 this
,这一篇文章讨论的鲜为人知的 WeakMap
WeakSet
两文讨论的东西大相径庭,其实想表达的意思却是一致的。对于一些语言特性亦或者第三方库,死背接口、甚至于一句一句地去扒源码,并不能让人用好它,理解这些接口背后的设计意图才是关键。
下一篇,打算整点写代码的东西,如果大家觉得本文还有点内容,还请高抬贵手点个赞哈。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!