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
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)}
Vue.prototype._init=function(options?: Object){constvm: Component=this// a uidvm._uid=uid++letstartTag,endTag/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){startTag=`vue-perf-start:${vm._uid}`endTag=`vue-perf-end:${vm._uid}`mark(startTag)}// a flag to avoid this being observedvm._isVue=true// merge options// 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法if(options&&options._isComponent){// optimize internal component instantiation// since dynamic options merging is pretty slow, and none of the// internal component options needs special treatment.initInternalComponent(vm,options)}else{// 合并vue属性vm.$options=mergeOptions(resolveConstructorOptions(vm.constructor),options||{},vm)}/* istanbul ignore else */if(process.env.NODE_ENV!=='production'){// 初始化proxy拦截器initProxy(vm)}else{vm._renderProxy=vm}// expose real selfvm._self=vm// 初始化组件生命周期标志位initLifecycle(vm)// 初始化组件事件侦听initEvents(vm)// 初始化渲染方法initRender(vm)callHook(vm,'beforeCreate')// 初始化依赖注入内容,在初始化data、props之前initInjections(vm)// resolve injections before data/props// 初始化props/data/method/watch/methodsinitState(vm)initProvide(vm)// resolve provide after data/propscallHook(vm,'created')/* istanbul ignore if */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)}}
functioninitData(vm: Component){letdata=vm.$options.data// 获取到组件上的datadata=vm._data=typeofdata==='function'
? getData(data,vm)
: data||{}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 instanceconstkeys=Object.keys(data)constprops=vm.$options.propsconstmethods=vm.$options.methodsleti=keys.lengthwhile(i--){constkey=keys[i]if(process.env.NODE_ENV!=='production'){// 属性名不能与方法名重复if(methods&&hasOwn(methods,key)){warn(`Method "${key}" has already been defined as a data property.`,vm)}}// 属性名不能与state名称重复if(props&&hasOwn(props,key)){process.env.NODE_ENV!=='production'&&warn(`The data property "${key}" is already declared as a prop. `+`Use prop default value instead.`,vm)}elseif(!isReserved(key)){// 验证key值的合法性// 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据proxy(vm,`_data`,key)}}// observe data// 响应式监听data是数据的变化observe(data,true/* asRootData */)}
仔细阅读上面的代码,我们可以得到以下结论:
初始化顺序:props、methods、data
data定义的时候可选择函数形式或者对象形式(组件只能为函数形式)
关于数据响应式在这就不展开详细说明
上文提到挂载方法是调用vm.$mount方法
源码位置:
Vue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{// 获取或查询元素
el =el&&query(el)/* istanbul ignore if */// vue 不允许直接挂载到body或页面文档上if(el===document.body||el===document.documentElement){process.env.NODE_ENV!=='production'&&warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`)returnthis}constoptions=this.$options// resolve template/el and convert to render functionif(!options.render){lettemplate=options.template// 存在template模板,解析vue模板文件if(template){if(typeoftemplate==='string'){if(template.charAt(0)==='#'){template=idToTemplate(template)/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&!template){warn(`Template element not found or is empty: ${options.template}`,this)}}}elseif(template.nodeType){template=template.innerHTML}else{if(process.env.NODE_ENV!=='production'){warn('invalid template option:'+template,this)}returnthis}}elseif(el){// 通过选择器获取元素内容template=getOuterHTML(el)}if(template){/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compile')}/** * 1.将temmplate解析ast tree * 2.将ast tree转换成render语法字符串 * 3.生成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/* istanbul ignore if */if(process.env.NODE_ENV!=='production'&&config.performance&&mark){mark('compile end')measure(`vue ${this._name} compile`,'compile','compile end')}}}returnmount.call(this,el,hydrating)}
// public mount methodVue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{
el =el&&inBrowser ? query(el) : undefined// 渲染组件returnmountComponent(this,el,hydrating)}
调用mountComponent渲染组件
exportfunctionmountComponent(vm: Component,el: ?Element,hydrating?: boolean): Component{vm.$el=el// 如果没有获取解析的render函数,则会抛出警告// render是解析模板文件生成的if(!vm.$options.render){vm.$options.render=createEmptyVNodeif(process.env.NODE_ENV!=='production'){/* istanbul ignore if */if((vm.$options.template&&vm.$options.template.charAt(0)!=='#')||vm.$options.el||el){warn('You are using the runtime-only build of Vue where the template '+'compiler is not available. Either pre-compile the templates into '+'render functions, or use the compiler-included build.',vm)}else{// 没有获取到vue的模板文件warn('Failed to mount component: template or render function not defined.',vm)}}}// 执行beforeMount钩子callHook(vm,'beforeMount')letupdateComponent/* istanbul ignore if */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=()=>{// 实际调⽤是在lifeCycleMixin中定义的_update和renderMixin中定义的_rendervm._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 defined// 监听当前组件状态,当有数据变化时,更新组件newWatcher(vm,updateComponent,noop,{before(){if(vm._isMounted&&!vm._isDestroyed){// 数据更新引发的组件更新callHook(vm,'beforeUpdate')}}},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=truecallHook(vm,'mounted')}returnvm}
阅读上面代码,我们得到以下结论:
会触发beforeCreate钩子
定义updateComponent渲染页面视图的方法
监听组件数据,一旦发生变化,触发beforeUpdate生命钩子
updateComponent方法主要执行在vue初始化时声明的render,update方法
render的作用主要是生成vnode
源码位置:src\core\instance\render.js
// 定义vue 原型上的render方法Vue.prototype._render=function(): VNode{constvm: Component=this// render函数来自于组件的optionconst{ render, _parentVnode }=vm.$optionsif(_parentVnode){vm.$scopedSlots=normalizeScopedSlots(_parentVnode.data.scopedSlots,vm.$slots,vm.$scopedSlots)}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode=_parentVnode// render selfletvnodetry{// There's no need to maintain a stack because all render fns are called// separately from one another. Nested component's render fns are called// when parent component is patched.currentRenderingInstance=vm// 调用render方法,自己的独特的render方法, 传入createElement参数,生成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/* istanbul ignore else */if(process.env.NODE_ENV!=='production'&&vm.$options.renderError){try{vnode=vm.$options.renderError.call(vm._renderProxy,vm.$createElement,e)}catch(e){handleError(e,vm,`renderError`)vnode=vm._vnode}}else{vnode=vm._vnode}}finally{currentRenderingInstance=null}// if the returned array contains only a single node, allow itif(Array.isArray(vnode)&&vnode.length===1){vnode=vnode[0]}// return empty vnode in case the render function errored outif(!(vnodeinstanceofVNode)){if(process.env.NODE_ENV!=='production'&&Array.isArray(vnode)){warn('Multiple root nodes returned from render function. Render function '+'should return a single root node.',vm)}vnode=createEmptyVNode()}// set parentvnode.parent=_parentVnodereturnvnode}
_update主要功能是调用patch,将vnode转换为真实DOM,并且更新到页面中
源码位置:src\core\instance\lifecycle.js
Vue.prototype._update=function(vnode: VNode,hydrating?: boolean){constvm: Component=thisconstprevEl=vm.$elconstprevVnode=vm._vnode// 设置当前激活的作用域constrestoreActiveInstance=setActiveInstance(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 */)}else{// updatesvm.$el=vm.__patch__(prevVnode,vnode)}restoreActiveInstance()// update __vue__ referenceif(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实例挂载的过程
一、思考
我们都听过知其然知其所以然这句话
那么不知道大家是否思考过
new Vue()
这个过程中究竟做了些什么?过程中是如何完成数据的绑定,又是如何将数据渲染到视图的等等
一、分析
首先找到
vue
的构造函数源码位置:src\core\instance\index.js
options
是用户传递过来的配置项,如data、methods
等常用的方法vue
构建函数调用_init
方法,但我们发现本文件中并没有此方法,但仔细可以看到文件下方定定义了很多初始化方法首先可以看
initMixin
方法,发现该方法在Vue
原型上定义了_init
方法源码位置:src\core\instance\init.js
仔细阅读上面的代码,我们得到以下结论:
在调用
beforeCreate
之前,数据初始化并未完成,像data
、props
这些属性无法访问到到了
created
的时候,数据已经初始化完成,能够访问data
、props
这些属性,但这时候并未完成dom
的挂载,因此无法访问到dom
元素挂载方法是调用
vm.$mount
方法initState
方法是完成props/data/method/watch/methods
的初始化源码位置:src\core\instance\state.js
我们和这里主要看初始化
data
的方法为initData
,它与initState
在同一文件上仔细阅读上面的代码,我们可以得到以下结论:
初始化顺序:
props
、methods
、data
data
定义的时候可选择函数形式或者对象形式(组件只能为函数形式)关于数据响应式在这就不展开详细说明
上文提到挂载方法是调用
vm.$mount
方法源码位置:
阅读上面代码,我们能得到以下结论:
不要将根元素放到
body
或者html
上可以在对象中定义
template/render
或者直接使用template
、el
表示元素选择器最终都会解析成
render
函数,调用compileToFunctions
,会将template
解析成render
函数对
template
的解析步骤大致分为以下几步:将
html
文档片段解析成ast
描述符将
ast
描述符解析成字符串生成
render
函数生成
render
函数,挂载到vm
上后,会再次调用mount
方法源码位置:src\platforms\web\runtime\index.js
调用
mountComponent
渲染组件阅读上面代码,我们得到以下结论:
beforeCreate
钩子updateComponent
渲染页面视图的方法beforeUpdate
生命钩子updateComponent
方法主要执行在vue
初始化时声明的render
,update
方法render
的作用主要是生成vnode
源码位置:src\core\instance\render.js
_update
主要功能是调用patch
,将vnode
转换为真实DOM
,并且更新到页面中源码位置:src\core\instance\lifecycle.js
三、结论
new Vue
的时候调用会调用_init
方法$set
、$get
、$delete
、$watch
等方法$on
、$off
、$emit
、$off
等事件_update
、$forceUpdate
、$destroy
生命周期调用
$mount
进行页面的挂载挂载的时候主要是通过
mountComponent
方法定义
updateComponent
更新函数执行
render
生成虚拟DOM
_update
将虚拟DOM
生成真实DOM
结构,并且渲染到页面中参考文献
The text was updated successfully, but these errors were encountered: