vue源码解析系列完美收官vue.js从入门到项目实战源码vue.js源码全方位深入解析




vue源码解析系列完美收官vue.js从入门到项目实战源码vue.js源码全方位深入解析

2022-07-21 2:51:49 网络知识 官方管理员

1.初始化流程概述图、代码流程图

1.1初始化流程概述

通过debug的方式(如何准备源码调试环境,大家可以参考我之前写的这篇文章)总结了Vue从初始化到递归创建元素的大致流程:定义Vue构造函数/全局API/实例的属性方法→newVue()→init()→mountComponent→render→patch→createElm->createComponent→createChildren→insert,整理的流程概述图如下。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(1)

1.2初始化代码执行流程图

下图代码的执行逻辑,表示Vue从初始化到DOM渲染的一个流程。之前看源码的时候,主要是用到什么API,再去看与这个API相关的逻辑。但是这样看代码缺少系统性,不利于总结和复习。所以就一边写demo,一边断点,画出大概的代码执行流程,虽然不是很完善,但至少能有个总线。等到要看其他功能代码的时候,可以在此基础上进行扩展,同时也便于代码定位和逻辑的梳理。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(2)


2.初始化相关代码分析

2.1initGlobalAPI(Vue)初始化Vue的全局静态API

平时开发通过newVue()去创建了根实例,当然在此之前,Vue已经做了一些前期的准备工作。Vue的核心代码都在src/core目录中,我们先来看看core/index.js这个入口文件,这部分代码逻辑很简单。

importVuefrom'./instance/index'import{initGlobalAPI}from'./global-api/index'import{isServerRendering}from'core/util/env'import{FunctionalRenderContext}from'core/vdom/create-functional-component'//初始化全局APIinitGlobalAPI(Vue)//下面代码是服务端ssr渲染使用,web端可以忽略Object.defineProperty(Vue.prototype,'$isServer',{get:isServerRendering})Object.defineProperty(Vue.prototype,'$ssrContext',{get(){/*istanbulignorenext*/returnthis.$vnode&&this.$vnode.ssrContext}})//exposeFunctionalRenderContextforssrruntimehelperinstallationObject.defineProperty(Vue,'FunctionalRenderContext',{value:FunctionalRenderContext})//添加vue版本号这个静态变量Vue.version='__VERSION__'exportdefaultVue

我们主要关注的initGlobalAPI(Vue)这个函数,它定义在core/global-api/index.js文件中,主要给构造函数,添加诸如Vue.set/delete/use/mixin/extend/component/directive/filter这些静态方法。

