我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Vue3模板编译优化的示例分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Vue3模板编译优化的示例分析

小编给大家分享一下Vue3模板编译优化的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

编译入口

了解过 Vue3 的同学肯定知道 Vue3 引入了新的组合 Api,在组件 mount 阶段会调用 setup 方法,之后会判断 render  方法是否存在,如果不存在会调用 compile 方法将 template 转化为 render。

// packages/runtime-core/class="lazy" data-src/renderer.ts const mountComponent = (initialVNode, container) => {   const instance = (     initialVNode.component = createComponentInstance(       // ...params     )   )   // 调用 setup   setupComponent(instance) }  // packages/runtime-core/class="lazy" data-src/component.ts let compile export function registerRuntimeCompiler(_compile) {   compile = _compile } export function setupComponent(instance) {   const Component = instance.type   const { setup } = Component   if (setup) {     // ...调用 setup   }   if (compile && Component.template && !Component.render) {    // 如果没有 render 方法     // 调用 compile 将 template 转为 render 方法     Component.render = compile(Component.template, {...})   } }

这部分都是 runtime-core 中的代码,之前的文章有讲过 Vue 分为完整版和 runtime 版本。如果使用 vue-loader 处理  .vue 文件,一般都会将 .vue 文件中的 template 直接处理成 render 方法。

//  需要编译器 Vue.createApp({   template: '<div>{{ hi }}</div>' })  // 不需要 Vue.createApp({   render() {     return Vue.h('div', {}, this.hi)   } })

完整版与 runtime 版的差异就是,完整版会引入 compile 方法,如果是 vue-cli 生成的项目就会抹去这部分代码,将 compile  过程都放到打包的阶段,以此优化性能。runtime-dom 中提供了 registerRuntimeCompiler 方法用于注入 compile  方法。

Vue3模板编译优化的示例分析

主流程

在完整版的 index.js 中,调用了 registerRuntimeCompiler 将 compile 进行注入,接下来我们看看注入的  compile 方法主要做了什么。

// packages/vue/class="lazy" data-src/index.ts import { compile } from '@vue/compiler-dom'  // 编译缓存 const compileCache = Object.create(null)  // 注入 compile 方法 function compileToFunction(  // 模板   template: string | HTMLElement,   // 编译配置   options?: CompilerOptions ): RenderFunction {   if (!isString(template)) {     // 如果 template 不是字符串     // 则认为是一个 DOM 节点,获取 innerHTML     if (template.nodeType) {       template = template.innerHTML     } else {       return NOOP     }   }    // 如果缓存中存在,直接从缓存中获取   const key = template   const cached = compileCache[key]   if (cached) {     return cached   }    // 如果是 ID 选择器,这获取 DOM 元素后,取 innerHTML   if (template[0] === '#') {     const el = document.querySelector(template)     template = el ? el.innerHTML : ''   }    // 调用 compile 获取 render code   const { code } = compile(     template,     options   )    // 将 render code 转化为 function   const render = new Function(code)();   // 返回 render 方法的同时,将其放入缓存   return (compileCache[key] = render) }  // 注入 compile registerRuntimeCompiler(compileToFunction)

在讲 Vue2 模板编译的时候已经讲过,compile 方法主要分为三步,Vue3 的逻辑类似:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 模板编译,将模板代码转化为 AST;

  3. 优化 AST,方便后续虚拟 DOM 更新;

  4. 生成代码,将 AST 转化为可执行的代码;

// packages/compiler-dom/class="lazy" data-src/index.ts import { baseCompile, baseParse } from '@vue/compiler-core' export function compile(template, options) {   return baseCompile(template, options) }  // packages/compiler-core/class="lazy" data-src/compile.ts import { baseParse } from './parse' import { transform } from './transform'  import { transformIf } from './transforms/vIf' import { transformFor } from './transforms/vFor' import { transformText } from './transforms/transformText' import { transformElement } from './transforms/transformElement'  import { transformOn } from './transforms/vOn' import { transformBind } from './transforms/vBind' import { transformModel } from './transforms/vModel'  export function baseCompile(template, options) {   // 解析 html,转化为 ast   const ast = baseParse(template, options)   // 优化 ast,标记静态节点   transform(ast, {     ...options,     nodeTransforms: [       transformIf,       transformFor,       transformText,       transformElement,       // ... 省略了部分 transform     ],     directiveTransforms: {       on: transformOn,       bind: transformBind,       model: transformModel     }   })   // 将 ast 转化为可执行代码   return generate(ast, options) }

