最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 大多数人没用过的 WeakMap / WeakSet 有啥用

    正文概述 掘金(坂有桑)   2021-04-09   654

    上一篇文章 《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 判断一个元素是否存在于集合中

    adddelete 都是对 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

    两文讨论的东西大相径庭,其实想表达的意思却是一致的。对于一些语言特性亦或者第三方库,死背接口、甚至于一句一句地去扒源码,并不能让人用好它,理解这些接口背后的设计意图才是关键。

    下一篇,打算整点写代码的东西,如果大家觉得本文还有点内容,还请高抬贵手点个赞哈。


    起源地下载网 » 大多数人没用过的 WeakMap / WeakSet 有啥用

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元