You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
import{initMixin}from'./init'import{stateMixin}from'./state'import{renderMixin}from'./render'import{eventsMixin}from'./events'import{lifecycleMixin}from'./lifecycle'import{warn}from'../util/index'functionVue(options){if(process.env.NODE_ENV!=='production'&&!(thisinstanceofVue)){warn('Vue is a constructor and should be called with the `new` keyword')}/*初始化*/this._init(options)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)exportdefaultVue
exportfunctionstateMixin(Vue: Class<Component>){// flow somehow has problems with directly declared definition object// when using Object.defineProperty, so we have to procedurally build up// the object here.constdataDef={}dataDef.get=function(){returnthis._data}constpropsDef={}propsDef.get=function(){returnthis._props}......Object.defineProperty(Vue.prototype,'$data',dataDef)Object.defineProperty(Vue.prototype,'$props',propsDef)Vue.prototype.$set=setVue.prototype.$delete=del// 数据绑定相关后面会详细解读Vue.prototype.$watch=function(expOrFn: string|Function,cb: Function,options?: Object): Function{
......
}
exportfunctionlifecycleMixin(Vue: Class<Component>){/*更新节点*/Vue.prototype._update=function(vnode: VNode,hydrating?: boolean){constvm: Component=this/*如果已经该组件已经挂载过了则代表进入这个步骤是个更新的过程,触发beforeUpdate钩子*/if(vm._isMounted){callHook(vm,'beforeUpdate')}constprevEl=vm.$elconstprevVnode=vm._vnodeconstprevActiveInstance=activeInstanceactiveInstance=vmvm._vnode=vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used./*基于后端渲染Vue.prototype.__patch__被用来作为一个入口*/if(!prevVnode){// initial rendervm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/* removeOnly */,vm.$options._parentElm,vm.$options._refElm)}else{// updatesvm.$el=vm.__patch__(prevVnode,vnode)}activeInstance=prevActiveInstance// update __vue__ reference/*更新新的实例对象的__vue__*/if(prevEl){prevEl.__vue__=null}if(vm.$el){vm.$el.__vue__=vm}// if parent is an HOC, update its $el as wellif(vm.$vnode&&vm.$parent&&vm.$vnode===vm.$parent._vnode){vm.$parent.$el=vm.$el}// updated hook is called by the scheduler to ensure that children are// updated in a parent's updated hook.}Vue.prototype.$forceUpdate=function(){constvm: Component=thisif(vm._watcher){vm._watcher.update()}}Vue.prototype.$destroy=function(){constvm: Component=thisif(vm._isBeingDestroyed){return}/* 调用beforeDestroy钩子 */callHook(vm,'beforeDestroy')/* 标志位 */vm._isBeingDestroyed=true// remove self from parentconstparent=vm.$parentif(parent&&!parent._isBeingDestroyed&&!vm.$options.abstract){remove(parent.$children,vm)}// teardown watchers/* 该组件下的所有Watcher从其所在的Dep中释放 */if(vm._watcher){vm._watcher.teardown()}leti=vm._watchers.lengthwhile(i--){vm._watchers[i].teardown()}// remove reference from data ob// frozen object may not have observer.if(vm._data.__ob__){vm._data.__ob__.vmCount--}// call the last hook...vm._isDestroyed=true// invoke destroy hooks on current rendered treevm.__patch__(vm._vnode,null)// fire destroyed hook/* 调用destroyed钩子 */callHook(vm,'destroyed')// turn off all instance listeners./* 移除所有事件监听 */vm.$off()// remove __vue__ referenceif(vm.$el){vm.$el.__vue__=null}// remove reference to DOM nodes (prevents leak)vm.$options._parentElm=vm.$options._refElm=null}}
new Vue()其实就是调用构造函数中的this._init(),this._init()就是调用上述instance/init中声明的Vue.prototype._init
exportfunctioninitMixin(Vue: Class<Component>){Vue.prototype._init=function(options?: Object){constvm: Component=this......// expose real selfvm._self=vm/*初始化生命周期*/initLifecycle(vm)/*初始化事件*/initEvents(vm)/*初始化render*/initRender(vm)/*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/callHook(vm,'beforeCreate')initInjections(vm)// resolve injections before data/props/*初始化props、methods、data、computed与watch*/initState(vm)initProvide(vm)// resolve provide after data/props/*调用created钩子函数并且触发created钩子事件*/callHook(vm,'created')/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){/*格式化组件名*/vm._name=formatComponentName(vm,false)mark(endTag)measure(`${vm._name} init`,startTag,endTag)}if(vm.$options.el){/*挂载组件*/vm.$mount(vm.$options.el)}}}
initLifecycle,主要把自己push到parent.$children中
/*初始化生命周期*/exportfunctioninitLifecycle(vm: Component){constoptions=vm.$options// locate first non-abstract parent/* 将vm对象存储到parent组件中(保证parent组件是非抽象组件,比如keep-alive) */letparent=options.parentif(parent&&!options.abstract){while(parent.$options.abstract&&parent.$parent){parent=parent.$parent}parent.$children.push(vm)}
......
}
/*初始化render*/exportfunctioninitRender(vm: Component){
......
/*将createElement函数绑定到该实例上,该vm存在闭包中,不可修改,vm实例则固定。这样我们就可以得到正确的上下文渲染*/vm._c=(a,b,c,d)=>createElement(vm,a,b,c,d,false)// normalization is always applied for the public version, used in// user-written render functions./*常规方法呗用于公共版本,被用来作为用户界面的渲染方法*/vm.$createElement=(a,b,c,d)=>createElement(vm,a,b,c,d,true)}
/*initData*/functioninitData(vm: Component){/*得到data数据*/letdata=vm.$options.datadata=vm._data=typeofdata==='function'
? getData(data,vm)
: data||{}/*对对象类型进行严格检查,只有当对象是纯javascript对象的时候返回true*/if(!isPlainObject(data)){data={}process.env.NODE_ENV!=='production'&&warn('data functions should return an object:\n'+'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instance/*遍历data对象*/constkeys=Object.keys(data)constprops=vm.$options.propsleti=keys.length//遍历data中的数据while(i--){/*保证data中的key不与props中的key重复,props优先,如果有冲突会产生warning*/if(props&&hasOwn(props,keys[i])){process.env.NODE_ENV!=='production'&&warn(`The data property "${keys[i]}" is already declared as a prop. `+`Use prop default value instead.`,vm)}elseif(!isReserved(keys[i])){/*判断是否是保留字段*//*这里是我们前面讲过的代理,将data上面的属性代理到了vm实例上*/proxy(vm,`_data`,keys[i])}}// observe data/*从这里开始我们要observe了,开始对数据进行绑定,下面会进行递归observe进行对深层对象的绑定。*/observe(data,true/* asRootData */)}
observe中new Observer(), new Observer()会将data中的所有数据调用defineReactive变成响应式。主要原理就是利用Object.defineProperty,get()时增加依赖,也就是观察者,set时通知观察者。
/* 一个解析表达式,进行依赖收集的观察者,同时在表达式数据变更时触发回调函数。它被用于$watch api以及指令 */exportdefaultclassWatcher{constructor(vm: Component,expOrFn: string|Function,cb: Function,options?: Object){this.vm=vm/*_watchers存放订阅者实例*/vm._watchers.push(this)......this.value=this.lazy
? undefined
: this.get()}/** * Evaluate the getter, and re-collect dependencies. *//*获得getter的值并且重新进行依赖收集*/get(){
......
/*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/pushTarget(this)......value=this.getter.call(vm,vm)......if(this.deep){/*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/traverse(value)}/*将观察者实例从target栈中取出并设置给Dep.target*/popTarget()this.cleanupDeps()return value
}/* 调度者工作接口,将被调度者回调。 */run(){
......
this.cb.call(this.vm,value,oldValue)......}/** * Evaluate the value of the watcher. * This only gets called for lazy watchers. *//*获取观察者的值*/evaluate(){this.value=this.get()this.dirty=false}
exportdefaultclassDep{constructor(){this.id=uid++this.subs=[]}/*添加一个观察者对象*/addSub(sub: Watcher){this.subs.push(sub)}/*移除一个观察者对象*/removeSub(sub: Watcher){remove(this.subs,sub)}/*依赖收集,当存在Dep.target的时候添加观察者对象*/depend(){if(Dep.target){Dep.target.addDep(this)}}/*通知所有订阅者*/notify(){// stabilize the subscriber list firstconstsubs=this.subs.slice()for(leti=0,l=subs.length;i<l;i++){subs[i].update()}}}/*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/Dep.target=nullconsttargetStack=[]/*将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中*/exportfunctionpushTarget(_target: Watcher){if(Dep.target)targetStack.push(Dep.target)Dep.target=_target}/*将观察者实例从target栈中取出并设置给Dep.target*/exportfunctionpopTarget(){Dep.target=targetStack.pop()}
/*初始化computed*/functioninitComputed(vm: Component,computed: Object){constwatchers=vm._computedWatchers=Object.create(null)for(constkeyincomputed){constuserDef=computed[key]/* 计算属性可能是一个function,也有可能设置了get以及set的对象。 可以参考 https://cn.vuejs.org/v2/guide/computed.html#计算-setter */letgetter=typeofuserDef==='function' ? userDef : userDef.getif(process.env.NODE_ENV!=='production'){/*getter不存在的时候抛出warning并且给getter赋空函数*/if(getter===undefined){warn(`No getter function has been defined for computed property "${key}".`,vm)getter=noop}}// create internal watcher for the computed property./* 为计算属性创建一个内部的监视器Watcher,保存在vm实例的_computedWatchers中 这里的computedWatcherOptions参数传递了一个lazy为true,会使得watch实例的dirty为true */watchers[key]=newWatcher(vm,getter,noop,computedWatcherOptions)// component-defined computed properties are already defined on the// component prototype. We only need to define computed properties defined// at instantiation here./*组件正在定义的计算属性已经定义在现有组件的原型上则不会进行重复定义*/if(!(keyinvm)){/*定义计算属性*/defineComputed(vm,key,userDef)}elseif(process.env.NODE_ENV!=='production'){/*如果计算属性与已定义的data或者props中的名称冲突则发出warning*/if(keyinvm.$data){warn(`The computed property "${key}" is already defined in data.`,vm)}elseif(vm.$options.props&&keyinvm.$options.props){warn(`The computed property "${key}" is already defined as a prop.`,vm)}}}}
exportdefaultclassVNode{constructor(tag?: string,data?: VNodeData,children?: ?Array<VNode>,text?: string,elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions){/*当前节点的标签名*/this.tag=tag/*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/this.data=data/*当前节点的子节点,是一个数组*/this.children=children/*当前节点的文本*/this.text=text/*当前虚拟节点对应的真实dom节点*/this.elm=elm/*当前节点的名字空间*/this.ns=undefined/*当前节点的编译作用域*/this.context=context/*函数化组件作用域*/this.functionalContext=undefined/*节点的key属性,被当作节点的标志,用以优化*/this.key=data&&data.key/*组件的option选项*/this.componentOptions=componentOptions/*当前节点对应的组件的实例*/this.componentInstance=undefined/*当前节点的父节点*/this.parent=undefined/*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/this.raw=false/*是否为静态节点*/this.isStatic=false/*是否作为跟节点插入*/this.isRootInsert=true/*是否为注释节点*/this.isComment=false/*是否为克隆节点*/this.isCloned=false/*是否有v-once指令*/this.isOnce=false}// DEPRECATED: alias for componentInstance for backwards compat./* istanbul ignore next */getchild(): Component|void{returnthis.componentInstance
}}
渲染(patch)主要逻辑大致如下
patch
functionpatch(oldVnode,vnode,hydrating,removeOnly,parentElm,refElm){/*vnode不存在则直接调用销毁钩子*/if(isUndef(vnode)){if(isDef(oldVnode))invokeDestroyHook(oldVnode)return}letisInitialPatch=falseconstinsertedVnodeQueue=[]if(isUndef(oldVnode)){// empty mount (likely as component), create new root element/*oldVnode未定义的时候,其实也就是root节点,创建一个新的节点*/isInitialPatch=truecreateElm(vnode,insertedVnodeQueue,parentElm,refElm)}else{/*标记旧的VNode是否有nodeType*/constisRealElement=isDef(oldVnode.nodeType)if(!isRealElement&&sameVnode(oldVnode,vnode)){// patch existing root node/*是同一个节点的时候直接修改现有的节点*/patchVnode(oldVnode,vnode,insertedVnodeQueue,removeOnly)}else{createElm(vnode,insertedVnodeQueue,// extremely rare edge case: do not insert if old element is in a// leaving transition. Only happens when combining transition +// keep-alive + HOCs. (#4590)oldElm._leaveCb ? null : parentElm,nodeOps.nextSibling(oldElm))}/*调用insert钩子*/invokeInsertHook(vnode,insertedVnodeQueue,isInitialPatch)returnvnode.elm}
functionupdateChildren(parentElm,oldCh,newCh,insertedVnodeQueue,removeOnly){letoldStartIdx=0letnewStartIdx=0letoldEndIdx=oldCh.length-1letoldStartVnode=oldCh[0]letoldEndVnode=oldCh[oldEndIdx]letnewEndIdx=newCh.length-1letnewStartVnode=newCh[0]letnewEndVnode=newCh[newEndIdx]letoldKeyToIdx,idxInOld,elmToMove,refElm// removeOnly is a special flag used only by <transition-group>// to ensure removed elements stay in correct relative positions// during leaving transitionsconstcanMove=!removeOnlywhile(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){if(isUndef(oldStartVnode)){oldStartVnode=oldCh[++oldStartIdx]// Vnode has been moved left}elseif(isUndef(oldEndVnode)){oldEndVnode=oldCh[--oldEndIdx]}elseif(sameVnode(oldStartVnode,newStartVnode)){/*前四种情况其实是指定key的时候,判定为同一个VNode,则直接patchVnode即可,分别比较oldCh以及newCh的两头节点2*2=4种情况*/patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue)oldStartVnode=oldCh[++oldStartIdx]newStartVnode=newCh[++newStartIdx]}elseif(sameVnode(oldEndVnode,newEndVnode)){patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue)oldEndVnode=oldCh[--oldEndIdx]newEndVnode=newCh[--newEndIdx]}elseif(sameVnode(oldStartVnode,newEndVnode)){// Vnode moved rightpatchVnode(oldStartVnode,newEndVnode,insertedVnodeQueue)canMove&&nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm))oldStartVnode=oldCh[++oldStartIdx]newEndVnode=newCh[--newEndIdx]}elseif(sameVnode(oldEndVnode,newStartVnode)){// Vnode moved leftpatchVnode(oldEndVnode,newStartVnode,insertedVnodeQueue)canMove&&nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm)oldEndVnode=oldCh[--oldEndIdx]newStartVnode=newCh[++newStartIdx]}else{/* 生成一个key与旧VNode的key对应的哈希表(只有第一次进来undefined的时候会生成,也为后面检测重复的key值做铺垫) 比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}] beginIdx = 0 endIdx = 2 结果生成{key0: 0, key1: 1, key2: 2} */if(isUndef(oldKeyToIdx))oldKeyToIdx=createKeyToOldIdx(oldCh,oldStartIdx,oldEndIdx)/*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld(即第几个节点,下标)*/idxInOld=isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : nullif(isUndef(idxInOld)){// New element/*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm)newStartVnode=newCh[++newStartIdx]}else{/*获取同key的老节点*/elmToMove=oldCh[idxInOld]/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&!elmToMove){/*如果elmToMove不存在说明之前已经有新节点放入过这个key的Dom中,提示可能存在重复的key,确保v-for的时候item有唯一的key值*/warn('It seems there are duplicate keys that is causing an update error. '+'Make sure each v-for item has a unique key.')}if(sameVnode(elmToMove,newStartVnode)){/*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/patchVnode(elmToMove,newStartVnode,insertedVnodeQueue)/*因为已经patchVnode进去了,所以将这个老节点赋值undefined,之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/oldCh[idxInOld]=undefined/*当有标识位canMove实可以直接插入oldStartVnode对应的真实Dom节点前面*/canMove&&nodeOps.insertBefore(parentElm,newStartVnode.elm,oldStartVnode.elm)newStartVnode=newCh[++newStartIdx]}else{// same key but different element. treat as new element/*当新的VNode与找到的同样key的VNode不是sameVNode的时候(比如说tag不一样或者是有不一样type的input标签),创建一个新的节点*/createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm)newStartVnode=newCh[++newStartIdx]}}}}if(oldStartIdx>oldEndIdx){/*全部比较完成以后,发现oldStartIdx > oldEndIdx的话,说明老节点已经遍历完了,新节点比老节点多,所以这时候多出来的新节点需要一个一个创建出来加入到真实Dom中*/refElm=isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx+1].elmaddVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx,insertedVnodeQueue)}elseif(newStartIdx>newEndIdx){/*如果全部比较完成以后发现newStartIdx > newEndIdx,则说明新节点已经遍历完了,老节点多余新节点,这个时候需要将多余的老节点从真实Dom中移除*/removeVnodes(parentElm,oldCh,oldStartIdx,oldEndIdx)}}
exportdefaultclassRenderStreamextendsstream.Readable{constructor(render: Function){super()this.buffer=''this.render=renderthis.expectedSize=0this.write=createWriteFunction((text,next)=>{constn=this.expectedSizethis.buffer+=textif(this.buffer.length>=n){this.next=nextthis.pushBySize(n)returntrue// we will decide when to call next}returnfalse},err=>{this.emit('error',err)})this.end=()=>{// the rendering is finished; we should push out the last of the buffer.this.done=truethis.push(this.buffer)}}pushBySize(n: number){constbufferToPush=this.buffer.substring(0,n)this.buffer=this.buffer.substring(n)this.push(bufferToPush)}tryRender(){try{this.render(this.write,this.end)}catch(e){this.emit('error',e)}}tryNext(){try{this.next()}catch(e){this.emit('error',e)}}_read(n: number){this.expectedSize=n// it's possible that the last chunk added bumped the buffer up to > 2 * n,// which means we will need to go through multiple read calls to drain it// down to < n.if(isTrue(this.done)){this.push(null)return}if(this.buffer.length>=n){this.pushBySize(n)return}if(isUndef(this.next)){// start the rendering chain.this.tryRender()}else{// continue with the rendering.this.tryNext()}}}
Vue.use=function(plugin: Function|Object){/* istanbul ignore if *//*标识位检测该插件是否已经被安装*/if(plugin.installed){return}// additional parametersconstargs=toArray(arguments,1)/*a*/args.unshift(this)if(typeofplugin.install==='function'){/*install执行插件安装*/plugin.install.apply(plugin,args)}elseif(typeofplugin==='function'){plugin.apply(null,args)}plugin.installed=truereturnthis}
网上Vue源码解读的文章有很多,但涉及到Vuex、vue-router的就比较少了。本文主要对描述了Vue的整体代码结构,用两个例子分别描述Vue实例化及ssr的流程;接着阐述Vue插件的注册方法,Vuex、vue-router大致实现原理。
Vue
如何用例子走一遍Vue代码
目录结构
vue构造函数
我们使用vue时都会先实例化一个Vue对象,先从Vue的构造函数说起。构造函数及原型相关代码绝大部分都在
core/instance
下。至于怎么找到Vue构造函数的位置,运用从后向前的方法,从
package.json
一点点往会看就好了。首先看
core/instance/index.js
文件,该文件主要定义了Vue的构造函数,并且初始化Vue.prototype
中的一些方法。initMixin
就做了一件事情,在Vue的原型上增加_init
方法,构造Vue实例的时候会调用这个_init
方法来初始化Vue实例,下面常用使用原理中会详细说一下这一块。stateMixin
中主要声明了Vue.prototype.$data
、Vue.prototype.$props
、Vue.prototype.$set
、Vue.prototype.$watch
eventsMixin
主要定义了Vue.prototype.$on/$off/$once
,原理就是利用观察者模型,为每一个event维护一个观察队列,存放在Vue._events中。lifecycleMixin
中定义了我们Vue中经常用到的Vue.prototype._update
方法,每当我们定义的组件data发生变化或其他原因需要重新渲染时,Vue会调用该方法,对Vnode
做diff和patch操作。renderMixin
中定义了Vue.prototype._render
等方法,_render()
调用实例化时传入的render方法,生成VNode。经常与Vue.prototype.update
一起使用。常见使用原理解读
创建实例
new Vue()
其实就是调用构造函数中的this._init()
,this._init()
就是调用上述instance/init
中声明的Vue.prototype._init
initLifecycle
,主要把自己push到parent.$children中
initEvents
,主要初始化了vm._events
存放事件。$on()方法就是将事件监听存放在这里。initRender
,定义了vm.$createElement
方法,我们调用render()
方法时,传入参数就是vm.$createElement
。initState
,这里主要initProps
,initComputed
,initData
。我们首先介绍initData
,并借initData来解读一下Vue的数据响应系统
。initData
调用observer/index.js
中的observe
方法,生成observer
对象,observer
遍历data中的数据,把每一项数据都变成响应式的。initData
,主要就看最后一行调用observe()
。observe中
new Observer()
, new Observer()会将data中的所有数据调用defineReactive
变成响应式。主要原理就是利用Object.defineProperty
,get()时增加依赖,也就是观察者,set时通知观察者。我们在声明组件时,经常使用
watch
,实际调用了new watcher(a, callback)
,watcher相当于一个观察者。我们来看watcher里的代码,其实也不难理解,watcher就是一个订阅者。关键在于watcher如何与observer
联系在一起,observer中的数据set()时,如何找到对应的watcher呢?dep
出现了!注意下面的get()中的pushTarget()
,该方法就是将自己放到dep模块中的全局变量上,然后调用this.getter.call(vm, vm)
,也就是调用了obsever的get(),get()中取得dep中的全局变量,加到了自身的dep中,当set时,会遍历执行dep中存放所有watcher的run()方法,执行callback。watcher和dep代码如下。
总结一下initData及Vue的响应式数据。
我们接下来看
initComputed
,其实就是new了一个watcher,然后执行computed函数时会调用其中所有依赖数据的getter,从而将该watcher加入到其依赖数据的dep中。声明组件
这里主要说一下
Vue.component
方法与Vue.extend方法。Vue.extend(core/global-api/extend.js)
, 其实就是寄生组合继承
了Vue。Vue.component
与Vue.extend
类似,core/global-api/assets.js
组件挂载
在
Vue.prototype._init
中最后调用了vm.$mount(vm.$options.el)
流程如下:
![GitHub](https://camo.githubusercontent.com/70cb5c6e5241f2b0d432e9d8a247cf21abaf9f98d34be5cf9a32b72dc781cb9d/68747470733a2f2f7366332d687363646e2d746f732e7073746174702e636f6d2f6f626a2f6965732e66652e6d69732f6137396538643838636265313634616665653134343539623264643538353461)
首先什么事vdom,其实很简单,就是一个组件就是一个vdom对象,维护在Vue中,方便取新老vnode去diff,然后针对性的去渲染。
Vnode:
渲染(patch)主要逻辑大致如下
patch
patchNode
:如果两个节点都有children,
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
这块网上有很多讲解,篇幅已经够长了,就不在这啰嗦了。推荐Vue 2.0 的 virtual-dom 实现简析
ssr
流程如下
const renderer = createRenderer(rendererOptions);
返回的renderer主要是使用renderNode()
方法,根据Vnode各种拼html。const run = createBundleRunner(entry, files, basedir, runInNewContext)
返回一个Promise,在Promise中执行打包后的代码,resolve(app)=>返回实例。那么我们打包后的ssr代码,如何执行的呢?在createBundleRunner有这样一段代码。其中NativeModule.wrap()方法也是node中包裹模块时使用的方法。
下面我们来看
renderToStream
,其实是调用了renderer.renderToStream
,下面我们来看renderer.renderToStream
。其中
new RenderStream()
,RenderStream代码如下主要看
_read()
方法。``
所有实现可读流的实例必须实现readable._read() 方法去获得底层的数据资源。
当 readable._read() 被调用,如果读取的数据是可用的,应该在最开始的实现的时候使用this.push(dataChunk)方法将该数据推入读取队列。_read() 应该一直读取资源直到推送数据方法readable.push()返回false的时候停止。想再次调用_read()方法,需要再次往可读流里面push数据。
RenderStream
继承stream.Readable
,声明_read读取底层数据,在数据流缓冲队列超过max_size
(node实现的16384),this.pushBySize
;当steam done之后,this.tryRender
tryRender:
值得借鉴的地方
1.代码组织结构。Vue的代码耦合度还是比较低的,比如核心的部分都在Core中,在Platforms的web和weex中很方便的对其进行扩展;代码组织也比较清晰,基本一个模块只做一件事情,比如compile中就是compile template的,大家看起来一目了然
2.缓存也是用的不错的,基本上可能重复用到的地方都用了缓存。
Vue插件
Vue中如何自定义插件
Vue中绝大本分插件都是通过Vue.use()方法,该方法传入一个对象作为参数,执行对象的Install方法。
那么我们一般使用插件时,以Vuex为例,直接在实例化Vue时加入,在组件中直接使用this.$store,这又是如何做到的呢?
一般会在install中注册beforeCreate的钩子,在钩子函数中将options或父组件中的方法或属性赋给自组件。利用了父组件create先于子组件的关系,从上到下的进行注册。下面以Vuex为例。
Vuex
从store的构造函数说起
this._modules = new ModuleCollection(options)
,初始化modules,返回一个Module树,数据结构如下:installModule(this, state, [], this._modules.root)
,根据上述module树,递归注册mutation,action。。。最终形成Store的数据结构如下:
mutation和action实现上又什么区别呢?可以从下面看出action执行handle,然后判断是否是Promise来决定返回。
state中的数据是怎样加入到Vue的响应体系中的呢?使用Vue.暴露出得$set。
Vue-router
挂载方法与上述类似,只不过多做了router._init及注册组件router-view和router-link。
我们接下来看一下router的构造函数
this.matcher = createMatcher(options.routes || [], this)
根据pathList, pathMap, nameMap来找出跟路由匹配的route对象。PathMap结构如下:
this.matcher.match
用来查找匹配的路由,返回route对象,主要步骤有标准化路由(normalizeLocation)、从pathMap/pathList/nameMap中取响应记录、返回route对象。router.history
,分history/hash/abstract三种,histoy、hash即咱们理解的history和hash,abstract是Vue router自己利用堆栈实现的一套记录路由的方式。大致操作方法如下。H5:
Hash
Abstract
router-view
和router-link
为vue-router默认的组件首先看router-view,router-view组件在render中首先向上遍历到根结点,找到当前router-view的深度,也就是定义router是children的深度;找到对应组件;render()。
那么,routerView是如何得知路由变化,触发其render()的呢?这又回到了View的响应式中,Vue中Vm或数据发生变化时,会调用q前文提到的
vue.update(vm.render())
方法更新操作。vue-router在初始化时
Vue.util.defineReactive(this, '_route', this._router.history.current)
将_route变成了响应式,在路由发生变化时,执行updateRoute()将新的route赋给_route。router-link
比较简单。默认a标签,监听click事件,确定是router.push还是router.replace。路由和组件时怎么对应的呢,路由变化后,组件如何变化呢?
The text was updated successfully, but these errors were encountered: