首页>>前端>>Vue->Vue编译之 optimize和 code generate源码解析

Vue编译之 optimize和 code generate源码解析

时间:2023-11-29 本站 点击:0

前言

笔者的整理出vue编译思想篇后,又整理了编译的parse部分。

今天准备的详细得讲解下vue在parse解析生成astElement后,编译过程中另外两个重要的部分optimizecode 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函数主要给所有符合要求的节点打上两个标志:

staticInForstaticRoot,而这个标志才是真正能用于下一个大阶段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的数组 后续补充。。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/834.html