iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > 其他 >vue3编译做了哪些优化
  • 872
分享到

vue3编译做了哪些优化

前端Vue.js 2023-05-14 21:05:35 872人浏览 安东尼
摘要

本教程操作环境:windows7系统、vue3版,DELL G3电脑。本文主要来分析 Vue3.0 编译阶段做的优化,在 patch 阶段是如何利用这些优化策略来减少比对次数。 由于组件更新时依然需要遍历该组件的整个 vnode 树,比如下

vue3编译做了哪些优化

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

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

<template>
  <div id="container">
    <p class="text">static text</p>
    <p class="text">static text</p>
    <p class="text">{{ message }}</p>
    <p class="text">static text</p>
    <p class="text">static text</p>
  </div>
</template>

整个 diff 过程如图所示:

1.png

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

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

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

PatchFlag

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

export const enum PatchFlags {
  TEXT = 1, // 动态文本节点
  CLASS = 1 << 1, // 动态class
  STYLE = 1 << 2, // 动态style
  PROPS = 1 << 3, // 除了class、style动态属性
  FULL_PROPS = 1 << 4, // 有key,需要完整diff
  HYDRATE_EVENTS = 1 << 5, // 挂载过事件的
  STABLE_FRAGMENT = 1 << 6, // 稳定序列,子节点顺序不会发生变化
  KEYED_FRAGMENT = 1 << 7, // 子节点有key的fragment
  UNKEYED_FRAGMENT = 1 << 8, // 子节点没有key的fragment
  NEED_PATCH = 1 << 9, // 进行非props比较, ref比较
  DYNAMIC_SLOTS = 1 << 10, // 动态插槽
  DEV_ROOT_FRAGMENT = 1 << 11, 
  HOISTED = -1, // 表示静态节点,内容变化,不比较儿子
  BAIL = -2 // 表示diff算法应该结束
}

Block Tree

2.png

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

let currentBlock = null
function _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 < n2.dynamicChildren.length; i++) {
    patchElement(n1.dynamicChildren[i], n2.dynamicChildren[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 会充分利用 patchFlagdynamicChildren 做优化。如果确定只是某个局部的变动,比如 style 改变,那么只会调用 hostPatchProp 并传入对应的参数 style 做特定的更新(靶向更新);如果有 dynamicChildren,会执行 patchBlockChildren 做对比更新,不会每次都对 props 和子节点进行全量的对比更新。图解如下:

4.png

静态提升

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

<div>
  <span>hello</span> 
  <span a=1 b=2>{{name}}</span>
  <a><span>{{age}}</span></a>
</div>

编译生成的 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 ),
    _createElementVNode("a", null, [
      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 )
    ])
  ]))
}

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

开启静态提升 hoistStatic 选项后

const _hoisted_1 = _createElementVNode("span", null, "hello", -1 )
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 ),
    _createElementVNode("a", null, [
      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 )
    ])
  ]))
}

预解析字符串化

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

假如有如下模板:

<div>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
</div>

开启静态提升 hoistStatic 选项后

const _hoisted_1 = _createStaticVNode("<span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span>", 10)
const _hoisted_11 = [  _hoisted_1]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, _hoisted_11))
}

函数缓存

假如有如下模板:

<div @click="event => v = event.target.value"></div>

编译后:

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 , _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 在编译阶段做的优化,基于上面几点,Vuejspatch 过程中极大地提高了性能。

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

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

--结束END--

本文标题: vue3编译做了哪些优化

本文链接: https://www.lsjlt.com/news/205964.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • vue3编译做了哪些优化
    本教程操作环境:windows7系统、vue3版,DELL G3电脑。本文主要来分析 Vue3.0 编译阶段做的优化,在 patch 阶段是如何利用这些优化策略来减少比对次数。 由于组件更新时依然需要遍历该组件的整个 vnode 树,比如下...
    99+
    2023-05-14
    前端 Vue.js
  • vue3编译优化的内容有哪些
    本文主要来分析 Vue3.0 编译阶段做的优化,在 patch 阶段是如何利用这些优化策略来减少比对次数。 由于组件更新时依然需要遍历该组件的整个 vnode 树,比如下面这个模板:<template> <div id...
    99+
    2023-05-17
    Vue3
  • Vue3模板编译优化的示例分析
    小编给大家分享一下Vue3模板编译优化的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!编译入口了解过 Vue3 的同学肯...
    99+
    2024-04-02
  • linux内核编译怎么优化
    编译 Linux 内核时,可以采取以下几种优化措施来提高性能: 选择合适的配置选项:根据硬件平台和特定要求,选择适合的配置选项。...
    99+
    2024-02-29
    linux
  • Golang编译优化技巧分享
    Golang编译优化技巧分享 为了提高Golang程序的性能和效率,优化编译过程是至关重要的。本文将分享一些Golang编译的优化技巧,并提供具体的代码示例供读者参考。 一、使用编译...
    99+
    2024-03-07
    技巧 golang 编译优化
  • 做SEO优化的小诀窍有哪些
    这篇文章将为大家详细讲解有关做SEO优化的小诀窍有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。通过对手获得外链资源外链资源对每一个站长都是一个秘密,没有任何一个SEO站长会把好的资源拿出来分享,所以...
    99+
    2023-06-10
  • Android系统优化Ninja加快编译
    目录背景环境关键编译阶段和耗时分析阶段一:Soong bootstrap阶段二:Kati遍历、mk搜集与ninja生成阶段三:Ninja编译编译优化对比汇总背景 Android系统模...
    99+
    2022-11-13
    Android Ninja加快编译 Android Ninja编译优化
  • 详解C++编译器优化技术
    目录前言RVONRVO复制省略优化失效的情况前言 注1:vc6、vs没有提供编译选项来关闭该优化,无论是debug还是release都会进行RVO和复制省略优化 注2:vc6、vs2...
    99+
    2024-04-02
  • python编译器有哪些
    今天就跟大家聊聊有关python编译器有哪些,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。python的五大特点是什么python的五大特点:1.简单易学,开发程序时,专注的是解决问...
    99+
    2023-06-14
  • golang编译器有哪些
    golang编译器有GC编译器、gccgo、TinyGo和llgo。详细介绍:1、GC编译器,负责将Go语言代码编译成中间代码,然后通过链接器等工具生成可执行文件;2、gccgo,提供了Go语言前端,可以用于将Go代码编译成机器码;3、Ti...
    99+
    2023-12-14
    go语言 Golang
  • 坚持做SEO优化的原因有哪些
    本篇内容主要讲解“坚持做SEO优化的原因有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“坚持做SEO优化的原因有哪些”吧!  已经不知道从什么时候开始,大家都提到了SEO已死,而且这一两年来...
    99+
    2023-06-10
  • 网站访问速度可做哪些优化
    这篇文章给大家介绍网站访问速度可做哪些优化,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、 服务器优化 Windows系列: 64位Win2008r2 + Hpyer-V + 负载均衡 + IIS7.5 64位Win...
    99+
    2023-06-12
  • Golang编译速度优化方法探讨
    Golang编译速度优化方法探讨 近年来,Golang(Go语言)作为一种快速、高效的编程语言在软件开发领域中越来越受欢迎。然而,随着项目规模不断增长,编译速度成为了开发者们比较关注的...
    99+
    2024-03-06
    优化 golang 速度 go语言
  • Vue编译优化实现流程详解
    目录动态节点收集与补丁标志1.传统diff算法的问题2.Block和PatchFlags3.收集动态节点4.渲染器运行时支持5.Block树静态提升预字符化缓存内联事件处理函数v-o...
    99+
    2023-01-28
    Vue编译优化 Vue代码优化
  • Project Reference优化TypeScript编译性能示例
    目录引言Project Reference总结引言 TypeScript 给 JavaScript 添加了一套类型系统,可以在编译期间检查出类型错误,这增加了代码的健壮性,但也多了一...
    99+
    2022-11-13
    Project Reference优化TypeScript TypeScript编译性能优化
  • 哔哩哔哩Android项目编译优化
    目录背景编译优化工作流程快编插件获取工程树结构version版本源码orAAR主动Skip模块Configuration策略远端uploadR8 class checkFaster云...
    99+
    2024-04-02
  • C++中编译优化问题的详解
    C++中编译优化问题的详解编写高效的C++代码是每个程序员都追求的目标,而编译优化就是其中一个重要的方面。正确理解和应用编译优化可以极大地提高程序的性能和效率。本文将从C++编译优化的基本原理、常见的优化技术和具体的代码示例入手,详细解析C...
    99+
    2023-10-22
    C++编译优化问题
  • 深入了解golang编译器的发展历程和优化策略
    一文读懂golang编译器的演化历程与优化策略当谈到编程语言中的编译器时,很多人可能会想到C语言或Java,但在近些年里,一门名为Golang的编程语言越来越受到程序员的关注和喜爱。Golang是Google开发的一门静态类型、编译型的高级...
    99+
    2023-12-29
    策略 编译器 优化 Golang 演化
  • nodejs的编译器有哪些
    今天小编给大家分享一下nodejs的编译器有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下...
    99+
    2024-04-02
  • MySQL server安装前后需要做哪些优化
    本篇内容介绍了“MySQL server安装前后需要做哪些优化”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作