前言
Vue 这个框架,我们使用的很是熟练了,那么我们有人想过new Vue()
帮我们做了什么?
js
const app = new Vue({
el:"#app",
// 对象格式
data:{
foo:"foo"
},
// 函数格式
data(){
return {
foo:"foo"
}
}
})
本文涉及源码版本:2.7.14
一、new Vue() 做了什么?
首先,我们可以先了解一下,new Vue
涉及了以下五个方面,所以我们可以从这五个方面进行分析:
- init初始化
- mount挂载
- compiler编译
- render渲染
- patcch补丁
1.1 init初始化
执行 init 操作,包括且不限制 initLifecycle、initState等。
通过入口文件,将配置项传入,调用 _init
方法进行初始化
Vue方法的源码如下:vue-2.7.14\src\core\instance\index.ts
js
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'
import type { GlobalAPI } from 'types/global-api'
function Vue(options) {
if (__DEV__ && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
//@ts-expect-error Vue has function type
initMixin(Vue)
//@ts-expect-error Vue has function type
stateMixin(Vue)
//@ts-expect-error Vue has function type
eventsMixin(Vue)
//@ts-expect-error Vue has function type
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
renderMixin(Vue)
export default Vue as unknown as GlobalAPI
通过上面的源码我们可以了解到,new Vue()
在不出意外的情况下,执行了方法this._init()
this._init()
方法主要步骤:
- 合并配置项
- 初始化生命周期、初始化事件中心、初始化渲染、调用
beforCreate
钩子,初始化injection
、state/data/props/computed...
、provide
、调用create
钩子; - 最后通过判断有无
vm.$options.el
进行vm.$mount
this._init()
源码:vue-2.7.14\src\core\instance\init.ts
js
export function initMixin(Vue: typeof Component) {
Vue.prototype._init = function (options?: Record<string, any>) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
// 性能检测
if (__DEV__ && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// 一个标志,将其标记为 vue 实例,而无需执行实例
vm._isVue = true
// 避免实例添加observer
vm.__v_skip = true
// 影响范围(作用域)
vm._scope = new EffectScope(true /* detached */)
vm._scope._vm = true
// 合并配置:业务逻辑以及组件的一些特性全都放到了 vm.$options 中
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 as any)
} else { // 顶层的vm
vm.$options = mergeOptions(
// 一些内置组件(keep-alive...)和指令(show、model...)
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
}
/* istanbul ignore else */
// 初始化vm._renderProxy 为后期渲染做准备
// 开发环境并支持proxy vm._renderProxy = new Proxy(vm)
if (__DEV__) {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化生命周期
initLifecycle(vm)
// 初始化事件
initEvents(vm)
// 初始化渲染
initRender(vm)
// 调用 beforeCreate 回调
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
// 初始化 injections
initInjections(vm) // resolve injections before data/props
// 初始化 state
initState(vm)
// 初始化 provide
initProvide(vm) // resolve provide after data/props
// 调用 created 回调
callHook(vm, 'created')
/* istanbul ignore if */
// 性能检测
if (__DEV__ && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
// el存在就进入下一步操作 - 挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
1.2 mount挂载
通过 init 源码我们可以了解到在进行一系列初始化操作后,下一步操作就是挂载
挂载过程中完成了最重要的两件事:
- 初始化
- 建立更新机制
1.2.1 生成 render 函数 -- vm.prototype.$mount()
vm.$mount()
方法主要作用是若配置项中没有 render
方法则将 template
作为编译函数的参数生成该方法,最后调用 runtime
中的 mount
方法
mount
源码:vue-2.7.14\src\platforms\web\runtime-with-compiler.ts
js
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
// 1. 不允许挂载在 html 和 body 上
if (el === document.body || el === document.documentElement) {
__DEV__ &&
warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
// 获得配置项
const options = this.$options
// resolve template/el and convert to render function
// render 不存在
if (!options.render) {
let template = options.template
// 2. 初始化 template
if (template) {
if (typeof template === 'string') {
// 如果模板开头是#,说明是一个ID选择器,通过idToTemplate获取相应的innerHtml
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (__DEV__ && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) { // 如果是node对象,直接获取innerHtml
template = template.innerHTML
} else { // 不合法配置项
if (__DEV__) {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// @ts-expect-error
template = getOuterHTML(el)
}
// 3. 将模板转化为 render 函数
if (template) {
/* istanbul ignore if */
// 性能检测
if (__DEV__ && config.performance && mark) {
mark('compile')
}
// 3.1 将模板转换为 render 函数,得到 ast 抽象树获得的render函数
const { render, staticRenderFns } = compileToFunctions(
template,
{
outputSourceRange: __DEV__,
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
},
this
)
// 3.2保存渲染函数 with(this){return _c('div',{attrs:{\"id\":\"app\"}},[_v(\"\\n......
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
// 性能检测
if (__DEV__ && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
// 4. 调用$mount方法
return mount.call(this, el, hydrating)
}
1.2.2 完成挂载------mountComponent
这是runtime
时的mount
方法。最后直接调用了mountComponent
方法。所以直接看mountComponent
方法吧。
源码:vue-2.7.14\src\platforms\web\runtime\index.ts
js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent
方法就是 vue 从 beforeMount
到 mounted
的过程。主要步骤如下:
render
函数是生成 DOM 的关键,所以在一开始会先判断是否存在,如果不存在创建一个空的虚拟DOM- 调用生命周期钩子
beforeMounts
- 生成
updateComponent
方法,用于触发页面的更新 - 生成
watcherOptions
配置,里面before
方法会触发beforeUpdate
钩子,将会在触发updateComponent
前调用 - 生成组件更新的
watcher
, 前面两部分是为了这部分做准备。new Watcher()
后触发updateComponent
方法的调用,生成页面虚拟DOM,将watcher
加入到影响页面变化data
的依赖收集器中,这样当data
发送变化时,就会触发页面更新,最终进行dom diff
,生成真实dom - 调用生命周期钩子
mounted
mountComponent
方法源码如下:vue-2.7.14\src\core\instance\lifecycle.ts
js
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
vm.$el = el
// render 不存在 - 警告
if (!vm.$options.render) {
// @ts-expect-error invalid type
vm.$options.render = createEmptyVNode
if (__DEV__) {
/* 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
)
}
}
}
// 调用钩子函数 beforeMount
callHook(vm, 'beforeMount')
// 初始化 updateComponent
let updateComponent
/* istanbul ignore if */
if (__DEV__ && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = 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._render() 得到Vnode
// vm._update() 更新页面
vm._update(vm._render(), hydrating)
}
}
// 页面更新前会调用 before 函数,触发钩子函数 beforeUpdate
const watcherOptions: WatcherOptions = {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}
if (__DEV__) {
watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
}
// 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
// 创建组件更新的 watcher,后面会加入到影响页面变化 data 的 deps
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
hydrating = false
// flush buffer for flush: "pre" watchers queued in setup()
const preWatchers = vm._preWatchers
if (preWatchers) {
for (let i = 0; i < preWatchers.length; i++) {
preWatchers[i].run()
}
}
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
// 挂载完成,触发狗仔函数 mounted
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
1.2.3 更新机制形成 - new Watcher()
这里主要分析上面 new Watcher()
后,如何触发 updateComponent
方法的调用。因此只涉及部分 Watcher
类的代码。
在 new Watcher()
构造方法里会初始化一些属性,最重要的是将 this.getter = expOrFn
,将 updateComponent
方法赋给了 this.getter
。最后初始化 this.value
时,调用了 this.get()
。
new watcher()
构造方法源码如下:vue-2.7.14\src\core\observer\watcher.ts
js
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
recordEffectScope(
this,
// if the active effect scope is manually created (not a component scope),
// prioritize it
activeEffectScope && !activeEffectScope._vm
? activeEffectScope
: vm
? vm._scope
: undefined
)
// 将 watcher 挂载到 vm._watcher 上
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
// options
// 根据 options 初始化一些属性
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
if (__DEV__) {
this.onTrack = options.onTrack
this.onTrigger = options.onTrigger
}
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.post = false
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = __DEV__ ? expOrFn.toString() : ''
// parse expression for getter
// render 类型 expOrFn 肯定是一个方法,所以 this.getter 就是页面更新方法了
if (isFunction(expOrFn)) {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
__DEV__ &&
warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
// 因为 render 类型的 watcher lazy 值不会是 true(只有computed才会是),所以接下来会调用 get 方法
this.value = this.lazy ? undefined : this.get()
}
在 get
方法中,可以看到这行代码 value = this.getter.call(vm, vm)
。就是这里调用了 updateComponent
方法。调用了 updateComponent
方法会触发 vm._update(vm.render(), hydrating)
。所以接下来分析 vm._render
方法。
get()
方法如下:vue-2.7.14\src\core\observer\watcher.ts
js
/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
// 相当于 Dep.target = this
pushTarget(this)
let value
const vm = this.vm
try {
// 调用 this.getter 方法,即调用了 updateComponent 方法
value = this.getter.call(vm, vm)
} catch (e: any) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
1.3 生成虚拟DOM - vm._render()
1.3.1 用户自定义 render 函数
这种情况是用户自定义 render
函数。上面调用 render
方法处,传了一个 vm.$createElement
函数作为参数,所以 h
即 vm.$createElement
js
new Vue({
router,
render: h => h(App),
}).$mount('#app')
找到 vm.$createElement
源码发现最终调用的是 createElement
方法。
vm.$createElement
源码:vue-2.7.14\src\core\instance\render.ts
js
// 这段代码在initRender中
// 用于用户自己手写的 render 函数
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
1.3.2 编译生成的 render 函数
这段html代码通过编译后生成的 render 函数如下:
js
<div id="app">
{{num1}}
{{hello}}
<button @click="changeNum">num1</button>
<button @click="changeNum2">num2</button>
<div v-for="(item,idx) of arr" :key="item">
{{item}}
</div>
<comp :msg="msg" @log-msg="logMsg"></comp>
</div>
代码中,使用了 with(this){}
说明 {}
所有引用都指向 this
对象(即vm
)。所以代码中的 _c = vm._c
,因此最终其实也是调用 createElement
函数生成虚拟DOM 。
在执行这段代码时,会读取到 data
中的属性,比如 num1、arr等等,读取时会调用这些属性的 getter
方法,在 getter
方法中会判断到 Dep.target
中存在值,会将该值存入依赖收集器,从而 完成依赖收齐。
属性发生改变时,触发 setter
方法,通过 dep
和 watcher
最终会调用 updateComponent
方法进行页面的更新。
vm._c
源码:vue-2.7.14\src\core\instance\render.ts
js
// src\core\instance\render.ts
// 这段代码在initRender中
// 用于编译后产生的render函数
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
1.3.3 createElement
createElement
源码如下:vue-2.7.14\src\core\vdom\create-element.ts
js
/**
* SIMPLE_NORMALIZE: 浅扁平
* ALWAYS_NORMALIZE:深扁平
*/
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
// wrapper function for providing a more flexible interface
// without getting yelled at by flow
export function createElement(
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any, // 使用哪种扁平化方式
alwaysNormalize: boolean // 当render 函数是手写的时候为true
): VNode | Array<VNode> {
// 实际上是平判断 data 是否存在,如果不存在参数向前进一个
if (isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
_createElement
方法是创建Vnode的核心:
- 规范
children
:children
按照normalizationType
类型进行扁平化处理,这个目的是规范children
,对于同一深度层次的元素,不管是单个元素还是钙元素在数组中,只要是属于同一深度层次,都扁平化一个一维数组中 - 生成
Vnode
:如果是普通标签,直接new Vnode()
。Vue组件则需要通过createComponent
生成Vnode
_createElement
源码如下:vue-2.7.14\src\core\vdom\create-element.ts
js
/**
* 生成虚拟DOM
* @param context: vm
* @param tag: 标签(div、p、ul)
* @param data: data标签上的属性
* @param children: 子节点
* @param normalizationType
* @returns Vnode: 虚拟dom
*/
export function _createElement(
context: Component,
tag?: string | Component | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
// Vnode data 不能为响应式对象
// 有 __ob__ 代表这个对象为响应式对象
if (isDef(data) && isDef((data as any).__ob__)) {
__DEV__ &&
warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(
data
)}\n` + 'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
// <Component :is=""/>
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (__DEV__ && isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
// support single function children as default scoped slot
if (isArray(children) && isFunction(children[0])) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
// 根据 normalizationType 对应不同扁平化处理方式
if (normalizationType === ALWAYS_NORMALIZE) {
// 深层扁平化
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
// 浅层扁平化
children = simpleNormalizeChildren(children)
}
let vnode, ns
// 标签为字符串类型
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
if (
__DEV__ &&
isDef(data) &&
isDef(data.nativeOn) &&
data.tag !== 'component'
) {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
// 创建普通标签的Vnode
vnode = new VNode(
config.parsePlatformTagName(tag),
data,
children,
undefined,
undefined,
context
)
} else if (
(!data || !data.pre) &&
isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
) {
// component
// 为组件,调用 createComponent 创建 Vnode
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
// 直接创建 Vnode
vnode = new VNode(tag, data, children, undefined, undefined, context)
}
} else {
// direct component options / constructor
// tag为组件构造函数或组件选项,创建Vue组件
vnode = createComponent(tag as any, data, context, children)
}
if (isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
1.3.4 创建Vue组件 - createComponent
createComponent
作用是返回 Vue 组件的虚拟DOM。同时在这过程中,会构造子类构造函数(这里会调用 _init
方法完成组件初始化)、安装组件钩子函数
所谓的组件化就是把页面拆分成多个组件,组件吧、内部包含自己的 HTML、CSS、JavaScript,这样可以拼成一个模块,并且组件可以复用、拼接,等同于积木一样,一个大的页面可以由很多小的组件拼接而成,接下来我们就用一个例子来看 vue 的组件内部是如何工作的
js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
const app = new Vue({
render: (h) => h(App),
}).$mount("#app");
console.log(app);
这段代码是通过 render
函数去渲染的,render
函数调用 createElement
,createElement
根据 tag
的不同调用不同的方法生成 Vnode
在例子中,我们可以看到传入的是一个名为 App
的对象,所以继续执行 createComponent
。
createComponent
在这里主要做了三件事:
- 把传入的组件对象构造成 vue 的子类
- 安装组件钩子函数
- 实例化 Vnode 并返回
createComponent
源码如下:vue-2.7.14\src\core\vdom\create-component.ts
js
export function createComponent(
Ctor: typeof Component | Function | ComponentOptions | void,
data: VNodeData | undefined,
context: Component,
children?: Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
// 根实例
// 在 initGlobalAPI 中定义的 vue.options._base = Vue
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
// 1.
// 通过 Vue 的 extend 方法,生成子类构造函数,使得子类也有 Vue 根实例的一些方法
// 其实就是构造Vue的子类
// src/core/global-api/extend.js
Ctor = baseCtor.extend(Ctor as typeof Component)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (__DEV__) {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
// 异步组件
let asyncFactory
// @ts-expect-error
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
// 解析构造函数选项,如果全局混合后应用
// 创建组件构造函数
resolveConstructorOptions(Ctor as typeof Component)
// transform component v-model data into props & events
if (isDef(data.model)) {
// @ts-expect-error
transformModel(Ctor.options, data)
}
// extract props
// @ts-expect-error
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
// @ts-expect-error
// 函数式组件
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(
Ctor as typeof Component,
propsData,
data,
context,
children
)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
// @ts-expect-error
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
// 2. 安装组件钩子函数
installComponentHooks(data)
// return a placeholder vnode
// @ts-expect-error
// 3.实例化 Vnode 并返回。需要注意组件Vnode没有children,这点在之后的patch在分析
const name = getComponentName(Ctor.options) || tag
const vnode = new VNode(
// @ts-expect-error
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data,
undefined,
undefined,
undefined,
context,
// @ts-expect-error
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
1.4 生成真实DOM - vm._update(vnode)
_update
源码:vue-2.7.14\src\core\instance\lifecycle.ts
js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
// 存储在前面的el
const prevEl = vm.$el
// 存储在前面的Vnode
const prevVnode = vm._vnode
// 存储活动的实例
const restoreActiveInstance = 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 {
// updates 更新
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// 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
let wrapper: Component | undefined = vm
while (
wrapper &&
wrapper.$vnode &&
wrapper.$parent &&
wrapper.$vnode === wrapper.$parent._vnode
) {
wrapper.$parent.$el = wrapper.$el
wrapper = wrapper.$parent
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
二、总结
综上所述,new Vue()
的整体过程大致可以划分为四部分,分别是初始化、挂载、生成虚拟节点,生成真实节点,最后调用 mounted
钩子
- 初始化 :合并配置项,初始化生命周期、事件、
data
、computed
、watch
等等,完成了beforeCreate
到created
的整个过程 - 挂载 :如果
options.render
不存在,将template
编译成render
函数。接下来就是建立更新机制,创建watcher
,通过watcher
中的get
方法,调用组件更新updateComponent
方法 - 生成虚拟DOM :调用更新函数,会调用
vm._render
方法,这个方法会调用vm.$options.render
方法用于生成虚拟DOM - 生成真实DOM :通过
vm._render
方法得到虚拟DOM后,会作为vm._update()
方法的参数去生成Vnode
相应的真实DOM