vue3编译做了哪些优化

vue3编译优化有:1、引入了 patchFlag,用来标记动态内容;在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速diff算法。2、Block Tree。3、静态提升,是将静态的节点或者属性提升出去。4、预解析字符串化,当连续静态节点超过10个时,会将静态节点序列化为字符串。5、函数缓存;开启cacheHandlers选项后,函数会被缓存起来,后续可直接使用。

vue3编译做了哪些优化

本教程操作环境:windows7系统、vue3版,DELL G3电脑。

本文主要来分析 Vue3.0 编译阶段做的优化,在 patch 阶段是如何利用这些优化策略来减少比对次数。由于组件更新时依然需要遍历该组件的整个 vnode 树,比如下面这个模板:

  
    

static text

    

static text

    

{{ message }}

    

static text

    

static text

  

登录后复制

整个 diff 过程如图所示:

1.png

可以看到,因为这段代码中只有一个动态节点,所以这里有很多 diff 和遍历其实都是不需要的,这就会导致 vnode 的性能跟模版大小正相关,跟动态节点的数量无关,当一些组件的整个模版内只有少量动态节点时,这些遍历都是性能的浪费。对于上述例子,理想状态只需要 diff 这个绑定 message 动态节点的 p 标签即可。

立即学习“前端免费学习笔记(深入)”;

Vue.js 3.0 通过编译阶段对静态模板的分析,编译生成了 Block tree。

Block tree 是一个将模板基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 Array 来追踪自身包含的动态节点。借助 Block tree,Vue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破。

PatchFlag

由于 diff 算法无法避免新旧虚拟 DOM 中无用的比较操作,Vue.js 3.0 引入了 patchFlag,用来标记动态内容。在编译过程中会根据不同的属性类型打上不同的标识,从而实现了快速 diff 算法。PatchFlags 的所有枚举类型如下所示:

