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

2691 0
王子 2022-10-21 15:26:53 | 显示全部楼层 |阅读模式
目录

    引言而 optimize 的作用是什么呢?
      1 是否存在 v-pre2 不能存在 node.hasBindings3 不能存在 node.if 和 node.for4 节点名称不能是slot 或者 component5 isPlatformReservedTag(node.tag)6 isDirectChildOfTemplateFor(node)7 Object.keys(node).every(isStaticKey)
    标记静态节点
      1 isStatic 这个方法对 ast 节点本身进行初步判断2 判断静态节点的额外的处理
        节点类型是 1
      3 是正常 html 标签,标签是 slot,存在 inline-template 属性
        疑点

    标记静态根节点
      markStaticRoots 和 markStatic$1 有什么区别?被判断为静态根节点的条件是什么?为什么子节点只有是静态文本时,成本会大?
        1 维护静态模板存储对象2 多层函数调用




引言

optimize这一步的内容好像不太多,但是非常重要,于是是一个更新性能优化, 非常重要
先来看看 optimize 在什么位置,就在 parse 处理完之后,generate 之前
  1. var ast = parse(template.trim(), options);
  2. if (options.optimize !== false) {
  3.     optimize(ast, options);
  4. }
  5. var code = generate(ast, options);
复制代码
上面这段代码在函数 baseCompile 中,如果想了解的,看这里 Compile - 从新建实例到 compile结束的主要流程

而 optimize 的作用是什么呢?

Vue官方注释
优化器的目标
遍历生成的模板AST树,检测纯静态的子树,即永远不需要更改的DOM。
一旦我们检测到这些子树,我们可以:
1、把它们变成常数,这样我们就不需要了在每次重新渲染时为它们创建新的节点
2、在修补过程中完全跳过它们。
那是怎么做的呢?
给静态ast节点设置属性 static,当节点时静态是
  1. el.static = true
复制代码
下面就来看下源码
  1. function optimize(root, options) {   
  2.     if (!root) return
  3.     // first pass: mark all non-static nodes.
  4.     markStatic$1(root);   
  5.     // second pass: mark static roots.
  6.     markStaticRoots(root);
  7. }
复制代码
里面主要调用了两个函数,这两个函数会分别分析
但是在此之前,我们先来看一个函数,这个函数就是 判断静态节点的 主力函数
直接传入 ast 节点,各种组合判断,然后给 ast 节点添加上 static 属性
  1. function isStatic(node) {   
  2.     // 文字表达式
  3.     if (node.type === 2) return false
  4.     // 纯文本
  5.     if (node.type === 3) return true
  6.     return (        
  7.         // 设置了 v-pre 指令,表示不用解析
  8.         node.pre ||
  9.         (
  10.             !node.hasBindings && // 没有动态绑定
  11.             ! node.if && !node.for && // 不存在v-if ,v-for 指令
  12.             ! ['slot','component'].indexOf(node.tag)>-1 && // 需要编译的标签
  13.             isPlatformReservedTag(node.tag) && // 正常html 标签
  14.             ! isDirectChildOfTemplateFor(node) &&
  15.             Object.keys(node).every(isStaticKey)
  16.         )
  17.     )
  18. }
复制代码
如果要判断为静态节点,就要经过下面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)

看下这个函数的源码
  1. function isDirectChildOfTemplateFor(node) {   
  2.     while (node.parent) {
  3.         node = node.parent;        
  4.         if (node.tag !== 'template') {            
  5.             return false
  6.         }        
  7.         if (node.for) {            
  8.             return true
  9.         }
  10.     }   
  11.     return false
  12. }
复制代码
表明了节点父辈以上所有节点不能是 template 或者 带有 v-for

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

isStaticKey是一个函数,用于判断传入的属性是否在下面的范围内
type,tag,attrsList,attrsMap,plain,parent,children,attrs
比如这样的 ast
  1. <div style="" ></div>
  2. {   
  3.     attrsList: []
  4.     attrsMap: {style: ""}
  5.     children: []
  6.     parent: undefined
  7.     plain: false
  8.     tag: "div"
  9.     type: 1
  10. }
复制代码
上面的 ast 的所有属性通过 isStaticKey 判断之后,都在上面列出的属性范围中,都是静态属性,所以这就是一个静态节点
而当你存在之外的其他属性的时候,这个节点就不是静态ast
然后下面就来看 optimize 中出现的两个函数把
markStatic$1 和 markStaticRoot

标记静态节点

怎么标记一个节点是否是静态节点呢,就在 markStatic$1 中进行处理
  1. // 标记节点是否是静态节点
  2. function markStatic$1(node) {
  3.     node.static = isStatic(node);   
  4.     if (node.type !== 1) return
  5.     // 不要将组件插槽内容设置为静态。
  6.     // 这就避免了
  7.     // 1、组件无法更改插槽节点
  8.     // 2、静态插槽内容无法热加载
  9.     if (        
  10.         // 正常 thml 标签 才往下处理,组件之类的就不可以
  11.         !isPlatformReservedTag(node.tag) &&
  12.         // 标签名是 slot 才往下处理
  13.         node.tag !== 'slot' &&
  14.         // 有 inline-tempalte 才往下处理
  15.         node.attrsMap['inline-template'] == null
  16.     ) {        
  17.         return
  18.     }   
  19.     // 遍历所有孩子,如果孩子 不是静态节点,那么父亲也不是静态节点
  20.     var l = node.children.length   
  21.     for (var i = 0;i < l; i++) {        
  22.         var child = node.children[i];  
  23.         // 递归设置子节点,子节点再调用子节点
  24.         markStatic$1(child);        
  25.         if (!child.static) {
  26.             node.static = false;
  27.         }
  28.     }   
  29.     if (node.ifConditions) {   
  30.         var c = node.ifConditions.length  
  31.         for (var j = 1; j < c; j++) {   
  32.             // block 是 节点的 ast
  33.             var block = node.ifConditions[j].block;
  34.             markStatic$1(block);  
  35.             if (!block.static) {
  36.                 node.static = false;
  37.             }
  38.         }
  39.     }
  40. }
复制代码
这个方法做了下面三种事情

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 属性,节点是否静态,一看便知

标记静态根节点
  1. // 标记根节点是否是静态节点
  2. function markStaticRoots(node) {   
  3.     if (node.type === 1) return
  4.     // 要使一个节点符合静态根的条件,它应该有这样的子节点
  5.     // 不仅仅是静态文本。否则,吊装费用将会增加
  6.     // 好处大于好处,最好总是保持新鲜。
  7.     if (        
  8.         // 静态节点
  9.         node.static &&        
  10.         // 有孩子
  11.         node.children.length &&        
  12.         // 孩子有很多,或者第一个孩子不是纯文本
  13.         ! (node.children.length === 1
  14.             && node.children[0].type === 3
  15.           )
  16.     ) {
  17.         node.staticRoot = true;        
  18.         return
  19.     }
  20.     else {
  21.         node.staticRoot = false;
  22.     }   
  23.     if (node.children) {   
  24.         var l = node.children.length   
  25.         for (var i = 0; i < l; i++) {
  26.             markStaticRoots(
  27.                 node.children[i]
  28.             );
  29.         }
  30.     }
  31. }
复制代码
这个方法只会不断的寻找 静态的根节点,应该说是区域根节点吧,反正一个节点下面有马仔节点,这个节点就算是根节点
递归他的所有子孙,看看谁是静态根节点,如果是静态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 中
比如下面这个静态模板


解析后被存了进去


随着静态根节点的增加,这个存储对象也会越来越大,那么占用的内存就会越来越多
势必要减少一些不必要的存储,所有只有纯文本的静态根节点就被排除了

2 多层函数调用

这个问题涉及到 render 和 静态 render 的合作
举个例子
一个动态跟静态混合的模板


生成的 render 函数是这样的
  1. with(this) {   
  2.     return _c('div', [
  3.         _m(0),
  4.         ( testStaticRender) ? _c('span') : _e()
  5.     ])
  6. }
复制代码
看到 _m(0) 了吗,这个函数就是去获取静态模板的
这样,静态模板的处理
就多了一个 _m 函数的调用,加上初期涉及到了很多函数的处理,其中包括上一步的存储
再者,既然纯文本节点不做优化
那么就是说更新时需要比对这部分喽?
但是纯文本的比对,就是直接 比较字符串 是否相等而已啊
消耗简直不要太小,那么这样,我还有必要去维护多一个静态模板缓存吗?
综上所述
只有纯文本子节点最好不要当做静态模板处理
以上只是个人的意淫想法,如有不同意见可以提出
番外疑惑
我不禁疑惑到,难道只有一个普通标签子节点的时候,好处难道会大一些吗?


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


结果论出发的话,可能消耗的确大一些吧哈哈哈
更新的时候,会比较 div 和 span 和 span 内的纯文本
以上就是vue原理Compile之optimize标记静态节点源码示例的详细内容,更多关于Compile optimize标记静态节点的资料请关注中国红客联盟其它相关文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行