资讯专栏INFORMATION COLUMN

vue原理Compile之optimize标记静态节点源码示例

3403771864 / 247人阅读

  引言

  optimize的内容虽然不多,但十分重要,它是一个更新性能优化,现在来说说:

  首先找到optimize位置,就在 parse 处理完之后,generate 之前

  var ast = parse(template.trim(), options);
  if (options.optimize !== false) {
  optimize(ast, options);
  }
  var code = generate(ast, options);

  上面这段代码在函数 baseCompile 中,如果想了解的,看这里Compile - 从新建实例到 compile结束的主要流程

  而 optimize 的作用是什么呢?

  Vue官方注释

  优化器的目标

  遍历生成的模板AST树,检测纯静态的子树,即永远不需要更改的DOM。

  一旦我们检测到这些子树,我们可以:

  1、将它们变成常数,就是为了在每次重新渲染时不需要再为它们创建新的节点

  2、在修补过程中完全跳过它们。

  那是怎么做的呢?

  给静态ast节点设置属性 static,当节点时静态是

  el.static = true

  下面就来看下源码

  function optimize(root, options) {
  if (!root) return
  // first pass: mark all non-static nodes.
  markStatic$1(root);
  // second pass: mark static roots.
  markStaticRoots(root);
  }

  里面主要调用了两个函数,这两个函数会分别分析

  但是在此之前,我们先来看一个函数,这个函数就是 判断静态节点的 主力函数

  直接传入 ast 节点,各种组合判断,然后给 ast 节点添加上 static 属性

  function isStatic(node) {
  // 文字表达式
  if (node.type === 2) return false
  // 纯文本
  if (node.type === 3) return true
  return (
  // 设置了 v-pre 指令,表示不用解析
  node.pre ||
  (
  !node.hasBindings && // 没有动态绑定
  ! node.if && !node.for && // 不存在v-if ,v-for 指令
  ! ['slot','component'].indexOf(node.tag)>-1 && // 需要编译的标签
  isPlatformReservedTag(node.tag) && // 正常html 标签
  ! isDirectChildOfTemplateFor(node) &&
  Object.keys(node).every(isStaticKey)
  )
  )
  }

  如果要判断为静态节点,就要经过下面7个条件的审判(把上面的代码列了出来)

  1 是否存在 v-pre

  如果添加了指令 v-pre,那么 node.pre 为 true,表明所有节点都不用解析了

  2 不能存在 node.hasBindings

  当节点有绑定 Vue属性的时候,比如指令,事件等,node.hasBindings 会为 true

  3 不能存在 node.if 和 node.for

  同样,当 节点有 v-if 或者 v-for 的时候,node.if 或者 node.for 为true

  4 节点名称不能是slot 或者 component

  因为这两者是要动态编译的,不属于静态范畴

  所以只要是 slot 或者 component 都不可能是静态节点

  5 isPlatformReservedTag(node.tag)

  isPlatformReservedTag 是用于判断该标签是否是正常的HTML 标签,有什么标签呢?

  标签必须是正常HTML标签,如下

  html,body,base,head,link,meta,style,title

  address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section

  div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul

  a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby

  s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video

  embed,object,param,source,canvas,script,noscript,del,ins

  caption,col,colgroup,table,thead,tbody,td,th,tr

  button,datalist,section,form,input,label,legend,meter,optgroup,option

  output,progress,select,textarea

  details,dialog,menu,menuitem,summary

  content,element,shadow,template,blockquote,iframe,tfoot

  svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face

  foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern

  polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view

  是不是挺多的,哈哈,尤大真厉害,估计收集了很多,我觉得应该有用,就全放上来了

  6 isDirectChildOfTemplateFor(node)

  看下这个函数的源码

  function isDirectChildOfTemplateFor(node) {
  while (node.parent) {
  node = node.parent;
  if (node.tag !== 'template') {
  return false
  }
  if (node.for) {
  return true
  }
  }
  return false
  }

  表明了节点父辈以上所有节点不能是 template 或者 带有 v-for

  7 Object.keys(node).every(isStaticKey)

  isStaticKey是一个函数,用于判断传入的属性是否在下面的范围内

  type,tag,attrsList,attrsMap,plain,parent,children,attrs

  比如这样的 ast

  <div style="" ></div>
  {
  attrsList: []
  attrsMap: {style: ""}
  children: []
  parent: undefined
  plain: false
  tag: "div"
  type: 1
  }

  这段代买就是表达ast 的所有属性通过 isStaticKey 判断之后,会在上面逐一列出的属性范围中,且是静态属性,因此,这是静态节点

  而当你存在之外的其他属性的时候,这个节点就不是静态ast

  然后下面就来看 optimize 中出现的两个函数把

  markStatic$1 和 markStaticRoot

  标记静态节点

  怎么标记一个节点是否是静态节点呢,就在 markStatic$1 中进行处理

   // 标记节点是否是静态节点
  function markStatic$1(node) {
  node.static = isStatic(node);
  if (node.type !== 1) return
  // 不要将组件插槽内容设置为静态。
  // 这就避免了
  // 1、组件无法更改插槽节点
  // 2、静态插槽内容无法热加载
  if (
  // 正常 thml 标签 才往下处理,组件之类的就不可以
  !isPlatformReservedTag(node.tag) &&
  // 标签名是 slot 才往下处理
  node.tag !== 'slot' &&
  // 有 inline-tempalte 才往下处理
  node.attrsMap['inline-template'] == null
  ) {
  return
  }
  // 遍历所有孩子,如果孩子 不是静态节点,那么父亲也不是静态节点
  var l = node.children.length
  for (var i = 0;i < l; i++) {
  var child = node.children[i];
  // 递归设置子节点,子节点再调用子节点
  markStatic$1(child);
  if (!child.static) {
  node.static = false;
  }
  }
  if (node.ifConditions) {
  var c = node.ifConditions.length
  for (var j = 1; j < c; j++) {
  // block 是 节点的 ast
  var block = node.ifConditions[j].block;
  markStatic$1(block);
  if (!block.static) {
  node.static = false;
  }
  }
  }
  }

  这个方法做了下面三种事情

  1 isStatic 这个方法对 ast 节点本身进行初步判断

  进行初步静态节点判断

  2 判断静态节点的额外的处理

  给节点本身判断完是否静态节点之后,就需要检查所有的子孙节点。后面就是逐层递归子节点,如果某子节点不是静态节点,那么父节点就不能是静态节点,现在就说说这样的节点处理,其实并不是所有节点都会进行特殊处理,是有条件的

  节点类型是 1

  类型 1 是 标签元素

  类型 2 是 文字表达式

  类型 3 是 纯文本

  3 是正常 html 标签,标签是 slot,存在 inline-template 属性

  1、必须是正常标签,也就是说自定义标签不需要再次处理

  2、slot 会额外处理

  3、有 inline-template 属性也会额外处理

  只有有一个满足,就会进行额外处理

  疑点

  你可以看到源码中的最后一步

  判断 node.ifCondition,并且如果 ifCondition 中保存的节点不是静态的话,那么这个 node 也不是静态节点

  这个判断就很让我匪夷所思了

  明明如果存在 v-if 的话,该节点在 一开始的 isStatic 中,就会被设置 node.static 为 false 了

  为什么还要在末尾 再判断一遍呢?

  这里多余的一步,但为保证正常运行还是加入在里面。

  经过这一步,所有的节点,都会被添加上 static 属性,节点是否静态,一看便知

  标记静态根节点

  // 标记根节点是否是静态节点
  function markStaticRoots(node) {
  if (node.type === 1) return
  // 要使一个节点符合静态根的条件,它应该有这样的子节点
  // 不仅仅是静态文本。否则,吊装费用将会增加
  // 好处大于好处,最好总是保持新鲜。
  if (
  // 静态节点
  node.static &amp;&amp;
  // 有孩子
  node.children.length &amp;&amp;
  // 孩子有很多,或者第一个孩子不是纯文本
  ! (node.children.length === 1
  &amp;&amp; node.children[0].type === 3
  )
  ) {
  node.staticRoot = true;
  return
  }
  else {
  node.staticRoot = false;
  }
  if (node.children) {
  var l = node.children.length
  for (var i = 0; i &lt; l; i++) {
  markStaticRoots(
  node.children[i]
  );
  }
  }
  }

  这样也只是可以寻找 静态的根节点,应该说是区域根节点吧,反正一个节点下面有马仔节点,这个节点就算是根节点。

  递归他的所有子孙,由此判断哪个是静态根节点,如果是静态ast,就会被添加上 staticRoot 这个属性

  markStaticRoots 也是递归调用的,但是并不是会处理到所有节点

  因为找到一个根节点是静态根节点后,就不会递归处理他的子节点了

  然后我们需要了解两个问题

  1、markStaticRoot 和 markStatic$1 的区别

  2、判断静态根节点的依据是什么

  markStaticRoots 和 markStatic$1 有什么区别?

  找出静态根节点才是性能优化的最终作用者

  markStatic$1 这个函数只是为 markStaticRoots 服务的,是为了先把每个节点都处理之后,更加方便快捷静态根节点,可以说是把功能分开,这样处理的逻辑就更清晰了

  划分好之后,就只用找 部分的根节点(区域负责人就好了)

  这个就是因为markStatic$1 添加的 static 属性,我全局搜索,并没有在处理DOM和 生成 render上使用过

  而 markStaticRoots 添加的 staticRoot ,在生成 render 上使用了

  而且再 根据 markStaticRoots 写的功能逻辑 并 使用了 static 属性进行判断

  所以我认为 markStatic$1 是为 markStaticRoots 服务的一个函数

  被判断为静态根节点的条件是什么?

  1该节点的所有子孙节点都是静态节点

  而 node.static = true 则表明了其所有子孙都是静态的,否则上一步就被设置为 false 了

  2必须存在子节点

  3子节点不能只有一个 纯文本节点

  简单来说就是这个只有一个纯文本子节点时,这个点不能是静态根节点。且只有纯文本子节点时,他是静态节点,但是不是静态根节点。静态根节点是optimize 优化的条件,没有静态根节点,说明这部分不会被优化

  注意在Vue 官方说明中写的是,如果子节点只有一个纯文本节点,如果优化的话,带来的成本就比好处多了,因此,选择不优化

  但为什么子节点只有是静态文本时,成本会大?现在来说说我个人的看法:

  首先,优化不仅可以减少DOM比对,而且可以提升加速更新

  而带来的成本是什么呢?

  1、维护静态模板存储对象

  2、多层函数调用

  现在我们来简单解释下上面两种成本

  1 维护静态模板存储对象

  首先所有的静态根节点 都会被解析生成 VNode,并且被存在一个缓存对象中,就在 Vue.proto._staticTree 中,比如下面这个静态模板

