笔者公司的前端小组掀起了Vue源码学习小组,先后几个月的共同窗习,让小组成员都已经对Vue对大体框架有了个模糊对轮廓。 如今已经进入第二阶段:整理。javascript
咱们将小组分为四个部分,vue对整理也分为三个大模块:数据绑定、从template到vnode、vnode转化为dom对patch。html
笔者对小组被分到了template到vnode对部分,拿到手后,感受内容比较多,就先将内容根据源码对布局分为两小块:parse 和 render过程。前端
再者,古人云:兵马未动,粮草先行。笔者打算先介绍思想和思路,而后在具体到一些细枝末节。vue
本文准备到就是parse部分思想。java
咱们都知道使用vue到时候,咱们使用定制样式到几种方式大体为:node
咱们须要明白到是不管是哪一种方式,咱们最终的目的都是
生成以vnode为单位vdom树express
而生成的vdom树,最终是用于patch过程当中,生成真实dom节点。浏览器
因此咱们的编译的最终目的是得到:缓存
形式大体以下图:性能优化
暂时不用管每一个属性的做用,咱们已经知道咱们的目标是怎样的了。那么还须要知道的是 起始点和过程。
正常状况下,若是咱们这样初始化的话:
new Vue({ el: '#el', template: ` <div> <div v-for="(item,index) in options" :key="item.id"> {{item.id}} <div>{{item.text}}</div> </div> </div> `, data: { name:"dinglei", options: [ { id: 1, text: 'Hello' }, { id: 2, text: 'World' } ] } }) 复制代码
那咱们的初始化的状态则是 template的 一串 String。
固然初始化为String的写法仍是满多的。 好比:
一串String的模版
到 virtue dom tree
的过程。一次性
到位的过程。多是由于尤大的设计的vnode和原生的dom属性差距过大,直接编译成vnode很差完成。其次是考虑到性能优化等方面。因此vue的编译过程实际上是分为三个过程:
分别对应三个过程:
流程大体以下图:
接下来咱们将进入本文的主题
首先须要明白astElement包括哪些属性。在vue源码 flow文件目录下的compiler.js能够找到astElement的模型
declare type ASTElement = { type: 1; tag: string; attrsList: Array<ASTAttr>; attrsMap: { [key: string]: any }; rawAttrsMap: { [key: string]: ASTAttr }; parent: ASTElement | void; children: Array<ASTNode>; start?: number; end?: number; processed?: true; static?: boolean; staticRoot?: boolean; staticInFor?: boolean; staticProcessed?: boolean; hasBindings?: boolean; text?: string; attrs?: Array<ASTAttr>; dynamicAttrs?: Array<ASTAttr>; props?: Array<ASTAttr>; plain?: boolean; pre?: true; ns?: string; component?: string; inlineTemplate?: true; transitionMode?: string | null; slotName?: ?string; slotTarget?: ?string; slotTargetDynamic?: boolean; slotScope?: ?string; scopedSlots?: { [name: string]: ASTElement }; ref?: string; refInFor?: boolean; if?: string; ifProcessed?: boolean; elseif?: string; else?: true; ifConditions?: ASTIfConditions; for?: string; forProcessed?: boolean; key?: string; alias?: string; iterator1?: string; iterator2?: string; staticClass?: string; classBinding?: string; staticStyle?: string; styleBinding?: string; events?: ASTElementHandlers; nativeEvents?: ASTElementHandlers; transition?: string | true; transitionOnAppear?: boolean; model?: { value: string; callback: string; expression: string; }; directives?: Array<ASTDirective>; forbidden?: true; once?: true; onceProcessed?: boolean; wrapData?: (code: string) => string; wrapListeners?: (code: string) => string; // 2.4 ssr optimization ssrOptimizability?: number; // weex specific appendAsTree?: boolean; }; 复制代码
角色
1.就是识别器(1)
利用正则从前到后识别全部敏感字段。如(标签、事件、迭代、数据绑定、插槽等等)
2.开始标签 例如<div>{{test}}</div>
里的<div>
,识别器(1)
一旦识别到此类标签,就会使用开始函数start (5)
来对开始标签作统一处理,而start函数 会建立一个astElement并 存放到stack
当中,当时建立的方式是利用建立函数(3)
建立的,同时也会处理一些非组件属性(具体如v-model、v-if、v-for)。这里须要注意的是:
浏览器等识别
方式达到目标一致。具体点击此处3.识别到闭合标签,则会转交给end函数
来处理,end函数 会摘取原生属性如:id 、 class等等、还有ref、slot、component、key等等,并跟上下文(4)
创建起父子关系
。最终造成了树的结构。
4.一直重复 1~3直到stack
没有任何标签。
流程图大体以下:
其中的细节特别多,若是有兴趣请关注咱们的动态,咱们会在下期给出详细过程讲解。 点击这里了解更多
咱们在上面已经给出了astElement的详细属性,其中有两个属性叫作staticRoot 和 staticInFor。而optimize 的过程,就是给astElement打上这两个标记。这里是为了让这类静态节点,在render过程,可以走缓存的方式,只渲染一次。
好处很明显,就是静态节点不须要重复对比和 从新渲染,以此来提升总体性能。
with(this) { _c('div'...xxx...xxx) } 复制代码
咱们在强调下,此次又是从astElement直接到另外一个String。 上面astElement是个树状结构。
而后在这个过程,基本一个astElement就对应一个短函数。 最基本短函数是createElement 也就是_c。 最终的树状结构,会以函数的形式表现处理函数以下
_c( 'div', { key:'xxx', ref:'xx', pre:'xxx', domPro:xxx, .... }, [ // chidren _v(_s('ding')), _c('p',{model:'isshow',}, [ ...xxx ]) ] ) 复制代码
能够清晰的看到,最终造成的string,依然是一个树状形式
,是以function形式展现的树状,并且全部属性都已经抽离成createElement的第二个参数。
1.短函数最终执行短this其实就是Vue实例,或者组件实例。
2.短函数比较重要的三个建立函数分别是_c(createElement)、_v(createTextVNode)、_e(createEmptyVNode),分别生成三种类型的vnode。
一句话归纳下code generate作的事情就是:
生成vnode的前置工做,抽离astElement全部的属性,造成短函数链。
短函数对应大体以下:
export function installRenderHelpers (target: any) { target._o = markOnce // v-once target._n = toNumber target._s = toString target._l = renderList // v-for target._t = renderSlot // slot target._q = looseEqual target._i = looseIndexOf target._m = renderStatic // static target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots // scopeSlot target._g = bindObjectListeners // linsners target._d = bindDynamicKeys target._p = prependModifier } 复制代码
这个过程是直接执行,就会获得以vnode为节点对虚拟dom树,其细节包括了component的处理,会有单独一节去介绍。 最后这些虚拟树会丢给patch函数,最终在不断对比对过程当中生成目标真实对dom树。
这里展现下vnode的格式。
declare interface VNodeData { key?: string | number; slot?: string; ref?: string; is?: string; pre?: boolean; tag?: string; staticClass?: string; class?: any; staticStyle?: { [key: string]: any }; style?: string | Array<Object> | Object; normalizedStyle?: Object; props?: { [key: string]: any }; attrs?: { [key: string]: string }; domProps?: { [key: string]: any }; hook?: { [key: string]: Function }; on?: ?{ [key: string]: Function | Array<Function> }; nativeOn?: { [key: string]: Function | Array<Function> }; transition?: Object; show?: boolean; // marker for v-show inlineTemplate?: { render: Function; staticRenderFns: Array<Function>; }; directives?: Array<VNodeDirective>; keepAlive?: boolean; scopedSlots?: { [key: string]: Function }; model?: { value: any; callback: Function; }; }; 复制代码