前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编译optimize源码实现过程

编译optimize源码实现过程

原创
作者头像
PHP开发工程师
发布2022-08-04 10:36:54
2660
发布2022-08-04 10:36:54
举报
文章被收录于专栏:thinkphp+vuethinkphp+vue

为什么optimize?

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

代码语言:javascript
复制
export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  isPlatformReservedTag = options.isReservedTag || no
  // 标记为静态节点
  markStatic(root)
  // 标记为静态根
  markStaticRoots(root, false)
}

复制代码

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

标记静态节点

代码语言:javascript
复制
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)
  ))
}
复制代码

首先执行 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。 针对下面这段代码:

代码语言:javascript
复制
template: '<div v-if="isShow">1231</div><div v-else></div>'
复制代码

当解析到<div v-else></div>的闭合标签的时候,会进入到下面这段逻辑里面,它会把当前节点添加到根节点的IfCondition属性中。

代码语言:javascript
复制
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
      })
    }
  }
}
复制代码

从图中可以看到<div v-if="isShow">1231</div>作为AST的根节点,<div v-else></div>存在于根节点的IfCondition属性中。

在这里插入图片描述
在这里插入图片描述
标记静态根
标记静态根
代码语言:javascript
复制
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)
      }
    }
  }
}
复制代码

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

代码语言:javascript
复制
<ul :class="bindCls" class="list" v-if="isShow">
    <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
复制代码
代码语言:javascript
复制
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
    ]
  }]
}
复制代码

我们发现每一个 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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么optimize?
  • 标记静态节点
  • 开源地址
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档