计算 PatchFlag

这里大致的逻辑与之前的并没有多大的差异,主要是 optimize 方法变成了 transform 方法,而且默认会对一些模板语法进行  transform。这些 transform 就是后续虚拟 DOM 优化的关键,我们先看看 transform 的代码 。

// packages/compiler-core/class="lazy" data-src/transform.ts export function transform(root, options) {   const context = createTransformContext(root, options)   traverseNode(root, context) } export function traverseNode(node, context) {   context.currentNode = node   const { nodeTransforms } = context   const exitFns = []   for (let i = 0; i < nodeTransforms.length; i++) {     // Transform 会返回一个退出函数,在处理完所有的子节点后再执行     const onExit = nodeTransforms[i](node, context)     if (onExit) {       if (isArray(onExit)) {         exitFns.push(...onExit)       } else {         exitFns.push(onExit)       }     }   }   traverseChildren(node, context)   context.currentNode = node   // 执行所以 Transform 的退出函数   let i = exitFns.length   while (i--) {     exitFns[i]()   } }

我们重点看一下 transformElement 的逻辑:

// packages/compiler-core/class="lazy" data-src/transforms/transformElement.ts export const transformElement: NodeTransform = (node, context) => {   // transformElement 没有执行任何逻辑,而是直接返回了一个退出函数   // 说明 transformElement 需要等所有的子节点处理完后才执行   return function postTransformElement() {     const { tag, props } = node      let vnodeProps     let vnodePatchFlag     const vnodeTag = node.tagType === ElementTypes.COMPONENT       ? resolveComponentType(node, context)       : `"${tag}"`          let patchFlag = 0     // 检测节点属性     if (props.length > 0) {       // 检测节点属性的动态部分       const propsBuildResult = buildProps(node, context)       vnodeProps = propsBuildResult.props       patchFlag = propsBuildResult.patchFlag     }      // 检测子节点     if (node.children.length > 0) {       if (node.children.length === 1) {         const child = node.children[0]         // 检测子节点是否为动态文本         if (!getStaticType(child)) {           patchFlag |= PatchFlags.TEXT         }       }     }      // 格式化 patchFlag     if (patchFlag !== 0) {         vnodePatchFlag = String(patchFlag)     }      node.codegenNode = createVNodeCall(       context,       vnodeTag,       vnodeProps,       vnodeChildren,       vnodePatchFlag     )   } }

buildProps 会对节点的属性进行一次遍历,由于内部源码涉及很多其他的细节,这里的代码是经过简化之后的,只保留了 patchFlag  相关的逻辑。

