一。系统性的学习VUE各个生命周期做了生命
注意:不涉及 keep-alive 的场景
初始化: new Vue
从 **new Vue(options)**开始作为入口,Vue构造函数:
function Vue(options){
this._init(options)
}
进入了 _init 函数之后,初始化一些属性,开始进入第一个生命周期
callHook(vm,'beforeCreate')
beforeCreate被调用完成, 之后=>
1.初始化 inject
2.初始化 state
3.初始化 props
4.初始化 methods
5.初始化 data
6.初始化 computed
7.初始化 initWatch
8.初始化 provide
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 && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
function initData (vm: Component) {
let data = vm.$options.data
// 这里判断data是不是一个function
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
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
)
}
所以在data 中可以使用 props 上的值,反过来则不行。
然后进入 created 阶段
callHook(vm,'created')
created被调用完成
调用 **
m
o
u
n
t
∗
∗
方
法
挂
载
组
件
到
d
o
m
上
V
u
e
.
p
r
o
t
o
t
y
p
e
.
mount** 方法挂载组件到 dom 上 Vue.prototype.
mount∗∗方法挂载组件到dom上Vue.prototype.mount函数中,说明的几个点:
Vue不能挂载到body或html这样的根节点上,一般都用div嵌套包括起来,会被覆盖,Vue2.0版本中,所有的vue组件渲染最终都需要rendr方法,不论写的是el或者template属性,最终都会转换陈render方法,即"在线编译的过程"
如果使用了 runtime-with-compile版本,
// 原型上添加$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
// 若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
// resolve template/el and convert to render function
// 如果是已经render()的话,不必再compile()
if (!options.render) {
let template = options.template
if (template) {
.....
}
}
// 如果是template模板,需要进行compile解析
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
}
// 最后会创建DOM元素,在这里内容进行覆盖,这也是为什么外层一般要有一个父级div包裹它,而不是写在body或html上,实际上template会走一个compileToFunctions的过程
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
会把传入的 template 或者 html 文本 通过编译生成render 函数。
1.编译 template,生成ast抽象语法树
2.优化这个 ast,标记静态节点。(渲染过程中不会变的那些节点,优化性能)。
3.根据ast,生成 render 函数。
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
render 函数后, 开始进行渲染的步骤开始进行
beforMount被调用成功
把 渲染组件的函数 定义好
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
vm._render 其实就是调用我们上一步拿到的render函数生成的vnode,而vm._unpdata方法则会对这个vnode进行 patch操作 把vnode通过createElment 函数创建新节点并且渲染到 dom节点中
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
响应式原理的一个核心类 Watcher 负责执行这个函数,为什么要它来代理执行呢?因为我们需要在这段过程中去 观察 这个函数读取了哪些响应式数据,将来这些响应式数据更新的时候,我们需要重新执行 updateComponent 函数。
如果是更新后调用 updateComponent 函数的话,updateComponent 内部的 patch 就不再是初始化时候的创建节点,而是对新旧 vnode 进行 diff,最小化的更新到 dom节点 上去。
before 属性上定义了 beforeUpdata 函数,也就说在Watcher别响应式属性触发之后,从新渲染试图之前,会调用beforeUpdata 生命周期
注意,在 render 的过程中,如果遇到了 子组件,则会调用 createComponent 函数。
createComponent 函数内部,会做这样的一件事情:
Ctor = baseCtor.extend(Ctor)
在普通的场景下,其实这就是 Vue.extend 生成的构造函数,这个函数可以理解为继承自 Vue 函数。 (Vnode也有自己的生命周期)
子组件会有自己的init周期:
// 创建子组件
const child = createComponentInstanceForVnode(vnode)
// 挂载到 dom 上
child.$mount(vnode.elm)
而 createComponentInstanceForVnode 内部又做了什么事呢?它会去调用 子组件 的构造函数。
new vnode.componentOptions.Ctor(options)
//构造函数的内部是这样的
const Sub = function VueComponent (options) {
this._init(options)
}
这个 _init 其实就是我们文章开头的那个函数,也就是说,如果遇到 子组件,那么就会优先开始子组件的构建过程,也就是说,从 beforeCreated 重新开始。这是一个递归的构建过程。
也就是说,如果我们有 父 -> 子 -> 孙 这三个组件,那么它们的初始化生命周期顺序是这样的:
父 beforeCreate
父 create
父 beforeMount
子 beforeCreate
子 create
子 beforeMount
孙 beforeCreate
孙 create
孙 beforeMount
孙 mounted
子 mounted
父 mounted
然后,mounted 生命周期被触发。
mounted被调用完成
到此为止,组件的挂载就完成了,初始化的生命周期结束。