写文章不容易,点个赞呗兄弟
专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】html
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧node
【Vue原理】Compile - 源码版 之 optimize 标记静态节点 canvas
compile 三大步骤,parse 咱们已经讲完了缓存
如今到了第二步了,optimize这一步的内容好像不太多,可是很是重要,因而是一个更新性能优化, 很是重要ruby
先来看看 optimize 在什么位置,就在 parse 处理完以后,generate 以前性能优化
var ast = parse(template.trim(), options);
if (options.optimize !== false) {
optimize(ast, options);
}
var code = generate(ast, options);
复制代码
上面这段代码在函数 baseCompile 中,若是想了解的,看这里 Compile - 重新建实例到 compile结束的主要流程 bash
而 optimize 的做用是什么呢?ide
Vue官方注释
优化器的目标
遍历生成的模板AST树,检测纯静态的子树,即永远不须要更改的DOM。
一旦咱们检测到这些子树,咱们能够: 一、把它们变成常数,这样咱们就不须要了在每次从新渲染时为它们建立新的节点
二、在修补过程当中彻底跳过它们。svg
那是怎么作的呢?函数
给静态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个条件的审判(把上面的代码列了出来)
若是添加了指令 v-pre,那么 node.pre 为 true,代表全部节点都不用解析了
当节点有绑定 Vue属性的时候,好比指令,事件等,node.hasBindings 会为 true
一样,当 节点有 v-if 或者 v-for 的时候,node.if 或者 node.for 为true
由于这二者是要动态编译的,不属于静态范畴
因此只要是 slot 或者 component 都不多是静态节点
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
复制代码
是否是挺多的,哈哈,尤大真厉害,估计收集了不少,我以为应该有用,就全放上来了
看下这个函数的源码
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
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
// 不要将组件插槽内容设置为静态。
// 这就避免了
// 一、组件没法更改插槽节点
// 二、静态插槽内容没法热加载
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 是 标签元素
类型 2 是 文字表达式
类型 3 是 纯文本
一、必须是正常标签,也就是说自定义标签不须要再次处理
二、slot 会额外处理
三、有 inline-template 属性也会额外处理
只有有一个知足,就会进行额外处理
你能够看到源码中的最后一步
判断 node.ifCondition,而且若是 ifCondition 中保存的节点不是静态的话,那么这个 node 也不是静态节点
这个判断就很让我匪夷所思了
明明若是存在 v-if 的话,该节点在 一开始的 isStatic 中,就会被设置 node.static 为 false 了
为何还要在末尾 再判断一遍呢?
这里我以为好像有点多余?反正我没有想通 尤大 的想法啊啊啊啊啊,为了确保正确?
通过这一步,全部的节点,都会被添加上 static 属性,节点是否静态,一看便知
// 标记根节点是不是静态节点
function markStaticRoots(node) {
if (node.type === 1) return
// 要使一个节点符合静态根的条件,它应该有这样的子节点
// 不只仅是静态文本。不然,吊装费用将会增长
// 好处大于好处,最好老是保持新鲜。
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) {
var l = node.children.length
for (var i = 0; i < l; i++) {
markStaticRoots(
node.children[i]
);
}
}
}
复制代码
这个方法只会不断的寻找 静态的根节点,应该说是区域根节点吧,反正一个节点下面有马仔节点,这个节点就算是根节点
递归他的全部子孙,看看谁是静态根节点,若是是静态ast,就会被添加上 staticRoot 这个属性
markStaticRoots 也是递归调用的,可是并非会处理到全部节点
由于找到一个根节点是静态根节点后,就不会递归处理他的子节点了
而后咱们须要了解两个问题
一、markStaticRoot 和 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比对,加速更新
而带来的成本是什么呢?
一、维护静态模板存储对象
二、多层函数调用
如今咱们来简单解释下上面两种成本
一开始的时候,全部的静态根节点 都会被解析生成 VNode,而且被存在一个缓存对象中,就在 Vue.proto._staticTree 中
好比下面这个静态模板
解析后被存了进去
随着静态根节点的增长,这个存储对象也会愈来愈大,那么占用的内存就会愈来愈多
势必要减小一些没必要要的存储,全部只有纯文本的静态根节点就被排除了
这个问题涉及到 render 和 静态 render 的合做
举个例子
一个动态跟静态混合的模板
生成的 render 函数是这样的
with(this) {
return _c('div', [
_m(0),
( testStaticRender) ? _c('span') : _e()
])
}
复制代码
看到 _m(0) 了吗,这个函数就是去获取静态模板的
这样,静态模板的处理
就多了一个 _m 函数的调用,加上初期涉及到了不少函数的处理,其中包括上一步的存储
再者,既然纯文本节点不作优化
那么就是说更新时须要比对这部分喽?
可是纯文本的比对,就是直接 比较字符串 是否相等而已啊
消耗简直不要过小,那么这样,我还有必要去维护多一个静态模板缓存吗?
综上所述
只有纯文本子节点最好不要当作静态模板处理
以上只是我的的意淫想法,若有不一样意见能够提出
番外疑惑
我不由疑惑到,难道只有一个普通标签子节点的时候,好处难道会大一些吗?
能够看到模板放在了 staticRenderFns 上,作了静态模板处理
结果论出发的话,可能消耗的确大一些吧哈哈哈
更新的时候,会比较 div 和 span 和 span 内的纯文本,须要比较三遍
因此干脆选择 静态处理算了哈哈哈
鉴于本人能力有限,不免会有疏漏错误的地方,请你们多多包涵,若是有任何描述不当的地方,欢迎后台联系本人,有重谢