export function buildProps(   node: ElementNode,   context: TransformContext,   props: ElementNode['props'] = node.props ) {   let patchFlag = 0   for (let i = 0; i < props.length; i++) {     const prop = props[i]     const [key, name] = prop.name.split(':')     if (key === 'v-bind' || key === '') {       if (name === 'class') {        // 如果包含 :class 属性,patchFlag | CLASS         patchFlag |= PatchFlags.CLASS       } else if (name === 'style') {        // 如果包含 :style 属性,patchFlag | STYLE         patchFlag |= PatchFlags.STYLE       }     }   }    return {     patchFlag   } }

上面的代码只展示了三种 patchFlag 的类型:

  • 节点只有一个文本子节点,且该文本包含动态的数据(TEXT = 1)

<p>name: {{name}}</p>
  • 节点包含可变的 class 属性(CLASS = 1 << 1)

  • <div :class="{ active: isActive }"></div>

节点包含可变的 style 属性(STYLE = 1 << 2)

<div :style="{ color: color }"></div>

可以看到 PatchFlags 都是数字 1 经过 左移操作符 计算得到的。

export const enum PatchFlags {   TEXT = 1,             // 1, 二进制 0000 0001   CLASS = 1 << 1,       // 2, 二进制 0000 0010   STYLE = 1 << 2,       // 4, 二进制 0000 0100   PROPS = 1 << 3,       // 8, 二进制 0000 1000   ... }

从上面的代码能看出来,patchFlag 的初始值为 0,每次对 patchFlag 都是执行 |  (或)操作。如果当前节点是一个只有动态文本子节点且同时具有动态 style 属性,最后得到的 patchFlag 为 5(二进制:0000 0101)。

<p :style="{ color: color }">name: {{name}}</p>

Vue3模板编译优化的示例分析

我们将上面的代码放到 Vue3 中运行:

const app = Vue.createApp({   data() {     return {       color: 'red',       name: 'shenfq'     }   },   template: `<div>    <p :style="{ color: color }">name: {{name}}</p>   </div>` })  app.mount('#app')

最后生成的 render 方法如下,和我们之前的描述基本一致。

Vue3模板编译优化的示例分析

function render() {}

render 优化

Vue3 在虚拟 DOM Diff 时,会取出 patchFlag 和需要进行的 diff 类型进行 &(与)操作,如果结果为 true  才进入对应的 diff。

Vue3模板编译优化的示例分析

patchFlag 判断

还是拿之前的模板举例:

<p :style="{ color: color }">name: {{name}}</p>

如果此时的 name 发生了修改,p 节点进入了 diff 阶段,此时会将判断 patchFlag & PatchFlags.TEXT  ,这个时候结果为真,表明 p 节点存在文本修改的情况。

Vue3模板编译优化的示例分析

patchFlag

patchFlag = 5 patchFlag & PatchFlags.TEXT // 或运算:只有对应的两个二进位都为1时,结果位才为1。 // 0000 0101 // 0000 0001 // ------------ // 0000 0001  =>  十进制 1
if (patchFlag & PatchFlags.TEXT) {   if (oldNode.children !== newNode.children) {     // 修改文本     hostSetElementText(el, newNode.children)   } }

但是进行 patchFlag & PatchFlags.CLASS 判断时,由于节点并没有动态 Class,返回值为 0,所以就不会对该节点的  class 属性进行 diff,以此来优化性能。

Vue3模板编译优化的示例分析

patchFlag

patchFlag = 5 patchFlag & PatchFlags.CLASS // 或运算:只有对应的两个二进位都为1时,结果位才为1。 // 0000 0101 // 0000 0010 // ------------ // 0000 0000  =>  十进制 0

以上是“Vue3模板编译优化的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Vue3模板编译优化的示例分析

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

下载Word文档

猜你喜欢

C++模板编程的示例分析

这篇文章主要为大家展示了“C++模板编程的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C++模板编程的示例分析”这篇文章吧。模板初阶泛型编程在计算机程序设计领域,为了避免因数据类型的不
2023-06-25

C++模板>>编译问题举例分析

本篇内容主要讲解“C++模板>>编译问题举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++模板>>编译问题举例分析”吧!在编译理论中,通常将编译过程抽象为5个主要阶段:词法分析(Lex
2023-06-17

java模板模式的示例分析

这篇文章主要介绍java模板模式的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Java设计模式-模板模式什么是模板模式?模板模式,顾名思义,就是通过模板拓印的方式。定义模板,就是定义框架、结构、原型。定义一
2023-06-20

Linux内核编译的示例分析

小编给大家分享一下Linux内核编译的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!  下载源代码Linux内核总是不断发展的,***的内核Kernel
2023-06-16

C++模板重载的示例分析

本篇文章为大家展示了C++模板重载的示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1.重载模板函数模板可以使得同一个函数对不同类型使用,非常地方便。但有的时候类型不同,只是通过模板是没办法解
2023-06-22

python中不同模板的示例分析

这篇文章将为大家详细讲解有关python中不同模板的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。python的数据类型有哪些?python的数据类型:1. 数字类型,包括int(整型)、long
2023-06-14

编译.NET Core 源码的示例分析

小编给大家分享一下编译.NET Core 源码的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一:Windows 编译VS 2019 16.6(不要安装预览版)Win 10 专业版,最新版本 (1903/2004)
2023-06-14

PHP中模板方法模式的示例分析

小编给大家分享一下PHP中模板方法模式的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!模板方法模式,也是我们经常会在不经意间有会用到的模式之一。这个模式是
2023-06-20

基于编译虚拟机jvm之openjdk编译的示例分析

这篇文章给大家分享的是有关基于编译虚拟机jvm之openjdk编译的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。java只所以被推广,实际上很大原因是因为本身是跨平台的,很大作用是因为虚拟机的关系。一般
2023-05-30

JavaScript模块化的示例分析

小编给大家分享一下JavaScript模块化的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 浏览器支持使用JavaScript 模块依赖于 impo
2023-06-15

Linux程序编译过程的示例分析

小编给大家分享一下Linux程序编译过程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!本文将介绍如何将高层的C/C++语言编写的程序转换成为处理器能够执
2023-06-15

Nginx源码编译安装的示例分析

这篇文章将为大家详细讲解有关Nginx源码编译安装的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。rpm包安装比较简单,这里不做说明。对于大多数开源软件,如果找不到安装包,可以使用源码安装方式,源
2023-06-25

编程热搜

目录