• 编译optimize源码实现过程


    为什么optimize?

    当我们的模板 template 经过 parse 过程后,会输出生成 AST 树,那么接下来我们需要对这颗树做优化,optimize 的逻辑是远简单于 parse 的逻辑,所以理解起来会轻松很多。
    为什么要有优化过程,因为我们知道 Vue 是数据驱动,是响应式的,但是我们的模板并不是所有数据都是响应式的,也有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在 patch 的过程跳过对他们的比对。
    来看一下 optimize 方法的定义,在 src/compiler/optimizer.js 中:

    export function optimize (root: ?ASTElement, options: CompilerOptions) {
      if (!root) return
      isStaticKey = genStaticKeysCached(options.staticKeys || '')
      isPlatformReservedTag = options.isReservedTag || no
      // 标记为静态节点
      markStatic(root)
      // 标记为静态根
      markStaticRoots(root, false)
    }
    
    复制代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们在编译阶段可以把一些 AST 节点优化成静态节点,所以整个 optimize 的过程实际上就干 2 件事情,markStatic(root) 标记静态节点 ,markStaticRoots(root, false) 标记静态根。

    标记静态节点

    function markStatic (node: ASTNode) {
      // 判断当前节点是否是静态的
      node.static = isStatic(node)
      if (node.type === 1) {
        ...
        // 判断当前节点的子节点是否是静态的,只有子节点全部是静态的父节点才是静态的
        for (let i = 0, l = node.children.length; i < l; i++) {
          const child = node.children[i]
          markStatic(child)
          if (!child.static) {
            node.static = false
          }
        }
        // 针对v-if
        if (node.ifConditions) {
          // 注意这里是从i=1开始的,所以只会找v-else v-else-if的节点,不会找v-if的节点,因为这个节点已经判断过了
          for (let i = 1, l = node.ifConditions.length; i < l; i++) {
            const block = node.ifConditions[i].block
            markStatic(block)
            if (!block.static) {
              node.static = false
            }
          }
        }
      }
    }
    
    function isStatic (node: ASTNode): boolean {
      // 如果节点类型是表达式
      if (node.type === 2) { // expression
        return false
      }
      // 如果节点类型是纯文本
      if (node.type === 3) { // text
        return true
      }
      
      return !!(node.pre || (
        !node.hasBindings && // no dynamic bindings
        !node.if && !node.for && // not v-if or v-for or v-else
        !isBuiltInTag(node.tag) && // not a built-in
        isPlatformReservedTag(node.tag) && // not a component
        !isDirectChildOfTemplateFor(node) &&
        Object.keys(node).every(isStaticKey)
      ))
    }
    复制代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    首先执行 node.static = isStatic(node)
    isStatic 是对一个 AST 元素节点是否是静态的判断,如果是表达式,就是非静态;如果是纯文本,就是静态;对于一个普通元素,如果有 pre 属性,那么它使用了 v-pre 指令,是静态,否则要同时满足以下条件:没有使用 v-if、v-for,没有使用其它指令(不包括 v-once),非内置组件,是平台保留的标签,非带有 v-for 的 template 标签的直接子节点,节点的所有属性的 key 都满足静态 key;这些都满足则这个 AST 节点是一个静态节点。
    如果这个节点是一个普通元素,则遍历它的所有 children,递归执行 markStatic。因为所有的 elseif 和 else 节点都不在 children 中, 如果节点的 ifConditions 不为空,则遍历 ifConditions 拿到所有条件中的 block,也就是它们对应的 AST 节点,递归执行 markStatic。在这些递归过程中,一旦子节点有不是 static 的情况,则它的父节点的 static 均变成 false。
    针对下面这段代码:

    template: '
    1231
    '
    复制代码
    • 1
    • 2

    当解析到

    的闭合标签的时候,会进入到下面这段逻辑里面,它会把当前节点添加到根节点的IfCondition属性中。

    function closeElement (element) {
      ...
      if (!stack.length && element !== root) {
        // allow root elements with v-if, v-else-if and v-else
        if (root.if && (element.elseif || element.else)) {
          ...
          addIfCondition(root, {
            exp: element.elseif,
            block: element
          })
        }
      }
    }
    复制代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    从图中可以看到

    1231
    作为AST的根节点,
    存在于根节点的IfCondition属性中。
    在这里插入图片描述

    标记静态根

    function markStaticRoots (node: ASTNode, isInFor: boolean) {
      // 静态根只会对type === 1 的节点,文本节点都不会标记静态根
      if (node.type === 1) {
        if (node.static || node.once) {
          node.staticInFor = isInFor
        }
       
        if (node.static && node.children.length && !(
          node.children.length === 1 &&
          node.children[0].type === 3
        )) {
          node.staticRoot = true
          return
        } else {
          node.staticRoot = false
        }
        if (node.children) {
          for (let i = 0, l = node.children.length; i < l; i++) {
            markStaticRoots(node.children[i], isInFor || !!node.for)
          }
        }
        if (node.ifConditions) {
          for (let i = 1, l = node.ifConditions.length; i < l; i++) {
            markStaticRoots(node.ifConditions[i].block, isInFor)
          }
        }
      }
    }
    复制代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    markStaticRoots 第二个参数是 isInFor,对于已经是 static 的节点或者是 v-once 指令的节点,node.staticInFor = isInFor。 接着就是对于 staticRoot 的判断逻辑,从注释中我们可以看到,对于有资格成为 staticRoot 的节点,除了本身是一个静态节点外,必须满足拥有 children,并且 children 不能只是一个文本节点,不然的话把它标记成静态根节点的收益就很小了。
    如果某个节点是静态根那么直接返回,如果不是,继续遍历children 以及 ifConditions,递归执行 markStaticRoots。
    回归我们之前的例子,经过 optimize 后,AST 树变成了如下:

    <ul :class="bindCls" class="list" v-if="isShow">
        <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
    </ul>
    复制代码
    
    • 1
    • 2
    • 3
    • 4
    ast = {
      'type': 1,
      'tag': 'ul',
      'attrsList': [],
      'attrsMap': {
        ':class': 'bindCls',
        'class': 'list',
        'v-if': 'isShow'
      },
      'if': 'isShow',
      'ifConditions': [{
        'exp': 'isShow',
        'block': // ul ast element
      }],
      'parent': undefined,
      'plain': false,
      'staticClass': 'list',
      'classBinding': 'bindCls',
      'static': false,
      'staticRoot': false,
      'children': [{
        'type': 1,
        'tag': 'li',
        'attrsList': [{
          'name': '@click',
          'value': 'clickItem(index)'
        }],
        'attrsMap': {
          '@click': 'clickItem(index)',
          'v-for': '(item,index) in data'
         },
        'parent': // ul ast element
        'plain': false,
        'events': {
          'click': {
            'value': 'clickItem(index)'
          }
        },
        'hasBindings': true,
        'for': 'data',
        'alias': 'item',
        'iterator1': 'index',
        'static': false,
        'staticRoot': false,
        'children': [
          'type': 2,
          'expression': '_s(item)+":"+_s(index)'
          'text': '{{item}}:{{index}}',
          'tokens': [
            {'@binding':'item'},
            ':',
            {'@binding':'index'}
          ],
          'static': false
        ]
      }]
    }
    复制代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    我们发现每一个 AST 元素节点都多了 staic 属性,并且 type 为 1 的普通元素 AST 节点多了 staticRoot 属性。
    那么至此我们分析完了 optimize 的过程,就是深度遍历这个 AST 树,去检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用。
    我们通过 optimize 我们把整个 AST 树中的每一个 AST 元素节点标记了 static 和 staticRoot,它会影响我们接下来执行代码生成的过程。

    源码附件已经打包好上传到百度云了,大家自行下载即可~

    链接: https://pan.baidu.com/s/14G-bpVthImHD4eosZUNSFA?pwd=yu27
    提取码: yu27
    百度云链接不稳定,随时可能会失效,大家抓紧保存哈。

    如果百度云链接失效了的话,请留言告诉我,我看到后会及时更新~

    开源地址

    码云地址:
    http://github.crmeb.net/u/defu

    Github 地址:
    http://github.crmeb.net/u/defu

  • 相关阅读:
    JavaScript数组常用的方法整理
    动态规划的简单套路(C++描述)
    字体的基础知识:英文字体的特征及结构(终于找到了)
    【对象存储】SpringBoot集成华为云OBS对象存储
    经典SQL语句大全
    Redis漏洞总结--未授权--沙箱绕过--(CNVD-2015-07557)&&(CNVD-2019-21763)&&(CVE-2022-0543)
    vulnhub靶场之DIGITALWORLD.LOCAL: FALL
    C++类型安全
    【自动化测试】如何在jenkins中搭建allure
    Spring-IOC 控制反转
  • 原文地址:https://blog.csdn.net/qq_39221436/article/details/126154748