/*@flow*/importconfigfrom'../config'import{initUse}from'./use'import{initMixin}from'./mixin'import{initExtend}from'./extend'import{initAssetRegisters}from'./assets'import{set,del}from'../observer/index'import{ASSET_TYPES}from'shared/constants'importbuiltInComponentsfrom'../components/index'import{observe}from'core/observer/index'import{warn,extend,nextTick,mergeOptions,defineReactive}from'../util/index'exportfunctioninitGlobalAPI(Vue:GlobalAPI){//config//这个是给Vue设置的config属性,不要手动的去替换这个对象,//如果替换,vue会给warn提示constconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('DonotreplacetheVue.configobject,setindividualfieldsinstead.')}}Object.defineProperty(Vue,'config',configDef)//exposedutilmethods.//NOTE:thesearenotconsideredpartofthepublicAPI-avoidrelyingon//themunlessyouareawareoftherisk.Vue.util={warn,extend,mergeOptions,defineReactive}//Vue的静态方法:Vue.set/delete/nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//用于标识Weex多实例场景中,通过“base”标识普通对象组件的构造函数。//thisisusedtoidentifythe"base"constructortoextendallplain-object//componentswithinWeex'smulti-instancescenarios.Vue.options._base=Vueextend(Vue.options.components,builtInComponents)//Vue的静态方法:Vue.use/mixin/extendinitUse(Vue)initMixin(Vue)initExtend(Vue)//Vue的静态属性方法:Vue.component/directive/filterinitAssetRegisters(Vue)}

其中initAssetRegisters(Vue),通过静态变量数组['component','directive','filter']遍历创建了Vue.component/directive/filter这三个静态属性方法。静态变量配置在src/shared/constants.js文件中,方法定义在core/global-api/assets.js文件中。

exportconstSSR_ATTR='data-server-rendered'//注册全局API时候使用exportconstASSET_TYPES=['component','directive','filter']//生命周期函数使用exportconstLIFECYCLE_HOOKS=['beforeCreate','created','beforeMount','mounted','beforeUpdate','updated','beforeDestroy','destroyed','activated','deactivated','errorCaptured','serverPrefetch']
/*@flow*/import{ASSET_TYPES}from'shared/constants'import{isPlainObject,validateComponentName}from'../util/index'exportfunctioninitAssetRegisters(Vue:GlobalAPI){/***Createassetregistrationmethods.*/ASSET_TYPES.forEach(type=>{//Vue.comoponent/directive/filter静态方法的绑定Vue[type]=function(id:string,definition:Function|Object):Function|Object|void{if(!definition){returnthis.options[type+'s'][id]}else{/*istanbulignoreif*/if(process.env.NODE_ENV!=='production'&&type==='component'){validateComponentName(id)}if(type==='component'&&isPlainObject(definition)){definition.name=definition.name||iddefinition=this.options._base.extend(definition)}if(type==='directive'&&typeofdefinition==='function'){definition={bind:definition,update:definition}}this.options[type+'s'][id]=definitionreturndefinition}}})}

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(3)

2.2定义Vue构造函数、实例方法

Vue这个构造函数,定义在core/instance/index.js文件中。从代码中可以看到,用工厂模式,执行不同的混入函数,对Vue.prototype原型进行加工,给实例添加对应的属性方法。

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('Vueisaconstructorandshouldbecalledwiththe`new`keyword')}//构造函数中执行Vue.prototype._init方法this._init(options)}//实例初始化方法:Vue.prototype._initinitMixin(Vue)//实例数据状态相关方法:Vue.prototype.$data/$props/$set/$delete,$watchstateMixin(Vue)//实例事件相关方法:Vue.prototype.$on/$once/$off/$emiteventsMixin(Vue)//实例生命周期相关方法:Vue.prototype._update/$forceUpdate/$destorylifecycleMixin(Vue)//实例渲染相关方法:Vue.prototype.$nextTick/_renderrenderMixin(Vue)exportdefaultVue

2.3newVue(options)

执行newVue()创建组件实例,同时this._init(options)初始化方法被执行,合并用户配置、初始化周期、事件、数据、属性等。

newVue({data:{...},props:{...},methods:{...},computed:{...}...})

这部分处理逻辑在core/instance/indexjs文件中,与_init()相关的主要看initMixin这个函数。

/*@flow*/importconfigfrom'../config'import{initProxy}from'./proxy'import{initState}from'./state'import{initRender}from'./render'import{initEvents}from'./events'import{mark,measure}from'../util/perf'import{initLifecycle,callHook}from'./lifecycle'import{initProvide,initInjections}from'./inject'import{extend,mergeOptions,formatComponentName}from'../util/index'letuid=0exportfunctioninitMixin(Vue:Class<Component>){Vue.prototype._init=function(options?:Object){constvm:Component=this//auidvm._uid=uid++letstartTag,endTag/*istanbulignoreif*/if(process.env.NODE_ENV!=='production'&&config.performance&&mark){startTag=`vue-perf-start:${vm._uid}`endTag=`vue-perf-end:${vm._uid}`mark(startTag)}//aflagtoavoidthisbeingobservedvm._isVue=true//mergeoptions//合并用户配置if(options&&options._isComponent){//optimizeinternalcomponentinstantiation//sincedynamicoptionsmergingisprettyslow,andnoneofthe//internalcomponentoptionsneedsspeci altreatment.initInternalComponent(vm,options)}else{vm.$options=mergeOptions(resolveConstructorOptions(vm.constructor),options||{},vm)}/*istanbulignoreelse*/if(process.env.NODE_ENV!=='production'){initProxy(vm)}else{vm._renderProxy=vm}//exposerealself//抛出vue实例本身vm._self=vm//初始化属性:vm.$parent/$root/$children/$refsinitLifecycle(vm)//初始化父组件传入的_parentListeners事件。initEvents(vm)//初始化render相关:vm.$slot/scopedSlots/_c/$createElementinitRender(vm)//调用生命钩子beforeCreatecallHook(vm,'beforeCreate')//在data/props之前解析注入initInjections(vm)//resolveinjectionsbeforedata/props//初始化相关用户配置的数据响应式:vm._props/_data,以及computed、watch、methodsinitState(vm)//在data/props之后提供数据initProvide(vm)//resolveprovideafterdata/props//调用生命钩子createdcallHook(vm,'created')/*istanbulignoreif*/if(process.env.NODE_ENV!=='production'&&config.performance&&mark){vm._name=formatComponentName(vm,false)mark(endTag)measure(`vue${vm._name}init`,startTag,endTag)}if(vm.$options.el){vm.$mount(vm.$options.el)}}}......

2.4执行$mount进行挂载

执行mount挂载,目录是为了生成vnode,进而转换为真实DOM执行更新。mount挂载,目录是为了生成vnode,进而转换为真实DOM执行更新。mount方法在web端相关两个src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js构建文件中都有定义。我们这里分析entry-runtime-with-compiler.js带compiler版本的入口文件。关于Vuescripts脚本构建相关的内容,大家可以参考我之前写的这篇文章的第2章节。

entry-runtime-with-compiler.js版本,是在src/platform/web/runtime/index.js版本的基础上,加compiler相关的功能逻辑。它首先保存runtime版本的mount=Vue.prototype.$mount方法。再重写Vue.prototype.$mount方法。如果用户传入template模板,就通过编译,转换成render函数。最后通过先前保存的mount方法进行挂载。下面我们在再来复习一下这个$mount实现逻辑。

......//1.保存runtime版本Vue.prototype上的$mount方法constmount=Vue.prototype.$mount//2.重写Vue.prototype上的$mount(加上compiler相关功能逻辑)Vue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{el=el&&query(el)/*istanbulignoreif*/if(el===document.body||el===document.documentElement){process.env.NODE_ENV!=='production'&&warn(`DonotmountVueto<html>or<body>-mounttonormalelementsinstead.`)returnthis}//处理options配置constoptions=this.$options//resolvetemplate/elandconverttorenderfunctionif(!options.render){lettemplate=options.templateif(template){if(typeoftemplate==='string'){if(template.charAt(0)==='#'){template=idToTemplate(template)/*istanbulignoreif*/if(process.env.NODE_ENV!=='production'&&!template){warn(`Templateelementnotfoundorisempty:${options.template}`,this)}}}elseif(template.nodeType){template=template.innerHTML}else{if(process.env.NODE_ENV!=='production'){warn('invalidtemplateoption:'+template,this)}returnthis}}elseif(el){template=getOuterHTML(el)}//3.存在template选项内容,就进行编译。if(template){/*istanbulignoreif*/if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compile')}//编译获取render函数const{render,staticRenderFns}=compileToFunctions(template,{outputSourceRange:process.env.NODE_ENV!=='production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters:options.delimiters,comments:options.comments},this)options.render=renderoptions.staticRenderFns=staticRenderFns/*istanbulignoreif*/if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compileend')measure(`vue${this._name}compile`,'compile','compileend')}}}//4.编译结束,调用runtime版本的$mount方法进行挂载returnmount.call(this,el,hydrating)}......

最后,代码执行mount.call(this,el,hydrating)。实际上复用了runtime/index.js中的定义的$mount公共方法,代码注释如下。

/*@flow*/importVuefrom'core/index'importconfigfrom'core/config'import{extend,noop}from'shared/util'import{mountComponent}from'core/instance/lifecycle'import{devtools,inBrowser}from'core/util/index'import{query,mustUseProp,isReservedTag,isReservedAttr,getTagNamespace,isUnknownElement}from'web/util/index'import{patch}from'./patch'importplatformDirectivesfrom'./directives/index'importplatformComponentsfrom'./components/index'//installplatformspecificutilsVue.config.mustUseProp=mustUsePropVue.config.isReservedTag=isReservedTagVue.config.isReservedAttr=isReservedAttrVue.config.getTagNamespace=getTagNamespaceVue.config.isUnknownElement=isUnknownElement//installplatformruntimedirectives&componentsextend(Vue.options.directives,platformDirectives)extend(Vue.options.components,platformComponents)//installplatformpatchfunctionVue.prototype.__patch__=inBrowser?patch:noop//定义了公共的$mount方法//publicmountmethodVue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{el=el&&inBrowser?query(el):undefinedreturnmountComponent(this,el,hydrating)}//devtoolsglobalhook/*istanbulignorenext*/....exportdefaultVue

公共$mount方法实际上调用了mountComponent函数,它core/instance/lifecycle.js文件中定义,在mountComponent函数中,实例化一个渲染Watcher,此时Watcher内部逻辑中调用定义的updateComponent函数。updateComponent被调用,vm._render执行生成vnode,最终调用_update将vnode更新成DOM,代码注释如下。

...exportfunctionmountComponent(vm:Component,el:?Element,hydrating?:boolean):Component{vm.$el=elif(!vm.$options.render){vm.$options.render=createEmptyVNodeif(process.env.NODE_ENV!=='production'){/*istanbulignoreif*/if((vm.$options.template&&vm.$options.template.charAt(0)!=='#')||vm.$options.el||el){warn('Youareusingtheruntime-onlybuildofVuewherethetemplate'+'compilerisnotavailable.Eitherpre-compilethetemplatesinto'+'renderfunctions,orusethecompiler-includedbuild.',vm)}else{warn('Failedtomountcomponent:templateorrenderfunctionnotdefined.',vm)}}}//调用beforeMount钩子callHook(vm,'beforeMount')letupdateComponent/*istanbulignoreif*///web端可以忽略if(process.env.NODE_ENV!=='production'&&config.performance&&mark){updateComponent=()=>{constname=vm._nameconstid=vm._uidconststartTag=`vue-perf-start:${id}`constendTag=`vue-perf-end:${id}`mark(startTag)constvnode=vm._render()mark(endTag)measure(`vue${name}render`,startTag,endTag)mark(startTag)vm._update(vnode,hydrating)mark(endTag)measure(`vue${name}patch`,startTag,endTag)}}else{//定义updateComponent方法,渲染watcher内部会调用。//如果updateComponent被调用,render方法先执行,生成vnode。//最后执行_update方法,进行DOM更新,newVue()走的是创建DOM逻辑。updateComponent=()=>{vm._update(vm._render(),hydrating)}}//初始化渲染watcher,内部逻辑会调用updateComponent。//wesetthistovm._watcherinsidethewatcher'sconstructor//sincethewatcher'sinitialpatchmaycall$forceUpdate(e.g.insidechild//component'smountedhook),whichreliesonvm._watcherbeingalreadydefinednewWatcher(vm,updateComponent,noop,{before(){if(vm._isMounted&&!vm._isDestroyed){callHook(vm,'beforeUpdate')}}},true/*isRenderWatcher*/)hydrating=false//如果vm.$vnode===null当前vm的父vnode为null。//即判断vm当前实例为Vue的根实例.//vm.$vnode在上面的updateChildComponent方法中有的定义vm.$vnode=parentVnode//manuallymountedinstance,callmountedonself//mountediscalledforrender-createdchildcomponentsinitsinsertedhookif(vm.$vnode==null){vm._isMounted=true//标记该Vue根实例挂载结束callHook(vm,'mounted')//执行钩子mounted。}returnvm}...

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(4)

2.5执行_render生成vnode

vm._render方法在之前的内容中有提到,它定义instance/index.js文件中,它是在Vue构造函数定义的时候,给Vue添加的实例方法。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(5)

具体逻辑在src/core/instance/render.js文件中。其他代码逻辑可以先不关注,主要关注,vnode=render.call(vm._renderProxy,vm.$createElement)这部分调用。

/*@flow*/importconfigfrom'../config'import{initUse}from'./use'import{initMixin}from'./mixin'import{initExtend}from'./extend'import{initAssetRegisters}from'./assets'import{set,del}from'../observer/index'import{ASSET_TYPES}from'shared/constants'importbuiltInComponentsfrom'../components/index'import{observe}from'core/observer/index'import{warn,extend,nextTick,mergeOptions,defineReactive}from'../util/index'exportfunctioninitGlobalAPI(Vue:GlobalAPI){//config//这个是给Vue设置的config属性,不要手动的去替换这个对象,//如果替换,vue会给warn提示constconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('DonotreplacetheVue.configobject,setindividualfieldsinstead.')}}Object.defineProperty(Vue,'config',configDef)//exposedutilmethods.//NOTE:thesearenotconsideredpartofthepublicAPI-avoidrelyingon//themunlessyouareawareoftherisk.Vue.util={warn,extend,mergeOptions,defineReactive}//Vue的静态方法:Vue.set/delete/nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//用于标识Weex多实例场景中,通过“base”标识普通对象组件的构造函数。//thisisusedtoidentifythe"base"constructortoextendallplain-object//componentswithinWeex'smulti-instancescenarios.Vue.options._base=Vueextend(Vue.options.components,builtInComponents)//Vue的静态方法:Vue.use/mixin/extendinitUse(Vue)initMixin(Vue)initExtend(Vue)//Vue的静态属性方法:Vue.component/directive/filterinitAssetRegisters(Vue)}0

render.call执行,传入了vm.$createElement,这里就是用户可以通过手写render函数,用来生成vnode的实现。示例如下,其中h就是vm.$createElement。

/*@flow*/importconfigfrom'../config'import{initUse}from'./use'import{initMixin}from'./mixin'import{initExtend}from'./extend'import{initAssetRegisters}from'./assets'import{set,del}from'../observer/index'import{ASSET_TYPES}from'shared/constants'importbuiltInComponentsfrom'../components/index'import{observe}from'core/observer/index'import{warn,extend,nextTick,mergeOptions,defineReactive}from'../util/index'exportfunctioninitGlobalAPI(Vue:GlobalAPI){//config//这个是给Vue设置的config属性,不要手动的去替换这个对象,//如果替换,vue会给warn提示constconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('DonotreplacetheVue.configobject,setindividualfieldsinstead.')}}Object.defineProperty(Vue,'config',configDef)//exposedutilmethods.//NOTE:thesearenotconsideredpartofthepublicAPI-avoidrelyingon//themunlessyouareawareoftherisk.Vue.util={warn,extend,mergeOptions,defineReactive}//Vue的静态方法:Vue.set/delete/nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//用于标识Weex多实例场景中,通过“base”标识普通对象组件的构造函数。//thisisusedtoidentifythe"base"constructortoextendallplain-object//componentswithinWeex'smulti-instancescenarios.Vue.options._base=Vueextend(Vue.options.components,builtInComponents)//Vue的静态方法:Vue.use/mixin/extendinitUse(Vue)initMixin(Vue)initExtend(Vue)//Vue的静态属性方法:Vue.component/directive/filterinitAssetRegisters(Vue)}1

vm.$createElement方法会在实例_init()初始化阶段,通过执行initRender函数添加。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(6)

initRender方法定义在src/core/instance/render.js文件中,可以看到vm._c和vm.$createElement方法最终都是执行createElement来生成vnode。vm._c是实例内部方法来创建vnode,vm.$createElement是用户手写render函数来创建vnode,代码注释如下。

/*@flow*/importconfigfrom'../config'import{initUse}from'./use'import{initMixin}from'./mixin'import{initExtend}from'./extend'import{initAssetRegisters}from'./assets'import{set,del}from'../observer/index'import{ASSET_TYPES}from'shared/constants'importbuiltInComponentsfrom'../components/index'import{observe}from'core/observer/index'import{warn,extend,nextTick,mergeOptions,defineReactive}from'../util/index'exportfunctioninitGlobalAPI(Vue:GlobalAPI){//config//这个是给Vue设置的config属性,不要手动的去替换这个对象,//如果替换,vue会给warn提示constconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('DonotreplacetheVue.configobject,setindividualfieldsinstead.')}}Object.defineProperty(Vue,'config',configDef)//exposedutilmethods.//NOTE:thesearenotconsideredpartofthepublicAPI-avoidrelyingon//themunlessyouareawareoftherisk.Vue.util={warn,extend,mergeOptions,defineReactive}//Vue的静态方法:Vue.set/delete/nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//用于标识Weex多实例场景中,通过“base”标识普通对象组件的构造函数。//thisisusedtoidentifythe"base"constructortoextendallplain-object//componentswithinWeex'smulti-instancescenarios.Vue.options._base=Vueextend(Vue.options.components,builtInComponents)//Vue的静态方法:Vue.use/mixin/extendinitUse(Vue)initMixin(Vue)initExtend(Vue)//Vue的静态属性方法:Vue.component/directive/filterinitAssetRegisters(Vue)}2

2.5执行update将vnode转化为真实DOM

上节内容中介绍了Vue在$mount方法执行挂载的时候,vm._update方法中的vm.render()执行生成vnode,下面继续分析这个vm._update方法。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(7)

vm._update这个方法也定义在src/core/instance/lifecycle.js文件中,内部通过prevVnode这个条件判断,执行不同参数的patch方法,来选择是初始化操作或还是更新操作。本章内容是执行初始化,所以vm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/*removeOnly*/)这个方法会被执行,创建DOM。

/*@flow*/importconfigfrom'../config'import{initUse}from'./use'import{initMixin}from'./mixin'import{initExtend}from'./extend'import{initAssetRegisters}from'./assets'import{set,del}from'../observer/index'import{ASSET_TYPES}from'shared/constants'importbuiltInComponentsfrom'../components/index'import{observe}from'core/observer/index'import{warn,extend,nextTick,mergeOptions,defineReactive}from'../util/index'exportfunctioninitGlobalAPI(Vue:GlobalAPI){//config//这个是给Vue设置的config属性,不要手动的去替换这个对象,//如果替换,vue会给warn提示constconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('DonotreplacetheVue.configobject,setindividualfieldsinstead.')}}Object.defineProperty(Vue,'config',configDef)//exposedutilmethods.//NOTE:thesearenotconsideredpartofthepublicAPI-avoidrelyingon//themunlessyouareawareoftherisk.Vue.util={warn,extend,mergeOptions,defineReactive}//Vue的静态方法:Vue.set/delete/nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//用于标识Weex多实例场景中,通过“base”标识普通对象组件的构造函数。//thisisusedtoidentifythe"base"constructortoextendallplain-object//componentswithinWeex'smulti-instancescenarios.Vue.options._base=Vueextend(Vue.options.components,builtInComponents)//Vue的静态方法:Vue.use/mixin/extendinitUse(Vue)initMixin(Vue)initExtend(Vue)//Vue的静态属性方法:Vue.component/directive/filterinitAssetRegisters(Vue)}3

关于update后面的流程,简单来说,就是通过遍历子vnode,递归创建DOM子节点,再插入到父节点的逻辑,它实现方式也蛮有意思的,我会在下一篇博文中对这部分代码做分析。

3.代码调试

demo示例

/*@flow*/importconfigfrom'../config'import{initUse}from'./use'import{initMixin}from'./mixin'import{initExtend}from'./extend'import{initAssetRegisters}from'./assets'import{set,del}from'../observer/index'import{ASSET_TYPES}from'shared/constants'importbuiltInComponentsfrom'../components/index'import{observe}from'core/observer/index'import{warn,extend,nextTick,mergeOptions,defineReactive}from'../util/index'exportfunctioninitGlobalAPI(Vue:GlobalAPI){//config//这个是给Vue设置的config属性,不要手动的去替换这个对象,//如果替换,vue会给warn提示constconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('DonotreplacetheVue.configobject,setindividualfieldsinstead.')}}Object.defineProperty(Vue,'config',configDef)//exposedutilmethods.//NOTE:thesearenotconsideredpartofthepublicAPI-avoidrelyingon//themunlessyouareawareoftherisk.Vue.util={warn,extend,mergeOptions,defineReactive}//Vue的静态方法:Vue.set/delete/nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//用于标识Weex多实例场景中,通过“base”标识普通对象组件的构造函数。//thisisusedtoidentifythe"base"constructortoextendallplain-object//componentswithinWeex'smulti-instancescenarios.Vue.options._base=Vueextend(Vue.options.components,builtInComponents)//Vue的静态方法:Vue.use/mixin/extendinitUse(Vue)initMixin(Vue)initExtend(Vue)//Vue的静态属性方法:Vue.component/directive/filterinitAssetRegisters(Vue)}4

debug:找到断点入口

当vue.js被加载。dev环境下,通过--sourcemap配置,生成vue.js.map文件对源码进行定位,通过入口文件entry-runtime-with-compiler.js,知道不同index.js入口文件的引用关系如下。

/*@flow*/importconfigfrom'../config'import{initUse}from'./use'import{initMixin}from'./mixin'import{initExtend}from'./extend'import{initAssetRegisters}from'./assets'import{set,del}from'../observer/index'import{ASSET_TYPES}from'shared/constants'importbuiltInComponentsfrom'../components/index'import{observe}from'core/observer/index'import{warn,extend,nextTick,mergeOptions,defineReactive}from'../util/index'exportfunctioninitGlobalAPI(Vue:GlobalAPI){//config//这个是给Vue设置的config属性,不要手动的去替换这个对象,//如果替换,vue会给warn提示constconfigDef={}configDef.get=()=>configif(process.env.NODE_ENV!=='production'){configDef.set=()=>{warn('DonotreplacetheVue.configobject,setindividualfieldsinstead.')}}Object.defineProperty(Vue,'config',configDef)//exposedutilmethods.//NOTE:thesearenotconsideredpartofthepublicAPI-avoidrelyingon//themunlessyouareawareoftherisk.Vue.util={warn,extend,mergeOptions,defineReactive}//Vue的静态方法:Vue.set/delete/nextTickVue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{Vue.options[type+'s']=Object.create(null)})//用于标识Weex多实例场景中,通过“base”标识普通对象组件的构造函数。//thisisusedtoidentifythe"base"constructortoextendallplain-object//componentswithinWeex'smulti-instancescenarios.Vue.options._base=Vueextend(Vue.options.components,builtInComponents)//Vue的静态方法:Vue.use/mixin/extendinitUse(Vue)initMixin(Vue)initExtend(Vue)//Vue的静态属性方法:Vue.component/directive/filterinitAssetRegisters(Vue)}5

确定断点位置如下。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(8)

debug:添加实例属性、方法。

在core/instance/index.js文件中断点如下,在initMixin(Vue)之前,Vue只是一个单纯的构造函数。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(9)

继续断点执行,可以看到Vue.prototype上添加了相应的实例方法。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(10)

debug:添加全局静态属性方法

断点core/index.js文件执行,可以看到给Vue添加的全局静态属性方法。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(11)

debug:newVue()、_init初始化

断点到demo文件,开始实例化。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(12)

stepinto进入调用的构造函数。断点this._init(options)处。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(13)

stepinto进入,可以看到此时vm实例上还没有相应的属性。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(14)

执行断点如下,可以看vm实例上,已经初始化parent、parent、slots、_c等属性方法。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(15)

stepover一直单步执行,直到断点$mout处进行挂载。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(16)

debug:$mount执行挂载

断点至entry-runtime-with-compiler.js文件的如下位置。此时需要关注render函数。通过compileToFunctions已经将template模板,编译成了render函数,赋值给this.$options。并且通过returnmount.call(this,el,hydrating),将当前实例的this引用,通过参数的方式进行传递。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(17)

生成的render函数,可以点击[[FunctionLocation]]进行查看,截图如下。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(18)

单步执行,进入调用mountComponent。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(19)

stepinto进入函数调用,并且打上断点。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(20)

继续单步执行可以看到定义了updateComponent这个方法。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(21)

继续单步执行到newWatcher,断点进入。

debug:实例化渲染watcher

断点到this.get()处,watcher的依赖收集等其他代码逻辑这里先不关注,主要关注这个this.get()执行,内部调用this.getter.call(vm,vm),进而执行先前定义的updateComponent方法。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(22)

stepinto进入updateComponent,打上断点标记。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(23)


debug:render执行生成vnode

如何生成render函数的编译逻辑这里先不关注,之后的博文中会对compiler内容进行代码分析。stepover单步执行一下,让vm._render()执行结束,返回vnode作为参数传递给vm._update。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(24)

update执行生成真实DOM

stepinto进入vm._update(vm._render(),hydrating)方法,它将传入的vnode作为当前实例的_vnode私有属性。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(25)

stepover单步往下走,执行完update之后,可以看到界面中的DOM已经替换完成。

vue源码解析系列完美收官(vue.js从入门到项目实战源码)(26)

总结

  • Vue在实例化之前,给原型对象Vue.prototype扩展了实例的属性和方法,同时给Vue构造函数,扩展全局静态属性和方法。
  • 当执行newVue()创建实例,构造函数内部执行_init初始化逻辑,给当前实例添加诸如parent、parent、slots、_c等属性方法。
  • 初始化结束之后,执行$mount进行挂载,最终是通过mountComponent方法来实现的。
  • mountComponent重点是给当前实例创建一个渲染Watcher,在Watcher的this.get()逻辑中会调用先前定义的updateComponent方法,开始更新。
  • updateComponent先执行vm._render方法生成vnode,最终调用vm._update将vnode转化成真实DOM更新视图。

发表评论:

最近发表
网站分类
标签列表