Hello,各位小伙伴,接下来的一段时间里,我会把我的课程《Vue.js 3.0 核心源码解析》中问题的答案陆续在我的公众号发布,由于课程的问题大多数都是开放性的问题,所以我的答案也不一定是标准的,仅供你参考喔。

本期的问题:在 Vue.js 模板的编译过程中,我们已经知道静态提升的好处:针对静态节点不用每次在 render 阶段都执行一次 createVNode 创建 vnode 对象。但它有没有成本呢?为什么?

在回答问题前,我们简单回顾一下什么是静态提升,假设我们有如下模板:

<p>hello {{ msg }}p><p>staticp>

在开启 hoistStatic 编译配置的情况下最终编译结果如下:

import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "static", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(), _createBlock(_Fragment, null, [    _createVNode("p", null, "hello " + _toDisplayString(_ctx.msg), 1 /* TEXT */),    _hoisted_1  ], 64 /* STABLE_FRAGMENT */))}

我们发现静态节点

static

编译生成 vnode 的过程被提取到 render 函数外面了,然后在 render 函数内部就可以直接拿到静态节点的编译结果 _hoisted_1

之所以可以这么做,是因为静态节点是不会改变的,所以它编译生成的 vnode 也不会改变,而动态节点是变化的,必须在每次 render 的时候动态创建,它的 vnode生成过程就不能提取到外面。

显然,这样做的好处是由于 render 函数在每次组件重新渲染的时候都会执行,而针对静态节点,创建 vnode 的过程只执行一次,相当于提升了 render 的性能。

但是这么做也是有成本的,创建的 hoisted_1 vnode 对象始终会在内存中占用,并不会在每次 render 函数执行后释放。

其实,这就是典型的空间换时间的做法,在绝大部分的场景,性能好意味着更好的用户体验,而牺牲那一点内存空间完全是可接受的,对于用户也是无感知的,所以空间换时间是常见的一种优化手段。

在整个 Vue.js 源码内部,经常可以见到这种空间换时间的操作,接下来我们就来看几个 Vue.js 中常见的空间换时间的操作。

Vue.js 中常见的空间换时间操作

  • reactive API

Vue.js 3.0 使用 Proxy API 把对象变成响应式,一旦某个对象经过 reactive API 变成响应式对象后,会把响应式结果存储起来,大致如下:

function createReactiveObject(  target: Target,  isReadonly: boolean,  baseHandlers: ProxyHandler<any>,  collectionHandlers: ProxyHandler<any>) {  // ...  const proxyMap = isReadonly ? readonlyMap : reactiveMap  const existingProxy = proxyMap.get(target)  if (existingProxy) {    return existingProxy  }  // ...  const proxy = new Proxy(    target,    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers  )  proxyMap.set(target, proxy)  return proxy}

在整个响应式模块的内部,使用了 WeakMap 的数据结构存储响应式结果,它的 key 是原始的 Target 对象,值是 Proxy 对象。

export const reactiveMap = new WeakMapany>()export const readonlyMap = new WeakMapany>()

这样一来,同样的对象如果再次执行 reactive,则从缓存的 proxyMap 中直接拿到对应的响应式值并返回。

  • KeepAlive 组件

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

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

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

KeepAlive 具体实现细节我在课程中有专门的一小节课说明,这里就不多赘述了。

  • 工具函数 cacheStringFunction

Vue.js 源码内部的一些工具函数的实现,也利用了空间换时间的思想,比如 cacheStringFunction 函数,如下:

const cacheStringFunction = 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}

cacheStringFunction 函数的实现很简单,内部定义了 cache 变量做缓存,并返回了一个新的函数。

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

我们来看看 cacheStringFunction 的几个应用场景:

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))

可以看到,这些字符串变形的相关函数都使用了 cacheStringFunction,这样就保证了同样的字符串,在调用某个字符串变形函数后会把结果缓存,然后同一字符串再次执行该函数的时候就能从缓存拿结果了。

注意,Vue.js 内部之所以给这些字符串变形函数设计缓存,是因为它们的缓存命中率高,如果缓存命中率低的话,这类空间换时间的缓存设计就可能变成负优化了。

会造成内存泄漏吗

看到这里,你可能会有疑惑,空间换时间的基本操作都是通过缓存的方式,那这会造成内存泄漏吗?

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

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

简单点说,内存泄漏就是那些你已经用不到的内存空间,由于没有释放而产生的内存浪费。

而我们空间换时间所设计的缓存,都是需要用到的内存空间,所以算是内存占用,并非内存泄漏。

关于使用 Vue.js 开发工作中可能会造成内存泄漏的场景,我在前几篇文章中提到了,如果你还不了解,建议你去看一看。

总结

综上,我们了解到 Vue.js 在编译过程中使用静态提升并非无成本,但是总体来看收益大于成本。此外,我们也了解 Vue.js 中一些空间换时间的操作,我希望你能学会这个优化思想并把它运用到自己平时的工作中。

我出这个题主要是希望你能做到以下两点:

  1. 学习 Vue.js 编译过程中的一些优化操作,并能思考它为什么能起到优化效果。

  2. 了解优化背后可能会造成的成本,学会评估成本和收益。

要记住,分析和思考的过程远比答案重要。

ie浏览器查看vue中js_浅析 Vue.js 中那些空间换时间的操作相关推荐

  1. js base64 php,php中的base64decode 与js中的互相转换

    php中的base64decode 与js中的相互转换 function utf16to8(str) { var out, i, len, c; out = ""; len = s ...

  2. Vue中foreach数组与js中遍历数组的写法

    场景 Vue中使用Axios发送get或者post请求,发送请求时需要在js中 对请求参数进行遍历并处理. 接收响应时需要对响应结果进行遍历和处理. 注意区分在vue和js中foreach数组的区别. ...

  3. vue watch 修改滚动条_Vue.js 中滚动条始终定位在底部的方法

    Vue.js 中滚动条始终定位在底部的方法 发布于 2020-2-23| 复制链接 分享一篇关于vue 中滚动条始终定位在底部的方法,具有很好的参考价值,希望对大家有所帮助.一起跟随小妖过来看看吧 滚 ...

  4. js去el的map_转:el表达式获取map对象的内容 js中使用el表达式 js 中使用jstl 实现 session.removeattribute...

    原文链接: 总结: el表达式获取map对象的内容 后端: HashMap map1 = new HashMap(); map1.put("key1","lzsb&quo ...

  5. php中的foreach和js中的foreach的用法和区别

    PHP中的foreach循环: 主要用于遍历数组 例如: (1)// $colors=array("red","yellow","blue" ...

  6. JavaWeb中外部引入的js中涉及到location.href的页面跳转/jsp页面的外部js文件引入

    当js在jsp页面中写的时候没有任何问题如下所示: function deleteP(obj){if(confirm("你真舍得丢弃我吗?")){//发送请求location.hr ...

  7. ajax中的trim方法,js中trim函数实例参考

    在调用trim()的js方法上加入如下js代码: 复制代码 代码示例: string.prototype.trim = function(){ return this.replace(/(^/s*)| ...

  8. 组件用.vue还是.js_一个Vue.js 2.0组件,用于生成首字母缩写或基于图像的头像

    组件用.vue还是.js Vue头像组件 (vue-avatar-component) This vue.js component provide a simple way to generate r ...

  9. vue + tone.js_用Tone.js和Vue.js构建的鼓合成器/音序器

    vue + tone.js 循环器 (looperator) Looperator is a super cool browser based drum machine / sequencer. It ...

最新文章

  1. 田志刚:所有大公司都是小公司(二)
  2. 在对linux系统分区进行格式化时需要对磁盘簇(或i节点密度)的大小进行选择,请说明选择的原则。
  3. u-boot分析之命令实现(四)
  4. Greys Java在线问题诊断工具
  5. 沉淀2017,勇闯2018
  6. 2020CCPC长春
  7. Win11怎么打开以前的word文档
  8. Veritas面向OpenStack推出全新软件定义存储解决方案
  9. iPhone质量成迷?被吴彦祖一箭射穿,却还能开机
  10. 设计灵感|大火C4D立体字效在海报中的应用
  11. 【网络安全面试题】——如何渗透测试文件目录穿越
  12. Q99:当Bezier曲面(Utah Teapot)同时遇上“噪声纹理”和“Phong反射模型”
  13. 程序员这样面试,拿到offer的几率是非常大
  14. maven打包失败:自定义项目工具类打包给其他微服务使用
  15. 西游记与面向对象编程
  16. 181218每日一句
  17. Halcon软件安装教程
  18. ANSYS win10家庭版安装经验:
  19. 试题管理小能手,免费下载单机软件-题库管家
  20. Footprint 8月月报 DeFi市场多点开花,9月是否会迎来下一个爆发点

热门文章

  1. python opencv 界面按钮_如何使用Python构建简单的UI?
  2. 学python能做什么-学了Python都能干什么,哪个最赚钱?
  3. python必背代码-Python高手必修课:如何让 Python 代码更易读,推荐收藏
  4. 学python可以做什么职业-python学完之后比较适合哪些职业工作呢?
  5. iOS中 语音识别功能/语音转文字教程具体解释 韩俊强的博客
  6. 解密车载语音识别架构 车载系统能听懂人说话?
  7. 小米电视4A核心技术之语音识别浅析
  8. grafana zabbix 模板_【Grafana教程】安装Grafana并配置Zabbix数据源
  9. python爬虫中for循环无法每一段输出_Python入门到掌握只需要这3大,4类,5大,6种,即可,附教程...
  10. JavaScript -- DOM树