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.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 optionsif(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{vm.$options=mergeOptions(resolveConstructorOptions(vm.constructor),options||{},vm)}/* istanbul ignore else */if(process.env.NODE_ENV!=='production'){initProxy(vm)}else{vm._renderProxy=vm}// expose real selfvm._self=vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm,'beforeCreate')initInjections(vm)// resolve injections before data/propsinitState(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)}}
constmount=Vue.prototype.$mountVue.prototype.$mount=function(el?: string|Element,hydrating?: boolean): Component{el=el&&query(el)/* istanbul ignore if */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.templateif(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')}const{ render, staticRenderFns }=compileToFunctions(template,{
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)}
首先缓存了原型上的 $mount 方法,然后再重新定义该方法
首先限制el不能挂载到根节点上,比如body、html
如果没有定义render方法,则会把 el 或者 template 字符串转换成 render 方法,也就是说,所有的Vue组件的渲染都需要render方法
exportfunctionmountComponent(vm: Component,el: ?Element,hydrating?: boolean): Component{vm.$el=elif(!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{warn('Failed to mount component: template or render function not defined.',vm)}}}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=()=>{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,{before(){if(vm._isMounted){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}
mountComponent 核心就是先调用 vm._render 方法先生成虚拟 Node,再实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,最终调用 vm._update 更新 DOM
Vue.prototype._render=function(): VNode{constvm: Component=thisconst{ render, _parentVnode }=vm.$options// reset _rendered flag on slots for duplicate slot checkif(process.env.NODE_ENV!=='production'){for(constkeyinvm.$slots){// $flow-disable-linevm.$slots[key]._rendered=false}}if(_parentVnode){vm.$scopedSlots=_parentVnode.data.scopedSlots||emptyObject}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode=_parentVnode// render selfletvnodetry{vnode=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'){if(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}}else{vnode=vm._vnode}}// 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}
// wrapper function for providing a more flexible interface// without getting yelled at by flow
export functioncreateElement(context: Component,tag: any,data: any,children: any,normalizationType: any,alwaysNormalize: boolean): VNode|Array<VNode>{if(Array.isArray(data)||isPrimitive(data)){normalizationType=childrenchildren=datadata=undefined}if(isTrue(alwaysNormalize)){normalizationType=ALWAYS_NORMALIZE}return_createElement(context,tag,data,children,normalizationType)}
Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护。
new Vue() 初始化
执行
new Vue()
时,调用了this._init(options)
方法在
src/core/instance/init.js
里面有定义Vue初始化干了这么几件事情:合并配置、初始化生命周期、初始化事件中心、初始化渲染、初始化data、props、computed、watcher等
在最后,如果检测到有
el
属性,调用vm.$mount(vm.$options.el)
方法挂载vm,目标是把模板渲染成最终的DOM实例挂载的实现
$mount
方法跟平台、构建方式有关,直接看带编译版本的实现src/platform/web/entry-runtime-with-compiler.js
首先缓存了原型上的
$mount
方法,然后再重新定义该方法首先限制
el
不能挂载到根节点上,比如body
、html
如果没有定义
render
方法,则会把el
或者template
字符串转换成render
方法,也就是说,所有的Vue组件的渲染都需要render
方法转换过程(在线编译)是通过
compileToFunctions
方法实现的,最后调用$mount
方法挂载那么原来的
$mount
方法是什么样的呢?在
src/platform/web/runtime/index.js
中定义,之所以这么设计完全是为了复用,因为它是可以被 runtime only 版本的 Vue 直接使用的el
表示挂载的元素,既可以是字符串,也可以是DOM对象第二个参数跟服务端渲染有关,暂且不管
$mount
最后调用的是mountComponent
方法,这个方法定义在src/core/instance/lifecycle.js
中mountComponent
核心就是先调用vm._render
方法先生成虚拟 Node,再实例化一个渲染Watcher
,在它的回调函数中会调用updateComponent
方法,最终调用vm._update
更新 DOM函数最后判断为根节点的时候设置
vm._isMounted
为 true, 表示这个实例已经挂载了,同时执行mounted
钩子函数。 这里注意vm.$vnode
表示 Vue 实例的父虚拟 Node,所以它为Null
则表示当前是根 Vue 的实例。Watcher
在这里起到两个作用mountComponent
完成整个渲染工作,最核心的两个方法vm._render
vm._update
render
Vue 的
_render
方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node,定义在src/core/instance/render.js
vm._render
最终是通过执行createElement
方法并返回的是 vnode,它是一个虚拟 NodeVue 2.0 相比 Vue 1.0 最大的升级就是利用了 Virtual DOM
下面看下
createElement
方法,在src/core/vdom/create-elemenet.js
在
_createElement
方法中首先会进行children 的规范化,变成一个类型为 VNode 的 Array
接下来会去创建一个 VNode 的实例,然后展开后变成 VNode Tree
最后调用
vm._update
把VNode 渲染成一个真实的 DOM 并渲染出来update
也是实例的一个私有方法,调用时机有两个
作用就是把VNode渲染成真实的DOM,定义在
src/core/instance/lifecycle.js
中核心代码是,重点是调用
__patch__
方法此处省略一大堆逻辑。。。
整个过程就是递归创建了一个完整的 DOM 树并插入到 Body 上
这里涉及递归调用,整个 vnode 树节点的插入顺序是先子后父
The text was updated successfully, but these errors were encountered: