初始化流程
new Vue
咱们在应用 Vue 的时候,首页就是先 new Vue(...)
;在上一章中通过剖析构建流程,咱们得出入口文件 src/platforms/web/entry-runtime-with-compiler.js
,通过入口文件,咱们一步一步找到 Vue 构造函数定义所在:
// src/platforms/web/entry-runtime-with-compiler.js
// ...
import Vue from './runtime/index'
// ...
// src/platforms/web/runtime/index.js
import Vue from 'core/index'
// ...
// src/core/index.js
import Vue from './instance/index'
import {initGlobalAPI} from './global-api/index'
import {isServerRendering} from 'core/util/env'
import {FunctionalRenderContext} from 'core/vdom/create-functional-component'
// 初始化全局 API
initGlobalAPI(Vue)
// ...
// src/core/instance/index.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'
// Vue 构造函数
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')
}
// 调用 Vue.prototype_init 办法,该办法是在 initMixin 中定义的
this._init(options)
}
// 定义 Vue.prototype_init 办法
initMixin(Vue)
/**
* 定义:* Vue.prototype.$data
* Vue.prototype.$props
* Vue.prototype.$set
* Vue.prototype.$delete
* Vue.prototype.$watch
*/
stateMixin(Vue)
/**
* 定义 事件相干的 办法:* Vue.prototype.$on
* Vue.prototype.$once
* Vue.prototype.$off
* Vue.prototype.$emit
*/
eventsMixin(Vue)
/**
* 定义:* Vue.prototype._update
* Vue.prototype.$forceUpdate
* Vue.prototype.$destroy
*/
lifecycleMixin(Vue)
/**
* 定义:* Vue.prototype.$nextTick
* Vue.prototype._render
*/
renderMixin(Vue)
export default Vue
_init
// src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 每个实例都保留一个 _uid
vm._uid = uid++
let startTag, 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 observed
vm._isVue = true
// 解决组件配置项
if (options && options._isComponent) {
// 每个子组件初始化时走这里,这里只做了一些性能优化
// 将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以进步代码的执行效率
initInternalComponent(vm, options)
} else {
// 合并选项,合并默认选项和自定义选项
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// 设置代理,将 vm 实例上的属性代理到 vm._renderProxy
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {initProxy(vm)
} else {vm._renderProxy = vm}
// expose real self
vm._self = vm
// 初始化实例关系属性,$parent、$children、$refs、$root 等
initLifecycle(vm)
// 初始化自定义事件,解决父组件传递的事件和回调
initEvents(vm)
// 解析组件的插槽信息,失去 vm.$slot, 解决渲染函数(_render), 失去 vm.$createElement 办法,即 h 函数
initRender(vm)
// 调用 beforeCreate 钩子函数
callHook(vm, 'beforeCreate')
// 初始化组件的 inject 配置项,失去 result[key] = val 模式的配置对象,而后对后果数据进行响应式解决,并代理每个 key 到 vm 实例
initInjections(vm)
// 数据响应式外围,解决 props、methods、data、computed、watch
initState(vm)
// 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
initProvide(vm) // resolve provide after data/props
// 调用 created 钩子函数
callHook(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)
}
}
}
下面代码很清晰的看出初始化都做了哪些事件,在初始化的最初,如果有 el
属性,则会主动调用 vm.$mount
进行挂载,否则咱们就须要手动调用 $mount
。接下里就进入了挂载阶段。
Vue 实例挂载
$mount
入口文件 src/platforms/web/entry-runtime-with-compiler.js
:
/* @flow */
import config from 'core/config'
import {warn, cached} from 'core/util/index'
import {mark, measure} from 'core/util/perf'
import Vue from './runtime/index'
import {query} from './util/index'
import {compileToFunctions} from './compiler/index'
import {shouldDecodeNewlines, shouldDecodeNewlinesForHref} from './util/compat'
const idToTemplate = cached(id => {const el = query(id)
return el && el.innerHTML
})
/**
* 编译器的入口
* 进行预编译,最终将模版编译成 render 函数
*/
// 缓存原型上的办法
const mount = Vue.prototype.$mount
// 从新定义该办法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {el = el && query(el)
// 不能挂载在 body、html 这样的根节点上
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.`)
return this
}
const options = this.$options
/**
* 若没有 render 办法,则解析 template 和 el,并转换为 render 函数
* 优先级:render > template > el
*/
if (!options.render) {
let template = options.template
// template
if (template) {if (typeof template === 'string') {if (template.charAt(0) === '#') {// { template: '#app'},以 id 为‘app’的节点,作为挂载节点
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 是一个失常的元素,获取其 innerHtml 作为模版
template = template.innerHTML
} else {if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// 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, {
// 在非生产环境下,编译时记录标签属性在模版字符串中开始和完结的地位索引
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
// 界定符,默认 {{}}
delimiters: options.delimiters,
// 是否保留正文
comments: options.comments
}, this)
// 将两个渲染函数放到 this.$options 上
options.render = render
options.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')
}
}
}
// 调用原型上办法挂载
return mount.call(this, el, hydrating)
}
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
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
}
}
Vue.compile = compileToFunctions
export default Vue
从下面代码能够看出,不论定义 render
办法还是 el
和 template
属性,最终的目标就是失去 render
渲染函数。而后保留在 options
上。
编译模板,失去 render
渲染函数,通过调用 compileToFunctions
办法,这个到编译器的时候再一块看。
最初调用原型上的 $mount
,定义在 src/platform/web/runtime/index.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
mountComponent
// src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (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')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && 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 {// 执行 vm._render() 函数,失去 虚构 DOM,并将 vnode 传递给 _update 办法,接下来就该到 patch 阶段了
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 defined
new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// vm.$vnode 示意 Vue 实例的父虚构 Node,所以它为 Null 则示意以后是根 Vue 的实例
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
mountComponent
内定义了 updateComponent
办法,而后实例化一个 Watcher
,同时将 updateComponent
作为参数传入,在 Watcher
的回调函数中被调用。Watcher
在这里次要是初始化和数据变动时,执行回调函数。
最初设置 vm._isMounted = true
, 示意实例已挂载。
updateComponent
的调用会执行 vm._update
和 vm._render
。vm._render
获取虚构 DOM,vm._update
更新视图。
下面代码呈现了三个生命周期钩子 beforeMount
、beforeUpdate
、mounted
;也就是说,在执行 vm._render()
之前,执行了 beforeMount
钩子函数;在执行完 vm._update()
把虚构 DOM 转换实在 DOM 后,执行 mounted
钩子函数;后续若数据变动时,通过 _isMounted
标记,示意已挂载则执行 beforeUpdate
钩子函数。
这里值得注意的是,在
mounted
钩子执行前有个判断,只有在父虚构 Node 为null
的时候执行。只有new Vue
才会走到这里,如果是组件的话,它的父虚构 Node 是存在的。组件的mounted
在别的中央。
相干链接
Vue(v2.6.14)源码解毒(预):手写一个简易版 Vue
Vue(v2.6.14)源码解毒(一):筹备工作
Vue(v2.6.14)源码解毒(二):初始化和挂载
[Vue(v2.6.14)源码解毒(三):响应式原理(待续)]()
[Vue(v2.6.14)源码解毒(四):更新策略(待续)]()
[Vue(v2.6.14)源码解毒(五):render 和 VNode(待续)]()
[Vue(v2.6.14)源码解毒(六):update 和 patch(待续)]()
[Vue(v2.6.14)源码解毒(七):模板编译(待续)]()
如果感觉还对付的话,给个赞吧!!!也能够来我的集体博客逛逛 https://www.mingme.net/