Day 19. slate × Operation × WeakMap

https://ithelp.ithome.com.tw/upload/images/20211004/20139359TwQRUR0Kp7.png

在正式开始介绍 Operation 的内容之前,先让我们花一点篇幅来介绍一下『 WeakMap 』这个感觉有点偏冷门的主题。

即便对它不熟悉的读者们,第一眼看到它的名字应该也能马上与 JS ES6 的 Map 联想在一块儿。

它们其实是非常相似的东西,使用与操作上的逻辑也差不多,重点就是差在那个『 Weak 』字而已。

What & Why WeakMap


预设读者对 Map 有一定基础的认识下,让我们直接列举出 Map 与 WeakMap 两者之间的差异:

  • Map instance 的 key 可以是任何 type ,但 WeakMap instance 中的 key 只能是 object type

  • WeakMap instance 里面引用的 key object ,必须在其外部存在其他的 references

    当外部没有其他引用 WeakMap 里特定 key object 的 reference 时,该项 key value pair 会被 Garbage Collection 清除出 WeakMap 里。

    https://ithelp.ithome.com.tw/upload/images/20211004/20139359LfLZ4wJWRZ.png

  • WeakMap instance 是 Non-enumerable (不可列举的)

    在 Javascript 里提供的 Map api 包含: entriesforEachkeysvalues 这四种 methods 让开发者能够列举一个 Map instance 里的资料,但在 WeakMap api 里却不存在着这些 methods 。

了解了 WeakMap 与他的兄长之间的差异以後,我们当然也要懂 WeakMap 存在的价值,依照 MDN 对 WeakMap 的解释我们可以列出两点使用 『 Map 』 时会遇到的问题:

  1. 当我们使用 set api 去新增新的 key value pair 进 Map 里头时, Map 会将这个新安插的资料 push 进整个 Map instance 资料的最尾端,这使得我们使用 get methods 查询 Map instance 里的资料时,都会因为需要遍历整个 Map list 而导致 O( n ) 的时间复杂度( n 是 key value pair 的个数)。
  2. 过度使用很容易造成 memory leak ,因为 Map 会无期限地确保对 instance 内所有 key 与 value 值的 references ,这导致了里头的 key 与 value 都不会被 GC 掉,就算在外部已经完全没有 reference 了也一样。

一个最直接理解 WeakMap 的方式就是:它提供给开发者一个『延伸物件储存资料的方式』。很多时候我们在设计与封装完一组 Object ,或甚至它的来源是从第三方套件取得的资料时,会希望能延伸出这组 Object 关联到的资料。此时就是 WeakMap 登场的时机了!

Slate Document tree 的特性使它尤其适合搭配 WeakMap 的使用,因为 tree 里头的任意一个节点与路径都有机会因为编辑器的更新而被当作过期的资料取消对它的关联,为了让它们能在正确的时机点被顺利地 GC 掉,透过 WeakMap 来做扩充标记就是一个很好的选择。

接着就来看看 Slate 里头有哪些使用 WeakMaps 的情境吧!

The WeakMaps In Slate


首先是以『 Cache 』的方式来使用它,这同时也是 WeakMap 最普遍被使用的方式了,基本上只要是有使用 WeakMap 的地方几乎都能看到这种用法的影子。

这边我们要举的范例是:interfaces/editor.ts Editor 里的 isEditor

isEditor 是 Editor 里的 method apis 里用来判断传入的 value 是否为 Editor 。

  1. 它首先从 IS_EDITOR_CACHE 的 WeakMap instance 中寻找 value key 的 value
  2. 如果回传的资料,也就是 cachedIsEditor 不为 undefined 则代表已对这个 value 做过缓存,直接 return 缓存的结果即可
  3. 若为 undefined 则重新进行一次 type check 并将结果( isEditor )存入以 value 作为 key 的 IS_EDITOR_CACHE WeakMap instance 里。

当我们将 value 的结果存入缓存後,外部对於 value 这个 object 的 reference 遭到拔除以後, IS_EDITOR_CACHE 也会将 value 的 key value pair 丢给 Garbage Collection 做清理。

const IS_EDITOR_CACHE = new WeakMap<object, boolean>()

export const Editor: EditorInterface = {
	// ...,
	isEditor(value: any): value is Editor {
		// ...
		const cachedIsEditor = IS_EDITOR_CACHE.get(value)
    if (cachedIsEditor !== undefined) {
      return cachedIsEditor
    }
		const isEditor = // Editor type checks
		IS_EDITOR_CACHE.set(value, isEditor)
    return isEditor
	},
}

