空间换时间的操作
在 Vue.js 模板的编译过程中,我们已经知道静态提升的好处:针对静态节点不用每次在 render 阶段都执行一次 createVNode 创建 vnode 对象。但它有没有成本呢?当然是有的。因为这是空间换时间的操作,所以需要有额外的空间成本,但是总体来看是收益大于成本的。 此外,我们也需要了解 Vue.js 中其他的一些空间换时间的操作,我希望你能学会这个优化思想并把它运用到自己平时的工作中。
编译优化中的空间换时间操作
- hoistStatic 静态提升
- 预字符串化
- cacheHandler 缓存內联的事件处理函数
- 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)/g
export const camelize = cacheStringFunction(
(str: string): string => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
}
)
const hyphenateRE = /\B([A-Z])/g
export 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 中空间换时间所设计的缓存,都是需要用到的内存空间,所以算是内存占用,并非内存泄漏。
总结
通过这篇文章,希望大家能做到以下两点:
- 学习 Vue.js 编译过程中的一些优化操作,并能思考它为什么能起到优化效果。
- 了解优化背后可能会造成的成本,学会评估成本和收益。