返回顶部
首页 > 资讯 > 精选 >Vue3.0静态节点提升是什么
  • 268
分享到

Vue3.0静态节点提升是什么

2023-06-27 11:06:26 268人浏览 独家记忆
摘要

今天小编给大家分享一下vue3.0静态节点提升是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。前言「静态节点提升」是「V

今天小编给大家分享一下vue3.0静态节点提升是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

前言

「静态节点提升」是「Vue3」针对 Vnode 更新过程性能问题而提出的一个优化点。众所周知,在大型应用场景下,「Vue2.x」 的 patchVNode 过程,即 diff 过程是非常缓慢的,这是一个十分令人头疼的问题。

虽然,对于面试常问的 diff 过程在一定程度上是减少了对 DOM 的直接操作。但是,「这个减少是有一定成本的」。因为,如果是复杂应用,那么就会存在父子关系非常复杂的 VNode,而这也就是 diff 的痛点,它会不断地递归调用 patchVNode,不断堆叠而成的几毫秒,最终就会造成 VNode 更新缓慢。

也因此,这也是为什么我们所看到的大型应用诸如阿里云之类的采用的是基于「React」的技术栈的原因之一。所以,「Vue3」也是痛改前非,重写了整个 Compiler 过程,提出了静态提升、靶向更新等优化点,来提高 patchVNode 过程。

那么,回到今天的正题,我们从源码角度看看在整个编译过程「Vue3」静态节点提升究竟是「何许人也」

什么是 patchFlag

由于,在 compile 过程的 transfrom 阶段会提及 AST Element 上的 patchFlag 属性。所以,在正式认识 complie 之前,我们先搞清楚一个概念,什么是 patchFlag

patchFlagcomplier 时的 transfORM 阶段解析 AST Element 打上的「优化标识」。并且,顾名思义 patchFlagpatch 一词表示着它会为 runtime时的 patchVNode 提供依据,从而实现靶向更新 VNode 的效果。因此,这样一来一往,也就是耳熟能详的 Vue3 巧妙结合 runtimecompiler 实现靶向更新和静态提升。

而在源码中 patchFlag 被定义为一个「数字枚举类型」

并且,值得一提的是整体上 patchFlag 的分为两大类:

  • patchFlag 的值「大于」 0 时,代表所对应的元素在 patchVNode 时或 render 时是可以被优化生成或更新的。

  • patchFlag 的值「小于」 0 时,代表所对应的元素在 patchVNode 时,是需要被 full diff,即进行递归遍历 VNode tree 的比较更新过程。

其实,还有两类特殊的 flagshapeFlagslogFlag,这里我就不对此展开,有兴趣的同学可以自行去了解。

Compile 编译过程

对比 Vue2.x 编译过程

了解过「Vue2.x」源码的同学,我想应该都知道在「Vue2.x」中的 Compile 过程会是这样:

  • parse 编译模板生成原始 AST。

  • optimize 优化原始 AST,标记 AST Element 为静态根节点或静态节点。

  • generate 根据优化后的 AST,生成可执行代码,例如 _c_l 之类的。

而在「Vue3」中,整体的 Compile 过程仍然是三个阶段,但是不同于「Vue2.x」的是,第二个阶段换成了正常编译器都会存在的阶段 transform

在源码中,它对应的伪代码会是这样:

export function baseCompile(  template: string | RootNode,  options: CompilerOptions = {}): CodegenResult {  ...  const ast = isString(template) ? baseParse(template, options) : template  ...  transform(    ast,    extend({}, options, {....})  )  return generate(    ast,    extend({}, options, {      prefixIdentifiers    })  )}

那么,我想这个时候大家可能会问为什么会是 transform?它的职责是什么?

通过简单的对比「Vue2.x」编译过程的第二阶段的 optimize,很明显,transform并不是「无米之炊」,它仍然有着「优化」原始 AST 的作用,而具体职责会表现在:

  • 对所有 AST Element 新增 codegen 属性来帮助 generate 更准确地生成「最优」的可执行代码。

  • 对静态 AST Element 新增 hoists 属性来实现静态节点的「单独创建」

  • ...

此外,transform 还标识了诸如 isBlockhelpers 等属性,来生成最优的可执行代码,这里我们就不细谈,有兴趣的同学可以自行了解。