再来就是我们在介绍 Ref concept 时也有提到过的『扩充标记』了, Slate 将这种类型的功能统一整理在一个叫 weak-maps.ts 的 file 里。在 slate-react package 里我们也能看到 weak-maps file 里统整了一系列将 Slate nodes 关联到 HTML DOM nodes 的 WeakMap list :

/**
 * Weak maps that allow us to go between Slate nodes and DOM nodes. These
 * are used to resolve DOM event-related logic into Slate actions.
 */
export const EDITOR_TO_WINDOW: WeakMap<Editor, Window> = new WeakMap()
export const EDITOR_TO_ELEMENT: WeakMap<Editor, HTMLElement> = new WeakMap()
export const EDITOR_TO_PLACEHOLDER: WeakMap<Editor, string> = new WeakMap()
export const ELEMENT_TO_NODE: WeakMap<HTMLElement, Node> = new WeakMap()
export const KEY_TO_ELEMENT: WeakMap<Key, HTMLElement> = new WeakMap()
export const NODE_TO_ELEMENT: WeakMap<Node, HTMLElement> = new WeakMap()
export const NODE_TO_KEY: WeakMap<Node, Key> = new WeakMap()

回到 slate package 的 weak-maps.ts file 也能看到一系列对於 Editor 的扩充标记,除了在 Day 14 提到过的 REFS WeakMap 之外,其余全部都被 Slate 应用在 Operation 相关的功能

export const DIRTY_PATHS: WeakMap<Editor, Path[]> = new WeakMap()
export const FLUSHING: WeakMap<Editor, boolean> = new WeakMap()
export const NORMALIZING: WeakMap<Editor, boolean> = new WeakMap()
export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap()
export const POINT_REFS: WeakMap<Editor, Set<PointRef>> = new WeakMap()
export const RANGE_REFS: WeakMap<Editor, Set<RangeRef>> = new WeakMap()

如果是位於 tree 里头的节点诸如 ElementText 透过 WeakMaps 进行标记我觉得合理,毕竟它们就是被设计成会被频繁更改的。但在 slate package 里 WeakMaps 的 key 存放的都是 Editor ,它身为整个 tree 的根节点应该不太需要考虑 GC 的问题吧?怎麽还需要刻意透过 WeakMaps 来存取它的资料呢?


如果有这样的想法的话就代表思考的方向已经违反了 Slate 的 principles 之一,也就是不会去预测开发者对编辑器亿的使用情境了。

确实位於 Slate node tree 中的根节点会是 Editor 没错,但我们却不应该去预测使用者不会在子层再放入另一个 Editor 作为 child node 。

Slate 视一切 Node 为『可被 Garbage Collect 的 unique object reference』,而 Node type 的定义为 Node = Editor | Element | Text ,在这样的前提下 Editor 也应当要被视为了可被 GC 的 unique object 。


介绍完 WeakMaps 後我们接着就要正式开始介绍 Operations 了!下一篇我们一样会从 Interface 篇章被我们略过的 interfaces/operation.ts 开始介绍起。

那麽我们就明天再见罗~


<<:  [Day 22] Python 视觉化解释数据 - Plotly Express

>>:  【Day19】Git 版本控制 - 多人协作 GitHub Flow

Day3-安装JDK

前言 提到JDK就不得不提到JRE了,先来介绍他们之间的差别吧。 JRE:Java Runtime ...

Day5 被动情蒐(2)-dig、fierce、DNSenum、DNSrecon、Sublist3r、dnsdumpster

DNS 工具:dig Domain Information Groper 一样可以检测 DNS 服...

第 17 集:Bootstrap 客制化 Sass 官网资源

此篇会用好理解 (好笑) 的方式导览官网、原始码如何阅读,详细介绍会放在往後的文章。 官网 英文官...

xampp 多个网站 必须重启I-040GW 才可连上 浮动IP no-ip

各位前辈好 这个问题困扰我一年多了,真的找不到问题点所以提出 我的问题跟这位很像 我的原先设定是 使...

DAY30 MongoDB 使用经验分享 & 完赛

DAY30 MongoDB 使用经验分享 & 完赛 终於来到铁人赛的最後一天了,今天不讲太多...