export const enum PatchFlags {  TEXT = 1, // 动态文本节点  CLASS = 1 

Block Tree

2.png

左侧的 template 经过编译后会生成右侧的 render 函数,里面有 _openBlock、_createElementBlock、_toDisplayString、_createElementVNode(createVnode) 等辅助函数。

let currentBlock = nullfunction _openBlock() {  currentBlock = [] // 用一个数组来收集多个动态节点}function _createElementBlock(type, props, children, patchFlag) {  return setupBlock(createVnode(type, props, children, patchFlag));}export function createVnode(type, props, children = null, patchFlag = 0) {  const vnode = {    type,    props,    children,    el: null, // 虚拟节点上对应的真实节点,后续diff算法    key: props?.["key"],    __v_isVnode: true,    shapeFlag,    patchFlag   };  ...  if (currentBlock && vnode.patchFlag > 0) {    currentBlock.push(vnode);  }  return vnode;}function setupBlock(vnode) {  vnode.dynamicChildren = currentBlock;  currentBlock = null;  return vnode;}function _toDisplayString(val) {  return isString(val)    ? val    : val == null    ? ""    : isObject(val)    ? JSON.stringify(val)    : String(val);}

登录后复制

此时生成的 vnode 如下:

3.png

此时生成的虚拟节点多出一个 dynamicChildren 属性,里面收集了动态节点 span。

节点 diff 优化策略:

我们之前分析过,在 patch 阶段更新节点元素的时候,会执行 patchElement 函数,我们再来回顾一下它的实现:

const patchElement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子  let el = n2.el = n1.el;  let oldProps = n1.props || {}; // 对象  let newProps = n2.props || {}; // 对象  patchProps(oldProps, newProps, el);  if (n2.dynamicChildren) { // 只比较动态元素    patchBlockChildren(n1, n2);  } else {    patchChildren(n1, n2, el); // 全量 diff  }}

登录后复制

我们在前面组件更新的章节分析过这个流程,在分析子节点更新的部分,当时并没有考虑到优化的场景,所以只分析了全量比对更新的场景。

而实际上,如果这个 vnode 是一个 Block vnode,那么我们不用去通过 patchChildren 全量比对,只需要通过 patchBlockChildren 去比对并更新 Block 中的动态子节点即可。由此可以看出性能被大幅度提升,从 tree 级别的比对,变成了线性结构比对。

我们来看一下它的实现:

const patchBlockChildren = (n1, n2) => {  for (let i = 0; i 

属性 diff 优化策略:

接下来我们看一下属性比对的优化策略:

const patchElement = (n1, n2) => { // 先复用节点、在比较属性、在比较儿子  let el = n2.el = n1.el;  let oldProps = n1.props || {}; // 对象  let newProps = n2.props || {}; // 对象  let { patchFlag, dynamicChildren } = n2    if (patchFlag > 0) {    if (patchFlag & PatchFlags.FULL_PROPS) { // 对所 props 都进行比较更新      patchProps(el, n2, oldProps, newProps, ...)    } else {      // 存在动态 class 属性时      if (patchFlag & PatchFlags.CLASS) {        if (oldProps.class !== newProps.class) {          hostPatchProp(el, 'class', null, newProps.class, ...)        }      }      // 存在动态 style 属性时      if (patchFlag & PatchFlags.STYLE) {        hostPatchProp(el, 'style', oldProps.style, newProps.style, ...)      }            // 针对除了 style、class 的 props      if (patchFlag & PatchFlags.PROPS) {        const propsToUpdate = n2.dynamicProps!        for (let i = 0; i < propsToUpdate.length; i++) {          const key = propsToUpdate[i]          const prev = oldProps[key]          const next = newProps[key]          if (next !== prev) {            hostPatchProp(el, key, prev, next, ...)          }        }      }      if (patchFlag & PatchFlags.TEXT) { // 存在动态文本        if (n1.children !== n2.children) {          hostSetElementText(el, n2.children as string)        }      }     } else if (dynamicChildren == null) {      patchProps(el, n2, oldProps, newProps, ...)    }  }}function hostPatchProp(el, key, prevValue, nextValue) {  if (key === 'class') { // 更新 class     patchClass(el, nextValue)  } else if (key === 'style') { // 更新 style    patchStyle(el, prevValue, nextValue)  } else if (/^on[^a-z]/.test(key)) {  // events  addEventListener    patchEvent(el, key, nextValue);  } else { // 普通属性 el.setAttribute    patchAttr(el, key, nextValue);  }}function patchClass(el, nextValue) {  if (nextValue == null) {    el.removeAttribute('class'); // 如果不需要class直接移除  } else {    el.className = nextValue  }}function patchStyle(el, prevValue, nextValue = {}){  ...}function patchAttr(el, key, nextValue){  ...}

登录后复制

总结: vue3 会充分利用 patchFlag 和 dynamicChildren 做优化。如果确定只是某个局部的变动,比如 style 改变,那么只会调用 hostPatchProp 并传入对应的参数 style 做特定的更新(靶向更新);如果有 dynamicChildren,会执行 patchBlockChildren 做对比更新,不会每次都对 props 和子节点进行全量的对比更新。图解如下:

4.png

静态提升

静态提升是将静态的节点或者属性提升出去,假设有以下模板:

  hello   {{name}}  {{age}}

登录后复制

编译生成的 render 函数如下:

export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(), _createElementBlock("div", null, [    _createElementVNode("span", null, "hello"),    _createElementVNode("span", {      a: "1",      b: "2"    }, _toDisplayString(_ctx.name), 1 /* TEXT */),    _createElementVNode("a", null, [      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */)    ])  ]))}

登录后复制

我们把模板编译成 render 函数是这个酱紫的,那么问题就是每次调用 render 函数都要重新创建虚拟节点。

开启静态提升 hoistStatic 选项后

const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "hello", -1 /* HOISTED */)const _hoisted_2 = {  a: "1",  b: "2"}export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(), _createElementBlock("div", null, [    _hoisted_1,    _createElementVNode("span", _hoisted_2, _toDisplayString(_ctx.name), 1 /* TEXT */),    _createElementVNode("a", null, [      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */)    ])  ]))}

登录后复制

预解析字符串化

静态提升的节点都是静态的,我们可以将提升出来的节点字符串化。 当连续静态节点超过 10 个时,会将静态节点序列化为字符串。

假如有如下模板:

  static  static  static  static  static  static  static  static  static  static

登录后复制

开启静态提升 hoistStatic 选项后

const _hoisted_1 = /*#__PURE__*/_createStaticVNode("staticstaticstaticstaticstaticstaticstaticstaticstaticstatic", 10)const _hoisted_11 = [  _hoisted_1]export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(), _createElementBlock("div", null, _hoisted_11))}

登录后复制

函数缓存

假如有如下模板:

 v = event.target.value">