baseParse 构建原始抽象语法树(AST)

baseParse 顾名思义起着「解析」的作用,它的表现和「Vue2.x」的 parse 相同,都是解析模板 tempalte 生成「原始 AST」

假设,此时我们有一个这样的模板 template

<div><div>hi vue3</div><div>{{msg}}</div></div>

那么,它在经过 baseParse 处理后生成的 AST 看起来会是这样:

{  cached: 0,  children: [{…}],  codegenNode: undefined,  components: [],  directives: [],  helpers: [],  hoists: [],  imports: [],  loc: {start: {…}, end: {…}, source: "<div><div>hi vue3</div><div>{{msg}}</div></div>"},  temps: 0,  type: 0}

如果,了解过「Vue2.x」编译过程的同学应该对于上面这颗 AST 的大部分属性不会陌生。AST 的本质是通过用对象来描述「DSL」(特殊领域语言),例如:

  • children 中存放的就是最外层 div 的后代。

  • loc 则用来描述这个 AST Element 在整个字符串template)中的位置信息。

  • type 则是用于描述这个元素的类型(例如 5 为插值、2 为文本)等等。

并且,可以看到的是不同于「Vue2.x」的 AST,这里我们多了诸如 helperscodegenNodehoists 等属性。而,这些属性会在 transform 阶段进行相应地赋值,进而帮助 generate 阶段生成「更优的」可执行代码。

transfrom 优化原始抽象语法树(AST)

对于 transform 阶段,如果了解过「编译器」的工作流程的同学应该知道,一个完整的编译器的工作流程会是这样:

  • 首先,parse 解析原始代码字符串,生成抽象语法树 AST。

  • 其次,transform 转化抽象语法树,让它变成更贴近目标「DSL」的结构。

  • 最后,codegen 根据转化后的抽象语法树生成目标「DSL」的可执行代码。

而在「Vue3」采用 Monorepo 的方式管理项目后,compile 对应的能力就是一个编译器。所以,transform 也是整个编译过程的重中之重。换句话说,如果没有 transformAST 做诸多层面的转化,「Vue」仍然会挂在 diff 这个「饱受诟病」的过程。

相比之下,「Vue2.x」的编译阶段没有完整的 transform,只是 optimize 优化了一下 AST,可以想象在「Vue」设计之初尤大也没想到它以后会「这么地流行」

那么,我们来看看 transform 函数源码中的定义:

function transform(root: RootNode, options: TransformOptions) {  const context = createTransformContext(root, options)  traverseNode(root, context)  if (options.hoistStatic) {    hoistStatic(root, context)  }  if (!options.ssr) {    createRootCodegen(root, context)  }  // finalize meta information  root.helpers = [...context.helpers]  root.components = [...context.components]  root.directives = [...context.directives]  root.imports = [...context.imports]  root.hoists = context.hoists  root.temps = context.temps  root.cached = context.cached}

可以说,transform 函数做了什么,在它的定义中是「一览无余」。这里我们提一下它对静态提升其决定性作用的两件事:

  • 将原始 AST 中的静态节点对应的 AST Element 赋值给根 AST 的 hoists 属性。

  • 获取原始 AST 需要的 helpers 对应的键名,用于 generate 阶段的生成可执行代码的获取对应函数,例如 createTextVNodecreateStaticVNoderenderList 等等。

并且,在 traverseNode 函数中会对 AST Element 应用具体的 transform 函数,大致可以分为两类:

  • 静态节点 transform 应用,即节点不含有插值、指令、props、动态样式的绑定等。

  • 动态节点 transform 应用,即节点含有插值、指令、props、动态样式的绑定等。

那么,我们就来看看对于静态节点 transform 是如何应用的?

静态节点 transform 应用

这里,对于上面我们说到的这个栗子,静态节点就是这个部分:

<div>hi vue3</div>

而它在没有进行 transform 应用之前,它对应的 AST 会是这样:

{  children: [{    content: "hi vue3"    loc: {start: {…}, end: {…}, source: "hi vue3"}    type: 2  }],  codegenNode: undefined,  isSelfClosing: false,  loc: {start: {…}, end: {…}, source: "<div>hi vue3</div>"},  ns: 0,  props: [],  tag: "div",  tagType: 0,  type: 1}

可以看出,此时它的 codegenNodeundefined。而在源码中各类 transform函数被定义为 plugin,它会根据 baseParse 生成的 AST 「递归应用」对应的 plugin。然后,创建对应 AST Element 的 codegen 对象。

所以,此时我们会命中 transformElementtransformText 两个 plugin的逻辑。

「transformText」

transformText 顾名思义,它和「文本」相关。很显然,此时的 AST Element 所属的类型就是 Text。那么,我们先来看一下 transformText 函数对应的伪代码:

export const transformText: NodeTransform = (node, context) => {  if (    node.type === NodeTypes.ROOT ||    node.type === NodeTypes.ELEMENT ||    node.type === NodeTypes.FOR ||    node.type === NodeTypes.IF_BRANCH  ) {    return () => {      const children = node.children      let currentContainer: CompoundExpressionNode | undefined = undefined      let hasText = false      for (let i = 0; i < children.length; i++) { // {1}        const child = children[i]        if (isText(child)) {          hasText = true          ...        }      }      if (        !hasText ||        (children.length === 1 &&          (node.type === NodeTypes.ROOT ||            (node.type === NodeTypes.ELEMENT &&              node.tagType === ElementTypes.ELEMENT)))      ) { // {2}        return      }      ...    }  }}

可以看到,这里我们会命中 「{2}」 的逻辑,即如果对于「节点含有单一文本」 transformText 并不需要进行额外的处理,即该节点仍然在这里仍然保留和「Vue2.x」版本一样的处理方式。

transfromText 真正发挥作用的场景是当模板中存在这样的情况:

<div>ab {a} {b}</div>

此时 transformText 需要将两者放在一个「单独的」 AST Element 下,在源码中它被称为「Compound Expression」,即「组合的表达式」。这种组合的目的是为了 patchVNode 这类 VNode 时做到「更好地定位和实现 DOM 的更新」。反之,如果是一个文本节点和插值动态节点的话,在 patchVNode 阶段同样的操作需要进行两次,例如对于同一个 DOM 节点操作两次。

「transformElement」

transformElement 是一个所有 AST Element 都会被执行的一个 plugin,它的核心是为 AST Element 生成最基础的 codegen 属性。例如标识出对应 patchFlag,从而为生成 VNode 提供依据,例如 dynamicChildren

而对于静态节点,同样是起到一个初始化它的 codegenNode 属性的作用。并且,从上面介绍的 patchFlag 的类型,我们可以知道它的 patchFlag 为默认值 0。所以,它的 codegenNode 属性值看起来会是这样:

{  children: {    content: "hi vue3"    loc: {start: {…}, end: {…}, source: "hi vue3"}    type: 2  },  directives: undefined,  disableTracking: false,  dynamicProps: undefined,  isBlock: false,  loc: {start: {…}, end: {…}, source: "<div>hi vue3</div>"},  patchFlag: undefined,  props: undefined,  tag: ""div"",  type: 13}

generate 生成可执行代码

generatecompile 阶段的最后一步,它的作用是将 transform 转换后的 AST 生成对应的「可执行代码」,从而在之后 Runtime 的 Render 阶段时,就可以通过可执行代码生成对应的 VNode Tree,然后最终映射为真实的 DOM Tree 在页面上。

同样地,这一阶段在「Vue2.x」也是由 generate 函数完成,它会生成是诸如 _l_c 之类的函数,这本质上是对 _createElement 函数的封装。而相比较「Vue2.x」版本的 generate,「Vue3」改变了很多,其 generate 函数对应的伪代码会是这样:

export function generate(  ast: RootNode,  options: CodegenOptions & {    onContextCreated?: (context: CodegenContext) => void  } = {}): CodegenResult {  const context = createCodegenContext(ast, options)  if (options.onContextCreated) options.onContextCreated(context)  const {    mode,    push,    prefixIdentifiers,    indent,    deindent,    newline,    scopeId,    ssr  } = context  ...  genFunctionPreamble(ast, context)  ...  if (!ssr) {    ...    push(`function render(_ctx, _cache${optimizeSources}) {`)  }  ....  return {    ast,    code: context.code,    // SourceMapGenerator does have toJSON() method but it's not in the types    map: context.map ? (context.map as any).tojsON() : undefined  }}

所以,接下来,我们就来「一睹」带有静态节点对应的 AST 生成的可执行代码的过程会是怎样。

CodegenContext 代码生成上下文

从上面 generate 函数的伪代码可以看到,在函数的开始调用了 createCodegenContext 为当前 AST 生成了一个 context。在整个 generate 函数的执行过程「都依托」于一个 CodegenContext 「生成代码上下文」(对象)的能力,它是通过 createCodegenContext 函数生成。而 CodegenContext 的接口定义会是这样:

interface CodegenContext  extends Omit {  source: string  code: string  line: number  column: number  offset: number  indentLevel: number  pure: boolean  map?: SourceMapGenerator  helper(key: symbol): string  push(code: string, node?: CodegenNode): void  indent(): void  deindent(withoutNewLine?: boolean): void  newline(): void}

可以看到 CodegenContext 对象中有诸如 pushindentnewline 之类的方法。而它们的作用是在根据 AST 来生成代码时用来「实现换行」「添加代码」「缩进」等功能。从而,最终形成一个个可执行代码,即我们所认知的 render 函数,并且,它会作为 CodegenContextcode 属性的值返回。

下面,我们就来看下静态节点的可执行代码生成的核心,它被称为 Preamble 前导。

genFunctionPreamble 生成前准备

整个静态提升的可执行代码生成就是在 genFunctionPreamble 函数部分完成的。并且,大家仔细「斟酌」一番静态提升的字眼,静态二字我们可以不看,但是「提升二字」,直抒本意地表达出它(静态节点)被「提高了」

为什么说是提高了?因为在源码中的体现,确实是被提高了。在前面的 generate 函数,我们可以看到 genFunctionPreamble 是先于 render 函数加入context.code 中,所以,在 Runtime 时的 Render 阶段,它会先于 render 函数执行。

geneFunctionPreamble 函数(伪代码):

function genFunctionPreamble(ast: RootNode, context: CodegenContext) {  const {    ssr,    prefixIdentifiers,    push,    newline,    runtimeModuleName,    runtimeGlobalName  } = context  ...  const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`  if (ast.helpers.length > 0) {    ...    if (ast.hoists.length) {      const staticHelpers = [        CREATE_VNODE,        CREATE_COMMENT,        CREATE_TEXT,        CREATE_STATIC       ]        .filter(helper => ast.helpers.includes(helper))        .map(aliasHelper)        .join(', ')      push(`const { ${staticHelpers} } = _Vue\n`)    }  }  ...  genHoists(ast.hoists, context)  newline()  push(`return `)}

可以看到,这里会对前面我们在 transform 函数提及的 hoists 属性的长度进行判断。显然,对于前面说的这个栗子,它的 ast.hoists.length 长度是大于 0 的。所以,这里就会根据 hoists 中的 AST 生成对应的可执行代码。因此,到这里,生成的可执行代码会是这样:

const _Vue = Vueconst { createVNode: _createVNode } = _Vue// 静态提升部分const _hoisted_1 = _createVNode("div", null, "hi vue3", -1 )// render 函数会在这下面

以上就是“Vue3.0静态节点提升是什么”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网精选频道。

--结束END--

本文标题: Vue3.0静态节点提升是什么

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

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

猜你喜欢
  • Vue3.0静态节点提升是什么
    今天小编给大家分享一下Vue3.0静态节点提升是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。前言「静态节点提升」是「V...
    99+
    2023-06-27
  • 静态定位的特点是什么
    静态定位的特点是什么,需要具体代码示例 在网页设计中,定位(Positioning)是一种常用的布局技术,用来控制网页元素的位置。静态定位(Static Positioning)是定位...
    99+
    2024-02-22
    相比于动态定位 排列 overflow 静态定位
  • PHP中静态方法的特点是什么
    小编给大家分享一下PHP中静态方法的特点是什么,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!PHP中静态方法的特点是:1、静态方法使用static关键字修饰;2、...
    99+
    2023-06-27
  • PHP中什么是静态以及静态属性和静态方法是什么意思
    本篇内容主要讲解“PHP中什么是静态以及静态属性和静态方法是什么意思”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“PHP中什么是静态以及静态属性和静态方法是什么意思”吧!静态是什么我们之前讲到,...
    99+
    2023-06-20
  • React状态提升的方法是什么
    今天小编给大家分享一下React状态提升的方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.介绍所谓 状态提升 就...
    99+
    2023-07-06
  • PHP页面静态化的优缺点是什么
    这篇文章主要介绍“PHP页面静态化的优缺点是什么”,在日常操作中,相信很多人在PHP页面静态化的优缺点是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”PHP页面静态化的优缺点是什么”的疑惑有所帮助!接下来...
    99+
    2023-07-05
  • HTML父节点的父节点是什么
    这篇文章主要讲解了“HTML父节点的父节点是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“HTML父节点的父节点是什么”吧!父节点的父节点,比如有这样一...
    99+
    2024-04-02
  • redis动态增加节点的方法是什么
    Redis动态增加节点的方法有两种,分别是使用Redis Sentinel和使用Redis Cluster。1. 使用Redis S...
    99+
    2023-08-24
    redis
  • hadoop节点动态增删的方法是什么
    Hadoop集群中可以动态增加或删除节点,这样可以根据需求灵活调整集群规模。以下是Hadoop节点动态增删的方法: 添加节点: ...
    99+
    2024-03-05
    hadoop
  • 什么是javascript节点
    本篇文章给大家分享的是有关什么是javascript节点,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。javascript节点是指页面中所有的内容,包括标签、属性、文本,其中j...
    99+
    2023-06-14
  • php静态方法有什么特点
    本文操作环境:Windows10系统、PHP7.1版、Dell G3电脑。php静态方法有什么特点静态方法也叫类方法,静态方法是属于所有对象实例:static function+方法名在类外部:类名::类方法名或者对象名->类方法名在...
    99+
    2020-06-20
    php
  • 什么是C#静态方法
    本篇内容介绍了“什么是C#静态方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!C#静态方法是什么呢?在我们编程开发的应用中C#静态方法可以...
    99+
    2023-06-17
  • php中的静态属性和静态方法是什么
    本教程操作环境:windows7系统、PHP7.1版、DELL G3电脑在 PHP 中,通过 static 关键字修饰的成员属性和成员方法被称为静态属性和静态方法,这里可以将它们统称为静态成员,类中的静态成员与类中的一般成员不同,静态成员不...
    99+
    2021-03-06
    php 静态属性 静态方法
  • java中什么是静态方法和非静态方法
    本篇文章为大家展示了java中什么是静态方法和非静态方法,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。常用的java框架有哪些1.SpringMVC,Spring Web MVC是一种基于Java的...
    99+
    2023-06-14
  • C++ 函数可以声明为静态函数吗?静态函数的特点是什么?
    静态函数在 c++++ 中被声明为 static,具有以下特点:仅在声明函数的文件内可见,不属于任何类,在程序启动时分配内存,不可访问非静态成员。例如,用静态函数计算圆形面积的代码片段可...
    99+
    2024-04-21
    函数 c++ 静态函数 作用域
  • php缓存技术和静态化的特点是什么
    PHP缓存技术和静态化的特点如下: PHP缓存技术的特点: 提高网站性能:通过缓存PHP脚本的执行结果,减少数据库查询和服务器...
    99+
    2023-10-27
    php
  • 使用静态ip有什么优缺点
    使用静态ip有什么优缺点?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。1、静态IP的优点静态IP是一种固定的IP地址,可以作属于自己的网站和服务器,所以静态IP...
    99+
    2023-06-15
  • 伪静态网页与html静态网页区别是什么
    这篇文章将为大家详细讲解有关伪静态网页与html静态网页区别是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。 什么是伪静态,伪静态感召 伪静态即是web自己是动静...
    99+
    2024-04-02
  • 静态IP的作用是什么
    本篇内容主要讲解“静态IP的作用是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“静态IP的作用是什么”吧!1、ip固定后,便于企业信息系统的管理和高稳定性。如果网站服务器的ip不固定,这意味...
    99+
    2023-06-20
  • php中什么是静态变量
    这篇文章主要介绍了php中什么是静态变量,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。什么是静态变量:在函数内部使用static声明得变量就是静态变量;静态变量的特征:静态变...
    99+
    2023-06-15
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作