前言
笔者的整理出vue编译思想篇后,又整理了编译的parse部分。
今天准备的详细得讲解下vue在parse解析生成astElement后,编译过程中另外两个重要的部分optimize
和code generate
。
一、optimize
让我们先看下vue中关于这部分的注释:
/***Goaloftheoptimizer:walkthegeneratedtemplateASTtree*anddetectsub-treesthatarepurelystatic,i.e.partsof*theDOMthatneverneedstochange.**Oncewedetectthesesub-trees,wecan:**1.Hoistthemintoconstants,sothatwenolongerneedto*createfreshnodesforthemoneachre-render;*2.Completelyskiptheminthepatchingprocess.*/
大致意思就是不需要重新创建新的nodes,跳过patch的过程。
再次重申下vue的工作流程。
parse 解析template,生成astElement tree。
optimize 遍历 astElement, 给astElement 打上必要的staticRoot标签。
code generate,将astElement转换成函数的形式,但是是以string的形式存在,等待执行。
render函数,执行第三步得到的string,生成vnode 节点,也就是我们经常碎碎念念的虚拟dom了。
patch,对比新旧虚拟dom,生成真实的dom。
笔者花了漫长的时间写完了第一个过程
的解析部分,今天带来的源码解析是关于2和3部分的。
而optimize的优化的部分就是在patch过程中直接忽略的。 下面给出这块的模块:
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}
1)我们先讲下功能函数isStaticKey:
首先我们得知道options.staticKeys其实是一个string:
"staticClass,staticStyle"
genStaticKeysCached的源码是:
constgenStaticKeysCached=cached(genStaticKeys);exportfunctioncached<F:Function>(fn:F):F{constcache=Object.create(null)return(functioncachedFn(str:string){consthit=cache[str]returnhit||(cache[str]=fn(str))}:any)}functiongenStaticKeys(keys:string):Function{returnmakeMap('type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap'+(keys?','+keys:''))}
1.genStaticKeysCached 就是 cached的 return 也就是 cachedFn, 所以
genStaticKeysCached(options.staticKeys||'')
就是
cachedFn(options.staticKeys||''),
2.因为一开始cache并不存在,所以会执行
cache[options.staticKeys]=fn(options.staticKeys);
而fn函数是传递进来的函数 genStaticKeys
: 也就是执行的其实是:
cache[options.staticKeys]=genStaticKeys(options.staticKeys);
在替换options.staticKeys 到 "staticClass,staticStyle"
,得到:
cache["staticClass,staticStyle"]=genStaticKeys("staticClass,staticStyle")
所以: 最终结果是 :
markUp('type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap,staticClass,staticStyle')
这个函数是干嘛的? 源码如下:
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}0
很容易发现,其实:
字符串'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap,staticClass,staticStyle'
,会变成对象:
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}1
最后返回的函数其实是:
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}2
恍然大悟是不是,就是判断到底是不是静态key,所谓的静态key就是上诉的这些属性。 这个函数下面会使用到的。
2)isPlatformReservedTag功能函数详解:
这个函数很简单,其实就是判断是否为平台的tag,web平台也就是普通的标签和svg标签。
3)markStatic函数。
这个函数其实是辅助函数,就是标志ast树的每个节点是否为静态的节点,具体的判断方式,需要拿源码的进行分析。
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}3
先从宏观的角度来思考符合static的标准。
node.type为2的时候一定不是静态节点,因为这个标志代表的是表达式。具体细节请参考笔者的parse篇
node.type 为3的时候一定是静态节点,因为这是文本。
node.type为1的时候这么考量的呢?? 很复杂
需要符合下面两个要求:
1.符合一系列的要求,这块需要查看isStatic的源码部分。
2.子节点必须都是静态节点,或者if的许多块级也不能存在动态节点。
两者缺一不可,我们一一来分析下:
先来看第一点,isStatic源码如下:
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}4
第一点中所谓的一些列的要求,其实就是这个函数的return部分。
当是node.pre,的时候就是静态节点。
或同时满足以下所有条件。
1). 没有动态bind如: v-focus :focus @focus .focus
2)没有if,没有for。
3)非built-in 标签,也就是不是 slot或着component标签。
4)必须是平台标签。
5)不是template for 中的一个节点的直属孩子。
6)所有属性都是静态属性,来自于前面分析的isStaticKey函数的判断。
再看第二点
也就是说当上面第一点当所有要求都已经达到了,依然只是基本条件。
还有要求就是所有的孩子节点都要满足上述当条件,才能算得上是静态节点
。
4)markStaticRoots函数详解
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}5
markStaticRoots函数主要给所有符合要求的节点打上两个标志:
staticInFor
和staticRoot
,而这个标志才是真正能用于下一个大阶段code generate。
staticInFor
非常简单,只要节点的祖先节点处于for当中,且自身有static或once属性,就会被标记。
再看看staticRoot,它的条件被分为两个部分,
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}6
条件一的意思是满足static且有孩子节点。
条件二的意思是。把条件一当中的只有一个孩子,且是动态节点的情况去掉。 举个例子:
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}7
这种情况下div是不能算是staticRoot。
相信会有人心中会有疑问,为啥?
这点我们后面会在code generate中给出答案。
总结
1.父节点是static,孩子一定是static,,子节点未必是staticRoot,父节点一定是staticRoot。
2.真正起到作用的是staticInFor和staticRoot属性。
二、code generate。
再次强调,generate生成的是字符串,短函数类的字符串,为后面的render的执行做准备。本质是对象形式转换成短函数形式。
解析前的准备
本质上,我们会从一个astElement Tree
变成以函数为单位的树节点:
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}8
那么一共有哪些短函数呢?
关于短函数,其实是在renderMixin中定义的,让我们看看。
exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)returnisStaticKey=genStaticKeysCached(options.staticKeys||'')//isPlatformReservedTag=options.isReservedTag||no//firstpass:markallnon-staticnodes.markStatic(root)//模块一//secondpass:markstaticroots.markStaticRoots(root,false)//模块二}9
我们需要明白 执行玩短函数得到短是另一种树,vnode树。 所以这些短函数要么是创建一些vnode节点,要么是为vnode节点创建一些属性。
所以我们短函数格式主要是
"staticClass,staticStyle"0
第一个参数是tag名称,第二个是属性,第三个参数则是子函数,也就是子节点。
接下来让我们看看主函数把。
1)主流程分析。
主流程是generate函数开始的。
"staticClass,staticStyle"1
先看下我们return的内容吧
"staticClass,staticStyle"2
可以看到一共两个属性render和staticRenderFns,
简单先说下,optimize部分标记的staticRoot和staticInFor不会放在code当中,而是放在staticRenderFns。 具体的玩法,后面会有详细的讲解。
函数的话,主要只有两个过程,上面的代码我已经将两个模块分别给出了注释。
我们先来看看第一个块的具体情况:
"staticClass,staticStyle"3
其实上面的注释并不准确,state还有很多功能函数,CodegenState本身是个class,具体如下:
"staticClass,staticStyle"4
需要说明的是directive其实只是生成短函数,真是短处理都是在render函数和patch部分完成的。如果对vue源码的周期还不熟悉的同学,请翻阅笔者的parse思想篇。
再看看第二模块的:
"staticClass,staticStyle"5
可以看到,ast的兜底是短函数_c("div")
,否则进入核心函数genElement。
genElement是codegen流程最重要的功能函数,处理短函数的绝大部分情况。
看下源码:
"staticClass,staticStyle"6
ifelse的分支一共分为8种情况,让我们来一一分析下。
(1)genStatic
先来看看判断的条件
"staticClass,staticStyle"7
恍然大悟有没有,原来optimize部分的staticRoot是在这个地方处理的,之前我们有说过,staticRoot的节点,在patch的过程中,只做第一次渲染,后面的对比是完全忽略的。
我们需要考虑两个问题
1.静态节点,是怎么完成一次渲染的?
2.静态节点,是在哪里判断不用再次渲染的?
先看看静态节点怎么渲染的,先观看genStatic代码:
"staticClass,staticStyle"8
state.staticRenderFns以数组的形式保存静态渲染函数,短函数真正执行的守候,根据传递的index,去数组拿需要渲染的静态函数。
看下_m短函数的原函数是renderStatic:
"staticClass,staticStyle"9
renderStatic的主要做了两件事,那就是生成vnode和打上static标记。所谓的标记就是:
constgenStaticKeysCached=cached(genStaticKeys);exportfunctioncached<F:Function>(fn:F):F{constcache=Object.create(null)return(functioncachedFn(str:string){consthit=cache[str]returnhit||(cache[str]=fn(str))}:any)}functiongenStaticKeys(keys:string):Function{returnmakeMap('type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap'+(keys?','+keys:''))}0
当然这些参数在patch环节会发挥相应的作用,这里不做赘述,笔者后续的源码分析模块会有patch的源码解析。
2)
constgenStaticKeysCached=cached(genStaticKeys);exportfunctioncached<F:Function>(fn:F):F{constcache=Object.create(null)return(functioncachedFn(str:string){consthit=cache[str]returnhit||(cache[str]=fn(str))}:any)}functiongenStaticKeys(keys:string):Function{returnmakeMap('type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap'+(keys?','+keys:''))}1
genOnce情况主要分为3种:
1.v-if标签
2.staticInFor (再次遇到optimize阶段打的标记,这里不做太多赘述)
3.genStatic。
所以genOnce的基础就是genStatic,但是当持有v-if的标签的时候,就必须走genIf,这是因为if的形式是将节点放在v-if的标签的ifConditions数组当中的,根据具体的条件进行渲染目标模块,并不具备一次渲染的情况
至于staticInFor,情况则比较复杂了。
我们必须知道Vue的数组 后续补充。。