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
Vue.extend=function(extendOptions){extendOptions=extendOptions||{};varSuper=this;varSuperId=Super.cid;// 给extendOptions设置_Ctor属性varcachedCtors=extendOptions._Ctor||(extendOptions._Ctor={});if(cachedCtors[SuperId]){returncachedCtors[SuperId]}varname=extendOptions.name||Super.options.name;
...
// 子组件实例的初始化函数varSub=functionVueComponent(options){this._init(options);};Sub.prototype=Object.create(Super.prototype);Sub.prototype.constructor=Sub;Sub.cid=cid++;// 完成options合并的工作,同时建立起子组件options和Super options的原型链Sub.options=mergeOptions(Super.options,extendOptions);Sub['super']=Super;
...
// allow further extension/mixin/plugin usageSub.extend=Super.extend;Sub.mixin=Super.mixin;Sub.use=Super.use;// create asset registers, so extended classes// can have their private assets too.ASSET_TYPES.forEach(function(type){Sub[type]=Super[type];});// enable recursive self-lookupif(name){Sub.options.components[name]=Sub;}// keep a reference to the super options at extension time.// later at instantiation we can check if Super's options have// been updated.Sub.superOptions=Super.options;Sub.extendOptions=extendOptions;Sub.sealedOptions=extend({},Sub.options);// cache constructorcachedCtors[SuperId]=Sub;returnSub};
// public mount methodVue.prototype.$mount=function(el,hydrating){el=el&&inBrowser ? query(el) : undefined;returnmountComponent(this,el,hydrating)};varmount=Vue.prototype.$mount;Vue.prototype.$mount=function(el,hydrating){el=el&&query(el);varoptions=this.$options;// resolve template/el and convert to render functionif(!options.render){vartemplate=options.template;if(template){if(typeoftemplate==='string'){if(template.charAt(0)==='#'){template=idToTemplate(template);/* istanbul ignore if */if("development"!=='production'&&!template){warn(("Template element not found or is empty: "+(options.template)),this);}}}elseif(template.nodeType){template=template.innerHTML;}else{{warn('invalid template option:'+template,this);}returnthis}}elseif(el){// 获取真实dom元素的字符串内容template=getOuterHTML(el);}if(template){// 获取到模板的字符串内容后,调用compileToFunctions方法将模板字符编译成render函数// 需要注意的是编译的时候只编译模板下的字符串,并不能直接编译当前模板的子组件的模板内容varref=compileToFunctions(template,{shouldDecodeNewlines: shouldDecodeNewlines,shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments},this);// 生成render函数varrender=ref.render;varstaticRenderFns=ref.staticRenderFns;options.render=render;// 挂载render函数options.staticRenderFns=staticRenderFns;// 挂载staticRenderFns函数}}returnmount.call(this,el,hydrating)}
functionmountComponent(vm,el,hydrating){vm.$el=el;if(!vm.$options.render){
...
}// 挂载前callHook(vm,'beforeMount');varupdateComponent;/* istanbul ignore if */if("development"!=='production'&&config.performance&&mark){
...
}else{updateComponent=function(){// vm._render首先构建完成vnode// 然后调用vm._update方法,更vnode挂载到真实的DOM节点上vm._update(vm._render(),hydrating);};}// we set this to vm._watcher inside the watcher's constructor// since the watcher's initial patch may call $forceUpdate (e.g. inside child// component's mounted hook), which relies on vm._watcher being already definednewWatcher(vm,updateComponent,noop,null,true/* isRenderWatcher */);hydrating=false;// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hookif(vm.$vnode==null){vm._isMounted=true;callHook(vm,'mounted');}returnvm}
Vue.prototype._render=function(){varvm=this;varref=vm.$options;varrender=ref.render;// 获取render函数var_parentVnode=ref._parentVnode;
...
// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode=_parentVnode;// render selfvarvnode;try{// 开始调用render函数,用以生成vnodevnode=render.call(vm._renderProxy,vm.$createElement);}catch(e){handleError(e,vm,"render");// return error render result,// or previous vnode to prevent render error causing blank component
...
}// return empty vnode in case the render function errored outif(!(vnodeinstanceofVNode)){
...
vnode=createEmptyVNode();}// set parentvnode.parent=_parentVnode;returnvnode};}
function_createElement(context,tag,data,children,normalizationType){
...
// support single function children as default scoped slotif(Array.isArray(children)&&typeofchildren[0]==='function'){data=data||{};data.scopedSlots={default: children[0]};children.length=0;}if(normalizationType===ALWAYS_NORMALIZE){children=normalizeChildren(children);}elseif(normalizationType===SIMPLE_NORMALIZE){children=simpleNormalizeChildren(children);}varvnode,ns;if(typeoftag==='string'){varCtor;ns=(context.$vnode&&context.$vnode.ns)||config.getTagNamespace(tag);if(config.isReservedTag(tag)){// platform built-in elements// 如果是内置的元素,那么直接创建VNodevnode=newVNode(config.parsePlatformTagName(tag),data,children,undefined,undefined,context);}elseif(isDef(Ctor=resolveAsset(context.$options,'components',tag))){// component// 如果是自己自定义标签元素,那么需要通过createComponent完成VNode的创建工作// 首先resolveAsset从$options属性上获取components定义// 需要注意的是全局注册的component,最终得到的Ctor为一个function// 而局部注册的component,最终得到的Ctor为一个plain Objectvnode=createComponent(Ctor,data,context,children,tag);}else{// unknown or unlisted namespaced elements// check at runtime because it may get assigned a namespace when its// parent normalizes childrenvnode=newVNode(tag,data,children,undefined,undefined,context);}}else{// direct component options / constructorvnode=createComponent(tag,data,context,children);}if(Array.isArray(vnode)){returnvnode}elseif(isDef(vnode)){if(isDef(ns)){applyNS(vnode,ns);}if(isDef(data)){registerDeepBindings(data);}returnvnode}else{returncreateEmptyVNode()}}functioncreateComponent(Ctor,data,context,children,tag){if(isUndef(Ctor)){return}// Vue构造函数varbaseCtor=context.$options._base;// plain options object: turn it into a constructor// 如果组件的定义是一个plain object,那么就需要通过使用Vue.extend方法将它转化为一个constructor// 即完成局部组件的注册if(isObject(Ctor)){Ctor=baseCtor.extend(Ctor);}
...
if(isUndef(Ctor.cid)){
...
}data=data||{};// resolve constructor options in case global mixins are applied after// component constructor creation// 获取Ctor构造函数上的options属性resolveConstructorOptions(Ctor);// extract propsvarpropsData=extractPropsFromVNodeData(data,Ctor,tag);// functional componentif(isTrue(Ctor.options.functional)){returncreateFunctionalComponent(Ctor,propsData,data,context,children)}
...
if(isTrue(Ctor.options.abstract)){
...
}// install component management hooks onto the placeholder node// 给component初始化挂载钩子函数,只有自定义的component才有,built-in的没有installComponentHooks(data);// return a placeholder vnodevarname=Ctor.options.name||tag;// 子vnode的id使用vue-component及对应的id来进行标识varvnode=newVNode(("vue-component-"+(Ctor.cid)+(name ? ("-"+name) : '')),data,undefined,undefined,undefined,context,{Ctor: Ctor,propsData: propsData,listeners: listeners,tag: tag,children: children},asyncFactory);/* istanbul ignore if */returnvnode}
Vue.prototype._update=function(vnode,hydrating){varvm=this;if(vm._isMounted){callHook(vm,'beforeUpdate');}varprevEl=vm.$el;varprevVnode=vm._vnode;varprevActiveInstance=activeInstance;activeInstance=vm;vm._vnode=vnode;// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if(!prevVnode){// initial render// 页面初始化渲染vm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/* removeOnly */,vm.$options._parentElm,vm.$options._refElm);// no need for the ref nodes after initial patch// this prevents keeping a detached DOM tree in memory (#5851)vm.$options._parentElm=vm.$options._refElm=null;}else{// updates// 更新vm.$el=vm.__patch__(prevVnode,vnode);}
...
}
functionpatch(oldVnode,vnode,hydrating,removeOnly,parentElm,refElm){
...
if(isUndef(oldVnode)){
...
}else{// 是否是真实的dom节点varisRealElement=isDef(oldVnode.nodeType);if(!isRealElement&&sameVnode(oldVnode,vnode)){// patch existing root nodepatchVnode(oldVnode,vnode,insertedVnodeQueue,removeOnly);}else{
...
// replacing existing elementvaroldElm=oldVnode.elm;varparentElm$1=nodeOps.parentNode(oldElm);// create new nodecreateElm(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$1,nodeOps.nextSibling(oldElm));}}}functioncreateElm(vnode,insertedVnodeQueue,parentElm,// 父节点refElm,nested,ownerArray,index){vnode.isRootInsert=!nested;// for transition enter check// 实例化customer component,而非built in component// 和上面提到的_createElement方法不同的是,那个方法是会创建新的vnode,这里是将vnode实例化成一个vue component。if(createComponent(vnode,insertedVnodeQueue,parentElm,refElm)){return}// anchorvardata=vnode.data;varchildren=vnode.children;vartag=vnode.tag;if(isDef(tag)){
...
// 创建真实的DOM节点vnode.elm=vnode.ns
? nodeOps.createElementNS(vnode.ns,tag)
: nodeOps.createElement(tag,vnode);setScope(vnode);/* istanbul ignore if */{// 挂载根节点之前首先递归遍历children vnode,将children vnode渲染成真实的dom节点,并挂载到传入的vnode所创建的DOM节点下createChildren(vnode,children,insertedVnodeQueue);if(isDef(data)){invokeCreateHooks(vnode,insertedVnodeQueue);}console.log('parentEle: ',parentElm)console.log('vnode.elm',vnode.elm)// 将vnode生成的dom节点插入到真实的dom节点当中insert(parentElm,vnode.elm,refElm);}if("development"!=='production'&&data&&data.pre){creatingElmInVPre--;}}elseif(isTrue(vnode.isComment)){vnode.elm=nodeOps.createComment(vnode.text);insert(parentElm,vnode.elm,refElm);}else{vnode.elm=nodeOps.createTextNode(vnode.text);insert(parentElm,vnode.elm,refElm);}}
Vue组件系统简析
Vue version: 2.5.16
Vue
的组件系统是Vue
最为核心的功能之一。它也是构建大型的复杂的web应用的基础能力。接下来就通过这篇文章去分析下Vue
组件系统是如何工作的。这篇文章主要是讲组件系统的渲染。组件注册
在组件的注册使用过程当中,有2种使用方式:
通过全局方式注册的组件,可在模板根实例下使用。
通过局部注册方式注册的
child
组件只能在parent
组件内部使用。全局组件
首先,我们来看下全局组件:
Vue.component
方法提供了全局注册组件的能力,这也是Vue
在初始化的过程中,通过内部的initGlobalAPI
方法,在Vue
这个全局唯一个根constructor
上挂载的一个方法。通过
Vue.component
方法注册的组件最终还是调用的Vue.extend
方法来完成子组件对父组件的一系列的继承的初始化的工作。主要是将根 Vue 构造函数上的 options 配置和组件定义所传入的 options 进行合并,对组件,同时在根constructor
的options
属性上挂载这个全局子组件的constructor
。Vue.extend
方法在整个Vue
组件系统中算是一个建立起父子组件之间联系的作用。大家可以看到在
Vue.extend
方法内部,实际上就是创建了一个VueComponent
的constructor
,同时还需完成这个构造函数和根constructor
方法、原型链的继承工作,其中有一点关于options
配置属性合并的工作。// TODO: 讲解下options合并的过程
调用
mergeOptions
方法,将根constructor
(superCtor)的options
属性和子constructor
的options
(subCtor)属性进行一次合并。就拿components
属性来说,最终的结果就是subCtor.options.components.prototype = superCtor.options.components
,同时通过mixin
,使得subCtor.options.components
可直接访问全局组件和局部组件。这样调用
Vue.component
方法后完成全局组件的注册。Vue
的局部组件和全局组件注册的方法还不太一样,首先在注册的阶段,局部组件并非和全局组件一样在代码初始化的阶段就完成了全局组件的注册,局部组件是在父组件在实例化的过程中动态的进行注册的(后面的内容会讲到这个地方)。VueComponent实例化
当全局组件在注册完毕后,开始根实例化的过程。(这篇文章主要将组件渲染过程,所以其他关于
Vue
的部分内容会略去不展开)从一个实例开始:
首先进行根实例化,从
Vue.prototype._init
方法开始:在这个内部,通过调用
initRender
方法给实例挂载生成vnode节点的方法:接下来根据是否有
el
配置选项来将vue component
挂载到真实dom
节点上。在上面的实例当中,
Vue
的编译系统会将html
模板内容转化为render
函数:完成模板编译,生成
render
函数后,接下来调用mountComponent
方法:!!! 前方高能:
在
mountComponent
方法内部首先定义updateComponent
方法,这个方法内部首先会调用vm._render
,将模板编译后生成的渲染函数转化成vnode
,然后再调用vm._update
完成真实dom
的更新,新实例化一个watcher
,开始进行页面的渲染工作。首先来看下
vm._render
是如何将渲染函数转化成vnode
的:在上面已经提到了关于最后关于编译生成的
render
函数:在实际的执行过程当中,首先完成
children
节点的vnode
的生成工作。这里首先生成my-component
子组件的vnode
,我们来看下vm._c
方法,这个方法内部最终是调用_createElement
方法来生成vnode
:因此上面提到的
render
函数,最终生成一个vnode
当根节点的
VNode
生成完毕后,让我们再回到mountComponent
方法内部:当
vm._render
函数生成完vnode
后,执行vm._update(vnode)
,将vnode
渲染为真实的DOM
节点:当页面进行首次渲染的时候:
可查阅关于patch的方法:
再让我们看下上面说的产生的根
VNode
节点:这个根
VNode
有3个VNode
子节点,这个时候开始调用createChildren
方法递归的完成子VNode
的实例化,以及将VNode
渲染成真实的DOM
节点,并插入到父节点当中。The text was updated successfully, but these errors were encountered: