美文网首页
vue2-优化器

vue2-优化器

作者: AAA前端 | 来源:发表于2021-05-19 23:12 被阅读0次

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

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

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

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

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

    优化器

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

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

    比如:
    <div id="el">Hello {{name}}</div>
    转换为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中找出所有的静态根几点并打上标记

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

    <p>我是永远不会变的 的节点</p>
    

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

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

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

    <ul>
      <li>lkjkjljkjlj</li>
      <li>asdf;lksda;l</li>
      <li>adskl;fka</li>
    </ul>
    

    落实到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){
      // 带变量的动态文本节点  比如  <p>我的名字是{{name}}</p>
      if(node.type === 2){
        return false
      }
      // 不带变量的纯文本节点 比如 <p>我的名字是张三</p>
      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-优化器

          本文链接:https://www.haomeiwen.com/subject/wcsmjltx.html