既然是源码分析,所以大家最好对着源码,一步一步来看。本篇文章,旨在通过一个简单的小栗子,带着大家从vm
创建,到显示到页面上都经历了哪些过程。
例子如下:
<div id="app">
<p>{{message}}</p>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
message: '第一个vue实例'
}
})
</script>
创建对象,当然要从构造函数看起,构造函数在src/core/instance/index.js
中。
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
我们看到,它首先判断了是不是通过new
关键词创建,然后调用了this._init(options)
。_init
函数是在src/core/instance/init.js
中添加的。我们先把整个函数都拿出来,然后看看每一步都做了什么。
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
// 性能统计相关
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-init:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// 内部使用Vnode部分使用
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 self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
// 性能相关
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
首先一进来,我们给当前vm
添加了一个唯一的_uid
,然后vm._isVue
设为true
(监听对象变化时用于过滤vm)。
_isComponent
是内部创建子组件时才会添加为true
的属性,我们的小栗子会直接走到了else
里面。mergeOptions
用于合并两个对象,不同于Object.assign
的简单合并,它还对数据还进行了一系列的操作,且源码中多处用到该方法,所以后面会详细讲解这个方法。resolveConstructorOptions
方法在Vue.extend中做了详细的解释,它的作用是合并构造器及构造器父级上定义的options
。
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
// 有super属性,说明Ctor是通过Vue.extend()方法创建的子类
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
这里的Ctor
就是vm.constructor
也就是Vue
对象,在上一篇文章中,其实我们提到过,在/src/core/global-api/index
文件中,我们给Vue
添加了一些全局的属性或方法。
Vue.options = Object.create(null)
// Vue.options.components、Vue.options.directives、Vue.options.filters
config._assetTypes.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// Vue.options._base
Vue.options._base = Vue
// Vue.options.components.KeepAlive
extend(Vue.options.components, builtInComponents)
所以,这里打印一下Ctor.options
,如下所示:
Ctor.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: {},
_base: Vue
}
Ctor.super
是在调用Vue.extend
时,才会添加的属性,这里先直接跳过。所以mergeOptions
的第一个参数就是上面的Ctor.options
,第二个参数是我们传入的options
,第三个参数是当前对象vm
。
mergeOptions
是Vue
中处理属性的合并策略的地方。
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
// 如果有options.components,则判断是否组件名是否合法
checkComponents(child)
}
// 格式化child的props
normalizeProps(child)
// 格式化child的directives
normalizeDirectives(child)
// options.extends
const extendsFrom = child.extends
if (extendsFrom) {
parent = typeof extendsFrom === 'function'
? mergeOptions(parent, extendsFrom.options, vm)
: mergeOptions(parent, extendsFrom, vm)
}
// options.mixins
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
let mixin = child.mixins[i]
if (mixin.prototype instanceof Vue) {
mixin = mixin.options
}
parent = mergeOptions(parent, mixin, vm)
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
前面和components
、props
、directives
、extends
、mixins
相关的内容我们暂且忽略,我们知道Vue
提供了配置optionMergeStrategies
对象,来让我们手动去控制属性的合并策略,这里的strats[key]
就是key
属性的合并方法。
function mergeAssets (parentVal: ?Object, childVal: ?Object): Object {
const res = Object.create(parentVal || null)
return childVal
? extend(res, childVal)
: res
}
config._assetTypes.forEach(function (type) {
strats[type + 's'] = mergeAssets
})
_assetTypes
就是components
、directives
、filters
,这三个的合并策略都一样,这里我们都返回了parentVal
的一个子对象。
data
属性的合并策略,是也是Vue
内置的,如下:
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
const keys = Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
mergeData(toVal, fromVal)
}
}
return to
}
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (!childVal) {
return parentVal
}
if (typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
if (!parentVal) {
return childVal
}
return function mergedDataFn () {
return mergeData(
childVal.call(this),
parentVal.call(this)
)
}
} else if (parentVal || childVal) {
return function mergedInstanceDataFn () {
// instance merge
const instanceData = typeof childVal === 'function'
? childVal.call(vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm)
: undefined
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
这里vm
且data
都不为空,所以会走到else if
,返回的是mergedInstanceDataFn
方法。关于mergedInstanceDataFn
方法,我们都知道,子组件中定义data
时,必须是一个函数,这里简单的判断了是函数就执行,不是就返回自身的值。然后通过mergeData
去合并,其实就是递归把defaultData
合并到instanceData
,并观察。
最后合并之后的vm.$option
如下:
vm.$option = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: {},
_base: Vue,
el: '#app',
data: function mergedInstanceDataFn(){}
}
回到我们的_init
接着放下看,之后如果是开发环境,则vm._renderProxy
值为一个Proxy
代理对象,生产环境就是vm
自身,这里不展开赘述。
接着就是一系列的操作,我们一个一个来看。
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
该方法比较简单,主要就是给vm
对象添加了$parent
、$root
、$children
属性,以及一些其它的生命周期相关的标识。
options.abstract
用于判断是否是抽象组件,组件的父子关系建立会跳过抽象组件,抽象组件比如keep-alive
、transition
等。所有的子组件$root
都指向顶级组件。
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
该方法初始化事件相关的属性,_parentListeners是父组件中绑定在自定义标签上的事件,供子组件处理。
export function initRender (vm: Component) {
vm.$vnode = null
vm._vnode = null
vm._staticTrees = null
const parentVnode = vm.$options._parentVnode
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
这里给vm
添加了一些虚拟dom、slot
等相关的属性和方法。
然后会调用beforeCreate
钩子函数。
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
export function initInjections (vm: Component) {
const inject: any = vm.$options.inject
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
// isArray here
const isArray = Array.isArray(inject)
const keys = isArray
? inject
: hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const provideKey = isArray ? key : inject[key]
let source = vm
while (source) {
if (source._provided && provideKey in source._provided) {
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, source._provided[provideKey], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, source._provided[provideKey])
}
break
}
source = source.$parent
}
}
}
}
这两个配套使用,用于将父组件_provided
中定义的值,通过inject
注入到子组件,且这些属性不会被观察。简单的例子如下:
<div id="app">
<p>{{message}}</p>
<child></child>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
message: '第一个vue实例'
},
components: {
child: {
template: "<div>{{a}}</div>",
inject: ['a']
}
},
provide: {
a: 'a'
}
})
</script>
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch) initWatch(vm, opts.watch)
}
这里主要就是操作数据了,props
、methods
、data
、computed
、watch
,从这里开始就涉及到了Observer
、Dep
和Watcher
,网上讲解双向绑定的文章很多,之后我也会单独去讲解这一块。而且,这里对数据操作也比较多,在讲完双向绑定的内容后,有时间我们再来说一说Vue
对我们传入的数据都进行了什么操作。
到这一步,我们看看我们的vm
对象变成了什么样:
// _init
vm._uid = 0
vm._isVue = true
vm.$options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: {},
_base: Vue,
el: '#app',
data: function mergedInstanceDataFn(){}
}
vm._renderProxy = vm
vm._self = vm
// initLifecycle
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
// initEvents
vm._events = Object.create(null)
vm._hasHookEvent = false
// initRender
vm.$vnode = null
vm._vnode = null
vm._staticTrees = null
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 在 initState 中添加的属性
vm._watchers = []
vm._data
vm.message
然后,就会调用我们的created
钩子函数。
我们看到create
阶段,基本就是对传入数据的格式化、数据的双向绑定、以及一些属性的初始化。
打开src/entries/web-runtime-with-compiler.js
。
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === '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
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}
}
return mount.call(this, el, hydrating)
}
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
首先,通过mount = Vue.prototype.$mount
保存之前定义的$mount
方法,然后重写。
这里的query
可以理解为document.querySelector
,只不过内部判断了一下el
是不是字符串,不是的话就直接返回,所以我们的el
也可以直接传入dom元素。
之后判断是否有render
函数,如果有就不做处理直接执行mount.call(this, el, hydrating)
。如果没有render
函数,则获取template
,template
可以是#id
、模板字符串、dom元素,如果没有template
,则获取el
以及其子内容作为模板。
compileToFunctions
是对我们最后生成的模板进行解析,生成render
。这里的内容也比较多,简单说一下:
该方法创建的地方在src/compiler/index.js
的createCompiler
中。
function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
optimize(ast, options)
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}
export function createCompiler (baseOptions: CompilerOptions) {
const functionCompileCache: {
[key: string]: CompiledFunctionResult;
} = Object.create(null)
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
...
const compiled = baseCompile(template, finalOptions)
...
return compiled
}
function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
options = options || {}
...
// compile
const compiled = compile(template, options)
...
return (functionCompileCache[key] = res)
}
return {
compile,
compileToFunctions
}
}
compileToFunctions
中调用了compile
,compile
中调用了baseCompile
。主要的操作就是baseCompile
中的三步。
第一步, const ast = parse(template.trim(), options)
。这里是解析template
,生成ast。我们的例子生成的ast
如下:
{
type: 1,
tag: 'div',
plain: false,
parent: undefined,
attrs: [{name:'id', value: '"app"'}],
attrsList: [{name:'id', value: 'app'}],
attrsMap: {id: 'app'},
children: [{
type: 1,
tag: 'p',
plain: true,
parent: ast,
attrs: [],
attrsList: [],
attrsMap: {},
children: [{
expression: "_s(message)",
text: "{{message}}",
type: 2
}]
}
第二步,optimize(ast, options)
主要是对ast进行优化,分析出静态不变的内容部分,增加了部分属性:
{
type: 1,
tag: 'div',
plain: false,
parent: undefined,
attrs: [{name:'id', value: '"app"'}],
attrsList: [{name:'id', value: 'app'}],
attrsMap: {id: 'app'},
static: false,
staticRoot: false,
children: [{
type: 1,
tag: 'p',
plain: true,
parent: ast,
attrs: [],
attrsList: [],
attrsMap: {},
static: false,
staticRoot: false,
children: [{
expression: "_s(message)",
text: "{{message}}",
type: 2,
static: false
}]
}
因为我们这里只有一个动态的{{message}}
,所以static
和staticRoot
都是false
。
最后一步,code = generate(ast, options)
,就是根据ast生成render
函数和staticRenderFns
数组。
最后生成的render
如下:
render = function () {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',[_v(_s(message))])])}
}
在src/core/instance/render.js
中,我们曾经添加过如下多个函数,这里和render
内返回值调用一一对应。
Vue.prototype._o = markOnce
Vue.prototype._n = toNumber
Vue.prototype._s = _toString
Vue.prototype._l = renderList
Vue.prototype._t = renderSlot
Vue.prototype._q = looseEqual
Vue.prototype._i = looseIndexOf
Vue.prototype._m = renderStatic
Vue.prototype._f = resolveFilter
Vue.prototype._k = checkKeyCodes
Vue.prototype._b = bindObjectProps
Vue.prototype._v = createTextVNode
Vue.prototype._e = createEmptyVNode
Vue.prototype._u = resolveScopedSlots
这里的staticRenderFns
目前是一个空数组,其实它是用来保存template
中,静态内容的render
,比如我们把例子中的模板改为:
<div id="app">
<p>这是<span>静态内容</span></p>
<p>{{message}}</p>
</div>
staticRenderFns
就会变为:
staticRenderFns = function () {
with(this){return _c('p',[_v("这是"),_c('span',[_v("静态内容")])])}
}
从上面的内容,我们可以知道其实template
最终还是转换为render
函数,这也是官方文档中所说的render
函数更加底层。
前面保存了mount = Vue.prototype.$mount
,最后又调用了mount
方法,我们来看看它干了什么。
打开src/entries/web-runtime.js
。
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
这里仅仅是返回了mountComponent
的执行结果,跟着代码的步伐,我们又回到了src/core/instance/lifecycle.js
。
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
...
callHook(vm, 'beforeMount')
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
上面的代码我简单的做了一些精简。可以看到首先调用了beforeMount
钩子函数,新建了一个Watcher
对象,绑定在vm._watcher
上,之后就是判断如果vm.$vnode == null
,则设置vm._isMounted = true
并调用mounted
钩子函数,最后返回vm
对象。
感觉上似乎有头没尾似得。这里就又不得不提Watcher
了,先简单概述一下。
打开src/core/observer/watcher.js
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
vm._watchers.push(this)
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
...
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
get () {
pushTarget(this)
let value
const vm = this.vm
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
value = this.getter.call(vm, vm)
}
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
在构造函数中,我们会把expOrFn
也就是updateComponent
赋值给this.getter
,并且在获取this.value
的值时会调用this.get()
,这里的this.lazy
默认值是false
,在computed
属性中创建的Watcher
会传入true
。
在this.get()
中,我们会调用this.getter
,所以上面的例子中,updateComponent
方法会被调用,所以接下来沿着updateComponent
再一路找下去。
updateComponent
中调用了vm._render()
函数,该方法在src/core/instance/render.js
中。
Vue.prototype._render = function (): VNode {
const vm: Component = this
const {
render,
staticRenderFns,
_parentVnode
} = vm.$options
...
if (staticRenderFns && !vm._staticTrees) {
vm._staticTrees = []
}
vm.$vnode = _parentVnode
// render self
let vnode
vnode = render.call(vm._renderProxy, vm.$createElement)
...
if (!(vnode instanceof VNode)) {
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 parent
vnode.parent = _parentVnode
return vnode
}
在该方法中,其实主要就是调用了vm.$options.render
方法,我们再拿出render
方法,看看它都干了什么。
render = function () {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',[_v(_s(message))])])}
}
函数调用过程中的this
,是vm._renderProxy
,是一个Proxy
代理对象或vm
本身。我们暂且把它当做vm
本身。
_c
是(a, b, c, d) => createElement(vm, a, b, c, d, false)
。我们简单说一下createElement
干了什么。a
是要创建的标签名,这里是div
。接着b
是data
,也就是模板解析时,添加到div
上的属性等。c
是子元素数组,所以这里又调用了_c
来创建一个p
标签。
_v
是createTextVNode
,也就是创建一个文本结点。_s
是_toString
,也就是把message
转换为字符串,在这里,因为有with(this)
,所以message
传入的就是我们data
中定义的第一个vue实例
。
所以,从上面可以看出,render
函数返回的是一个VNode
对象,也就是我们的虚拟dom对象。它的返回值,将作为vm._update
的第一个参数。我们接着看该函数,返回src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
从mountComponent
中我们知道创建Watcher
对象先于vm._isMounted = true
。所以这里的vm._isMounted
还是false
,不会调用beforeUpdate
钩子函数。
下面会调用vm.__patch__
,在这一步之前,页面的dom还没有真正渲染。该方法包括真实dom的创建、虚拟dom的diff修改、dom的销毁等,具体细节且等之后满满分析。
至此,一个Vue
对象的创建到显示到页面上的流程基本介绍完了。有问题欢迎吐槽~