登录后复制

编译后:

const _hoisted_1 = ["onClick"]export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(), _createElementBlock("div", {    onClick: event => _ctx.v = event.target.value  }, null, 8 /* PROPS */, _hoisted_1))}

登录后复制

每次调用 render 的时候要创建新函数,开启函数缓存 cacheHandlers 选项后,函数会被缓存起来,后续可以直接使用

export function render(_ctx, _cache, $props, $setup, $data, $options) {  return (_openBlock(), _createElementBlock("div", {    onClick: _cache[0] || (_cache[0] = event => _ctx.v = event.target.value)  }))}

登录后复制

总结

以上几点即为 Vuejs 在编译阶段做的优化,基于上面几点,Vuejs 在 patch 过程中极大地提高了性能。

【相关推荐:vuejs视频教程、web前端开发】

以上就是vue3编译做了哪些优化的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2923907.html

(0)
上一篇 2025年3月11日 18:52:05
下一篇 2025年2月21日 13:16:42

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • vue页面有哪些组成

    vue页面有3个组成部分:1、模板(template),即template标签包裹的界面展示代码(HTML代码);2、script标签包裹的业务实现代码(js脚本代码);3、style标签包裹的界面样式代码(css样式代码)。 本教程操作环…

    2025年3月11日
    200
  • vue vw是什么

    在vue中,vw是一种视口单位,是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽;vw始终是针对窗口的宽。vh和vw单位主要用于页面视口大小布局,可以根据电脑浏览器自适应,在页面布局上更加方便简单。 本教程操作环…

    2025年3月11日
    200
  • vue渲染函数使用哪个命令

    vue渲染函数使用“render”命令。vue中是使用模板HTML语法组建页面的,使用render函数可以用js语言来构建DOM。因为vue是虚拟DOM,所以在拿到template模板时也要转译成VNode的函数,而用render()函数构…

    2025年3月11日 编程技术
    200
  • vue初始化都做什么

    做的事:1、选项合并,处理组件的配置内容;2、初始化vue实例生命周期相关的属性;3、初始化自定义组件事件的监听;4、初始化render渲染所需的slots、渲染函数等;5、调用beforeCreate函数;6、初始化注入数据;7、对pro…

    2025年3月11日
    200
  • vue实现双向绑定有哪几个方法

    vue实现双向绑定的方法:1、利用v-model指令实现绑定,自定义组件上的v-model相当于传递了modelValue prop并接收抛出的update:modelValue事件;2、利用vue-better-sync插件实现绑定;3、…

    2025年3月11日
    200
  • React父组件怎么调用子组件的方法

    调用方法:1、类组件中的调用可以利用React.createRef()、ref的函数式声明或props自定义onRef属性来实现;2、函数组件、Hook组件中的调用可以利用useImperativeHandle或forwardRef抛出子组…

    2025年3月11日
    200
  • web前端与前端有什么区别

    前端是一个泛指,范畴更广泛,web前端属于web开发的前端,是一个明确的范围,方向更明确;前端开发包括了移动前端开发和web前端开发,即包括了PC端开发和移动端领域的开发,而web前端开发主要指传统的PC端网页开发,这是它们之间最大的区别。…

    2025年3月11日
    200
  • Web3.0来了,它对前端友好吗?

    最近 web3.0 的呼声真的是越来越高,也越来越疯狂。对于我们前端来说,我们需要具备什么技术呢?它对前端友好吗?下面本篇文章就来带大家聊聊,希望对大家有所帮助! 首先先介绍一下 web3.0 是如何衍生的 互联网 我们先聊一下啥是web,…

    2025年3月11日
    200
  • 实例详解如何给轮播图做自适应的高度

    本篇文章给大家带来了关于前端的相关知识,其中主要介绍了怎么给轮播图做一个自适应的高度,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。 不知道大家有没有遇到这样的需求或者说看到类似的效果,就是列表进去详情看轮播图的时候,当手指滚动轮播图时…

    2025年3月11日 编程技术
    200
  • 总结5个前端开发中很棒的资源

    本篇文章给大家带来了关于前端开发的相关知识,其中主要跟大家分享前端开发中5个很赞的资源,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。 Web 开发社区非常庞大,要找到可靠且准确的资源让人觉得慌。幸运的是,我们的开发者已经为你收集了 &…

    2025年3月11日
    200

发表回复

登录后才能评论