Vue3 中空间换时间的操作

143 阅读4分钟

空间换时间的操作

在 Vue.js 模板的编译过程中,我们已经知道静态提升的好处:针对静态节点不用每次在 render 阶段都执行一次 createVNode 创建 vnode 对象。但它有没有成本呢?当然是有的。因为这是空间换时间的操作,所以需要有额外的空间成本,但是总体来看是收益大于成本的。 此外,我们也需要了解 Vue.js 中其他的一些空间换时间的操作,我希望你能学会这个优化思想并把它运用到自己平时的工作中。

编译优化中的空间换时间操作

  1. hoistStatic 静态提升
  2. 预字符串化
  3. cacheHandler 缓存內联的事件处理函数
  4. v-once, v-memo

reactive API

一旦某个对象经过 reactive API 变成响应式对象后,会使用 WeakMap 的数据结构把响应式结果存储起来。 它的 key 是原始的 Target 对象,值是 Proxy 对象。 这样一来,同样的对象如果再次执行 reactive,则从缓存的 WeakMap 中直接拿到对应的响应式值并返回。

Computed API

它的内部会缓存上次的计算结果 value,而且只有 dirty 为 true 时才会重新计算。 如果访问计算属性时 dirty 为 false,那么直接返回这个 value。

KeepAlive 组件

整个 KeepAlive 组件的设计,本质上就是空间换时间 在 KeepAlive 组件内部,在组件渲染挂载和更新前都会缓存组件的渲染子树 subTree

const cacheSubtree = () => {
  if (pendingCacheKey != null) {
    cache.set(pendingCacheKey, getInnerChild(instance.subTree))
  }
}
onMounted(cacheSubtree)
onUpdated(cacheSubtree)

这个子树一旦被缓存了,在下一次渲染的时候就可以直接从缓存中拿到子树 vnode 以及对应的 DOM 元素来渲染。

工具函数 cacheStringFunction

实现

cacheStringFunction 函数的实现:

const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
  const cache: Record<string, string> = Object.create(null)
  return ((str: string) => {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }) as any
}

在内部定义了 cache 变量做缓存,并返回了一个新函数。 新函数的内部,会先尝试中从缓存中拿数据,如果不存在则执行函数 fn,并把 fn 的返回结果用 cache 缓存,这样下一次就可以命中缓存了。

应用场景

应用场景主要是:字符串变形的相关函数

const camelizeRE = /-(\w)/gexport const camelize = cacheStringFunction(
  (str: string): string => {
    return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
  }
)
​
const hyphenateRE = /\B([A-Z])/gexport const hyphenate = cacheStringFunction((str: string) =>
  str.replace(hyphenateRE, '-$1').toLowerCase()
)
​
export const capitalize = cacheStringFunction(
  (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
)

这样就保证了同样的字符串,在调用某个字符串变形函数后会把结果缓存,然后同一字符串再次执行该函数的时候就能从缓存拿结果了。 :::danger 注意 Vue.js 内部之所以给这些字符串变形函数设计缓存,是因为它们的缓存命中率高,如果缓存命中率低的话,这类空间换时间的缓存设计就可能变成负优化了。 :::

是否会造成内存泄漏?

空间换时间的基本操作都是通过缓存的方式,那这会造成内存泄漏吗?

回答这个问题前,你先要明白什么是内存泄漏:

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

简单点说,内存泄漏就是由于疏忽或错误,导致那些你已经用不到的内存空间没有释放而产生的内存浪费。 而 vue 中空间换时间所设计的缓存,都是需要用到的内存空间,所以算是内存占用,并非内存泄漏。

总结

通过这篇文章,希望大家能做到以下两点:

  1. 学习 Vue.js 编译过程中的一些优化操作,并能思考它为什么能起到优化效果。
  2. 了解优化背后可能会造成的成本,学会评估成本和收益。