vue2-优化器

在vue2-AST抽象语法树 https://www.jianshu.com/p/d7822b42a3e3
中 说了一下 把模板 --> ast抽象语法树 --> 渲染函数;

其实在里面 详细 是 模板 ——> 模板编译 ——> 渲染函数

其中 模板编译 包括 三个模块: 解析器 -> 优化器 -> 代码生成器

  • 解析器: 把模板解析成AST
  • 优化器:遍历AST 标记静态节点
  • 代码生成器: 使用AST 生成渲染函数

由于 把模板转 AST语法树 以及讲过。现在看一下 优化器。

优化器

优化器的目标 是遍历AST, 检测出所有 静态子树(永远不会发生变化的DOM节点) 并打上标记

当AST的静态子树 被打上 标记后, 每次重新渲染时, 就不需要为打上标记的 静态节点创建 新的虚拟节点, 而是直接克隆 已存在的虚拟节点。 在虚拟DOM 的更新操作中,如果发现两个节点是 同一个节点,正常情况下会对两个节点进行更新, 但是如果这两个节点是静态节点, 那么直接跳过 更新节点流程。

比如:

Hello {{name}}

转换为AST如下

{
  'type': 1,
  'tag': 'div',
  'attrsList': [
    {
      'name': 'id',
      'value': 'el'
    }
  ],
  'attsMap': {
    'id': 'el'
  },
  'children': [
    {
      'type': 2,
      'expression': '"Hello "+_s(name)',
      'text': 'Hello {{name}}'
    }
  ],
  'plain': false, // true 则没有属性
  'attrs': [
    {
      'name': 'id',
      'value': '"el"'
    }
  ]
}

经过优化器优化之后,AST如下

{
  'type': 1,
  'tag': 'div',
  'attrsList': [
    {
      'name': 'id',
      'value': 'el'
    }
  ],
  'attsMap': {
    'id': 'el'
  },
  'children': [
    {
      'type': 2,
      'expression': '"Hello "+_s(name)',
      'text': 'Hello {{name}}',
      'static': false  // 静态节点标记
    }
  ],
  'plain': false, // true 则没有属性
  'attrs': [
    {
      'name': 'id',
      'value': '"el"'
    }
  ],
  'static': false, // 静态节点标记
  'staticRoot': false // 静态根节点标记
}

其中_s 是下面toString函数的别名 (跟优化器没啥联系,代码生成器中的)

function toString(val) {
  return val === null ?
    '' :
    typeof val === 'object' ?
      JSON.stringify(val, null, 2) :  // 返回值文本在每个级别缩进2个的空格
      String(val)
}

优化器内部实现 分为两个步骤:

  1. 在AST中找出所有 静态节点并打上标记
  2. 在AST中找出所有的静态根几点并打上标记

静态节点:永远不会变化的节点

我是永远不会变的 的节点

落实到AST中,静态节点指的是static 属性为true的节点

{
  type: 1,
  tag: 'p',
  staticRoot: false,
  static: true
  ....
}

静态根节点 : 如果一个节点下面所有的子节点都是静态节点,并且 它的父级 是动态节点
比如

  • lkjkjljkjlj
  • asdf;lksda;l
  • adskl;fka

落实到AST中静态根节点只是的 staticRoot属性为true的节点

{
  type: 1,
  tag: 'ul',
  staticRoot: true,
  static: true,
 ....
}

代码的实现如下

export function optimize(root){
  if(!root) return;
  // 第一步: 标记所有的静态节点
  markStatic(root)
  // 第二部: 标记所有的静态根节点
  markStaticRoots(root)
}

找出所有的静态节点并标记

使用isStatic函数判断节点 是否是静态节点, 然后如果节点的类型为1, 说明节点是元素节点,那么循环改节点的子节点, 递归调用markStatic函数处理子节点

function markStatic(node) {
  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)
    }
  }
}

先看一下 node.type取值范围

type值 说明
1 元素节点
2 带变量的动态文本节点
3 不带变量的纯文本节点

实现isStatic函数,判断是否是静态节点

function isStatic(node){
  // 带变量的动态文本节点  比如  

我的名字是{{name}}

if(node.type === 2){ return false } // 不带变量的纯文本节点 比如

我的名字是张三

if(node.type ===3){ return true } return !!(ndoe.pre || ( // v-pre 标记的静态节点 !node.hasBindings && // 没有动态绑定 不能有 v- @ :开头的属性 !node.if && !node.for && // 没有 v-if 或 v-for或 v-else 指令 !isBuildInTag(node.tag)&& // 不是内置标签,就是说标签名不能是slot或 component isPlatformReservedTag(node.tag) && // 不是组件,标签名必须是保留标签 比如 div,p,h1,h2,ul,li,img,hr,ol,svg,table,html,body等 !isDirectChildOfTemplateFor(node) && // 当前节点的父节点不能是带v-for指令的tempalte标签 Object.keys(node).every(isStaticKey) // 节点中不存在动态节点才会有的属性 )) }

其中最后一条 节点中不存在动态节点才会有的属性,事实上, 一个元素节点如果是静态节点,那么这个节点的属性其实是有范围的。如果这个节点是静态节点,那么它的属性是可以在这个范围内找到的。这个范围是 type, tag, attrsList,attrsMap,plain,parent,children, attrs,staticClass和staticStyle.

如果一个元素节点上的属性在上面这个范围内找不到相同的属性名,就说明这个节点不是静态节点。

还有一个问题,递归从上向下标记静态节点。 如果父节点被标记为静态节点后,子节点却被标记为动态节点,这个会发生矛盾。所有要改一下

function markStatic(node) {
  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
      }
    }
  }
}

找出所有的静态根节点并标记

function markStaticRoots(node) {
  if (node.type === 1) {
    // 要使节点符合静态根节点的要求,它必须有子节点
    // 这个子节点不能是只有一个静态文本 的子节点, 否则优化成本将超过收益
    if (node.static && node.children.length && !(node.children.length !== 1 && node.childern[0].type === 3)) {
      node.staticRoot = upTask.onProgressUpdate((result) => {
        return
      });
    } else {
      // 其他情况 不是静态根节点
      node.staticRoot = false
    }
    // 如果 不是静态根节点 并且有 子节点 ,那么递归 遍历 子节点
    if (node.children) {
      for (let i = 0, l = node.children.length; i < l; i++) {
        markStaticRoots(node.children[i])
      }
    }

  }
}

你可能感兴趣的:(vue2-优化器)