1.png

  之后就解析,且存进去

2.png

  但逐渐存储内容越多,越大,占用空间就成为问题。减少储存成为必须,所有只有纯文本的静态根节点就被排除了

  2 多层函数调用

  这个问题涉及到 render 和 静态 render 的合作

  举个例子

  一个动态跟静态混合的模板

3.png

  生成的 render 函数是这样的

  with(this) {
  return _c('div', [
  _m(0),
  ( testStaticRender) ? _c('span') : _e()
  ])
  }

  看到 _m(0) 了吗,这个函数就是去获取静态模板的。现在说说,态模板的处理,就多了一个 _m 函数的调用,加上初期涉及到了很多函数的处理,其中包括上一步的存储,之后再去,既然纯文本节点不做优化,这样就说明更新时需要比对这部分喽?

  但是纯文本的比对,就是直接 比较字符串 是否相等而已啊

  消耗简直不要太小,那么这样,我还有必要去维护多一个静态模板缓存吗?

  其实总来说就是:

  只有纯文本子节点最好不要当做静态模板处理

4.png

  可以看到模板放在了 staticRenderFns 上,做了静态模板处理

5.png

  看到了吧。消耗大了吧.

  更新的时候,会比较 div 和 span 和 span 内的纯文本

  vue原理Compile之optimize标记静态节点源码示例的详细内容讲述完毕,欢迎大家看后续精彩内容。


文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/127837.html

相关文章

  • Vue原理Compile - 源码 optimize 标记静态节点

    摘要:一旦我们检测到这些子树,我们可以把它们变成常数,这样我们就不需要了在每次重新渲染时为它们创建新的节点在修补过程中完全跳过它们。否则,吊装费用将会增加好处大于好处,最好总是保持新鲜。 写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,...

    Soarkey 评论0 收藏0
  • Vue原理Compile - 白话版

    摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理白话版终于到了要讲白话的时候了 写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究...

    dingding199389 评论0 收藏0
  • JS每日一题:简述一下Vue.js的template编译过程?

    摘要:问简述一下的编译过程先上一张图大致看一下整个流程从上图中我们可以看到是从后开始进行中整体逻辑分为三个部分解析器将模板字符串转换成优化器对进行静态节点标记,主要用来做虚拟的渲染优化代码生成器使用生成函数代码字符串开始前先解释一下抽象 20190215问 简述一下Vue.js的template编译过程? 先上一张图大致看一下整个流程showImg(https://image-static....

    NicolasHe 评论0 收藏0
  • FE.SRC-Vue实战与原理笔记

    摘要:超出此时间则渲染错误组件。元素节点总共有种类型,为表示是普通元素,为表示是表达式,为表示是纯文本。 实战 - 插件 form-validate {{ error }} ...

    wangjuntytl 评论0 收藏0
  • 聊聊Vue.js的template编译

    摘要:具体可以查看抽象语法树。而则是带缓存的编译器,同时以及函数会被转换成对象。会用正则等方式解析模板中的指令等数据,形成语法树。是将语法树转化成字符串的过程,得到结果是的字符串以及字符串。里面的节点与父节点的结构类似,层层往下形成一棵语法树。 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。 文...

    gnehc 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<