当前位置: 首页 > Web前端 > vue.js

Vue源码分析——从实例化到渲染过程

时间:2023-04-01 00:39:23 vue.js

本文Vue版本为2.5.17,分析为Runtime+Compiler构建的Vue.js。在Vue.js2.0中,最终是通过render函数来渲染的。如果它包含模板属性,则需要将模板编译成渲染函数。那么这个编译过程会在运行时发生,所以需要一个带有Compiler的版本。本文是Vue源码介绍系列的第一篇。主要是对Vue实例化进行总结整合,将render函数转化为vnode,生成挂载真实DOM的主流程。详情请参考源码。第二部分将介绍组件化过程。vue源码比较复杂,需要耐心反复理解调试。不懂的可以多调试问百度。罗马不是一天建成的。相信坚持会有收获~具体调试可以下载vue.js,然后打断点调试器调试。

首先分析上图流程1.定义VuefunctionVue(options){if("development"!=='production'&&!(thisinstanceofVue)){warn('Vue是一个构造函数,应该使用`new`关键字调用');}this._init(options);}initMixin(Vue);//定义_initstateMixin(Vue);//定义$set$get$delete$watch等eventsMixin(Vue);//定义事件$on$once$off$emitlifecycleMixin(Vue);//定义_update$forceUpdate$destroyrenderMixin(Vue);//定义_render返回虚拟dom2.initMixin实例化Vue时,执行_init,_init定义在initMixinVue.prototype._init=function(options){//合并选项if(options&&options._isComponent){initInternalComponent(vm,options);//组件合并}else{//非组件合并vm.$options=mergeOptions(resolveConstructorOptions(vm.constructor),options||{},vm);}初始化生命周期(虚拟机);//定义vm.$parentvm.$rootvm.$childrenvm.$refs等initEvents(vm);//定义vm._eventsvm._hasHookEvent等initRender(vm);//定义$createElement$ccallHook(vm,'beforeCreate');//挂载beforeCreate钩子函数initInjections(vm);//在data/props之前解析注入initState(vm);//初始化props方法datacomputedwatchetc.methodinitProvide(vm);//resolveprovideafterdata/propscallHook(vm,'created');//mount创建的钩子函数if(vm.$options.el){vm.$mount(vm.$options.el);//实例挂载并渲染dom}};3.$mountvue最终通过render函数将dom编译成虚拟dom//构建render函数if(!options.render){//如果没有render属性,则编译模板template并转为render}//最后调用mountreturnmount.call(this,el,hydrating)//mount调用mountComponentreturnmountComponent(this,el,hydrating)4.mountComponent通过newWatcher调用updateComponent,vm._render获取虚拟dom,vm._update将虚拟dom转为真实dom并挂载到页面//hydrating表示服务端渲染hydrating=>falseupdateComponent=function(){vm._update(vm._render(),hydrating);//关键点};5._render_render执行render函数,返回vnoeVue.prototype._render=function(){//这里的vm._renderProxy相当于vmvnode=render.call(vm._renderProxy,vm.$createElement);}$createElement主要是参数重载,整合成统一格式后调用_createElement函数functioncreateElement(context,tag,data,children,normalizationType,alwaysNormalize){//参数重载if(Array.isArray(data)||isPrimitive(data)){归一化类型=儿童;孩子=数据;数据=未定义;}if(isTrue(alwaysNormalize)){normalizationType=ALWAYS_NORMALIZE;}return_createElement(context,tag,data,children,normalizationType)}_createElement主要根据tag标签判断是组件还是普通节点标签,返回对应的vnode虚拟domfufunction_createElement(context,tag,data,children,normalizationType){if(typeoftag==='string'){//平台内置元素vnode=newVNode(config.parsePlatformTagName(tag),data,children,undefined,未定义,上下文);}else{//直接组件选项/构造函数vnode=createComponent(tag,data,context,children);}}6._update_update主要实现将vnode转化为实际的dom,注入到页面中,同时销毁页面模板Define_update//_update=>__patch__Vue.prototype._update=function(vnode,hydrating){if(!prevVnode){//初始化vm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/*仅删除*/);}else{//当更新时vm.$el=vm.__patch__(prevVnode,vnode);}}定义__patch__//__patch__=>patchVue.prototype.__patch__=inBrowser?补丁:noop;definepatch,//使用functioncurrying将服务端和浏览器的差异整合到模块中,nodeOps是dom元素操作方法的集合varmodules=platformModules.concat(baseModules);varpatch=createPatchFunction({nodeOps:nodeOps,modules:modules});definecreatePatchFunctionfunctioncreatePatchFunction(backend){返回函数patch(oldVnode,vnode,hydrating,removeOnly){//创建一个新节点createElm(vnode,insertedVnodeQueue,oldElm._leaveCb?null:parentElm,nodeOps.nextSibling(oldElm));//销毁节点if(isDef(parentElm)){removeVnodes(parentElm,[oldVnode],0,0);}elseif(isDef(oldVnode.tag)){invokeDestroyHook(oldVn颂);}}}定义createElm,根据vnode创建真正的dom函数);if(isDef(data)){invokeCreateHooks(vnode,insertedVnodeQueue);}//子节点插入父节点insert(parentElm,vnode.elm,refElm);}以上是Vue从实例化到调用render函数生成vnode,通过vnode转换成真正的dom节点patch,以及挂载到页面的过程,后面第二部分会扫描vue的组件化和生命周期过程。