Vue官网中的约束源码解释 -- 生命周期
muwoo opened this issue · 0 comments
关于生命周期的源码执行
首先我们先来看一张官网的图:
然后我们来看一下源码里什么时候开始执行各个生命周期的:
1. beforeCreate、created
beforeCreate
和created
钩子在core/instance/init.js
的_init
方法中执行
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
nitProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
这里主要是初始化一些vm的属性,initState
主要为定义的data
属性进行obsever
以及处理一些props
、watch
和computed
:
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)
}
}
2. beforMounted
在执行beforMounted
的钩子的时候,会进行几部判断:
1. 判断存不存在$el
属性
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
2. 判断存不存在template
属性:
let template = options.template
let template = options.template
if (template) {
// string
if (typeof template === 'string') {
// 如果第一个字符是#,则 template = query(id).innerHTML
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
// dom 节点
} else if (template.nodeType) {
template = template.innerHTML
} else {
return this
}
} else if (el) {
template = getOuterHTML(el)
}
3. mounted
这一步主要是经过了 render --> VNode --> path
步骤后生成了一个真实的dom
节点,并挂载到el
上:
return function patch () {
...
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
function invokeInsertHook (vnode, queue, initial) {
...
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
// 这里执行 mounted
callHook(componentInstance, 'mounted')
}
}
4. beforeUpdate
当我们执行dom更新之前,且已经经过mounted
。会触发的钩子:
vm._update(vm._render(), hydrating)
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
...
}
5. updated
这个钩子函数主要是在异步更新队列中执行,也就是nextTick
更新dom后会执行的钩子
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted) {
callHook(vm, 'updated')
}
}
}
function flushSchedulerQueue () {
...
watcher.run()
...
callUpdatedHooks(updatedQueue)
}
关于什么是nextTick
?以及Event loop
相关知识,有兴趣可以参考我的这两篇文章:
6. beforeDestroy destroyed
当$destroy
函数被调用时,会首先触发beforeDestroy
钩子:
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
可以看到,destroy
步骤如下:
remove(parent.$children, vm)
从父节点中先移除自己vm._watcher.teardown()
销毁watchersvm._data.__ob__.vmCount--
从数据ob中删除引用vm.__patch__(vm._vnode, null)
调用当前渲染树上的销毁钩子callHook(vm, 'destroyed')
调用destroyed
钩子vm.$off()
销毁事件监听 ...
到这里差不多就执行完了销毁任务,从而触发了destroyed
钩子
一些警告
不要在选项属性或回调上使用箭头函数,比如 created: () => console.log(this.a) 或 vm.$watch('a', newValue => this.myMethod())。因为箭头函数是和父级上下文绑定在一起的,this 不会是如你所预期的 Vue 实例,经常导致
Uncaught TypeError: Cannot read property of undefined
或Uncaught TypeError: this.myMethod is not a function
之类的错误。
我们可以看一下Vue是如何执行生命周期函数的:
export function callHook (vm: Component, hook: string) {
const handlers = vm.$options[hook]
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm)
} catch (e) {
handleError(e, vm, `${hook} hook`)
}
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
}
比如我们执行beforeCreate
钩子:callHook(vm, 'beforeCreate')
。因为是箭头函数,所以可以先了解箭头函数的几个特性:
- 箭头函数作为函数的一种形式,对于this的处理和普通函数有所区别,其没有自己的this上下文,也就是说通过bind/call/apply函数方法设置this值时无效的,会被忽略
- 因为箭头函数没有自己的this上下文,那么当它作为对象的方法函数运行时,this并不指向这个对象
- 箭头函数的的函数体中出现的this在运行时绑定到最近的作用域上下文对象
- 你可以认为箭头函数的this和调用者无关,只和其定义时所在的上下文相关
说到这里,应该明白了为什么不要在选项属性或回调上使用箭头函数了吧...