Vue2源码解析 优化器

目录

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

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

3  总结


解析器的作用是将html模板解析成AST,而优化器的作用是:在AST中找出静态子树并打上标记

静态子树是指那些在AST中永远不会发生变化的节点。

标记静态子树两个好处:
        1.每次重新渲染时,不需要为静态子树创建新节点;

        2.在虚拟dom中打补丁的过程可以跳过。

每次重新渲染时,不需要为静态子树创建新节点,是什么意思?

前面介绍了每次重新渲染 都会使用最新的状态生成一份全新的VNode与旧的VNode进行对比。而在生成VNode的过程中,如果发现一个节点被标记为静态子树,那么除了首次渲染会生成节点之外,在重新渲染时并不会生成新的子节点树,而是克隆已存在的静态子树。

在虚拟dom中打补丁的过程可以被跳过,又是什么意思?

两个节点对比并更新dom时,如果是两个静态节点,旧不需要进行对比更新dom操作,直接跳过。

优化器的内部原理实现主要分为两个步骤:
        1.在AST中找出所有静态节点并打上标记;

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

落实在AST中,静态节点指的是static属性为true的节点,例如:

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

例如:
<div  id="el">Hello  {{ name }}</div>

经过优化后的AST:

{
  type:1,
  tag:"div",
  attrsList:[
    {
      name:"id",
      value:"el"
    }
  ],
  attrsMap:{
    id:"el"
  },
  children:[
    {
      type:2,
      expression:'"Hello "+_s(name)',
      text:'Hello  {{name}}',
      static:false // 标识
    }
  ],
  plain:false,
  attrs:[
    {
      name:"id",
      value:"'el'"
    }
  ],
  static:false, // 标识
  staticRoot:false  // 标识
}

在源码中是这样实现的:

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

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

找出所有静态子节点并不难,只需要从根节点开始,先判断根节点是不是静态根节点,再用相同的方式处理子节点,接着用同样的方式去处理子节点的子节点,知道所有子节点都被处理之后程序结束,这个过程叫做递归。

源码实现:

function isStatic(node){
  if(node.type === 2){
    // 带变量的动态文本节点
    return false
  }
  if(node.type === 3){
    // 不带变量的纯文本节点
    return true
  }
  return !!(npde.pre || (
    !node.hasBindings && // 没有动态绑定
    !node.if && !node.for && // 没有v-if或v-else
    !isBuiltInTag(node.tag) && // 不是内置标签
    isPlatformReserverTag(node.tag) && // 不是组件
    !isDirectChildOfTemplateFor(node) &&
    Object.keys(node).every(isStaticKey)
  ))
}

AST中,type的取值:

1 表示 元素节点;

2  表示 带变量的动态文本节点;

3  表示  不带变量的纯文本节点。

如果元素节点使用了指令v-pre,那么直接断定是一个静态节点;如果元素节点没有使用指令v-pre,那么它必须同时满足以下条件才会被认为是一个静态节点:

        1.不能使用动态绑定语法,也就是说标签上不能有以v-、@、:开头的属性;

        2.不能使用v-if、v-for或v-else指令;

        3.不能是内置标签,也就是说不能是slot或component;

        4.不能是组件,即标签必须是保留标签,例如:<div></div>是保留标签,<list />不是保留标签;

        5.当前节点的父节点不能是带v-for指定的template标签;

        6.节点中不存在动态节点才会有的属性(v-for、v-if、v-else、v-else-if、v-once等)。

递归是从上向下依次标记,那么怎么标记父节点是静态根节点呢?

在字节的点被打完标记之后,需要判断它是否是静态节点,如果不是,那么它的父节点也不可能是静态节点,此时需要将父节点的static属性设置为false。

具体代码如下:

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
      }
    }
  }
}

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

从根节点开始向下一层一层地用递归方式去找。不一样的是,如果一个节点被判定为静态根节点,那么将不会继续向它的子级继续寻找。因为静态子树肯定只有一个根,就是最上面的那个静态节点,如下图所示。

 代码如下:

function markStaticRoots(node){
  if(node.type === 1){
    // 要使节点符合静态根节点的要求,它必须有子节点
    // 这个子节点不能是只有一个静态文本节点的子节点,否则优化成本将超过受益
    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])
      }
    }
  }
}

标记当前节点是否是静态根节点:如果节点是静态节点,并且有子节点,并且子节点不是只有一个文本类型的节点,那么该系欸按就是静态根节点,否则不是。排除两种情况:如果静态节点没有子节点,那么就不是静态根节点;如果静态节点只有一个文本节点,那么它也不是静态根节点。

标记子节点是否是静态根节点:循环子节点列表,然后将每一个子节点重复执行同一套逻辑即可。如果当前节点已经被标记为静态根节点,将不会在处理子节点,所以当node.staticRoot = true时就return掉。

3  总结

优化器的作用在AST中找出静态子树并打上标记,有两个好处:

        1.每次重新渲染时,不需要为静态子树创建新节点;

        2.在虚拟dom中打补丁的过程可以跳过。

优化器的内部实现分两个步骤:
        1.在AST中找到所有静态节点并打上标记;

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

通过递归的方式从上向下标记静态节点时,如果一个节点被标记为静态节点,但它的子节点却被标记为动态节点,就说明该节点不是静态节点,可以将它改为动态节点。静态节点的特征是它的子节点必须是静态节点。

标记完静态节点之后标记静态根节点,其标记方式也是使用递归的方式从上往下寻找,在寻找的过程中遇到的第一个静态节点就为静态根节点,同时不再向下继续寻找。

注:本文章来自于《深入浅出vue.js》(人民邮电出版社)阅读后的笔记整理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值