春节继续写博客~加油!
此次来学习一下Vue的生命周期,看看生命周期是怎么回事。html
生命周期主要就是在源码某个时间点执行这个 callHook
方法来调用 vm.$options
的生命周期钩子方法(若是定义了生命周期钩子方法的话)。
咱们来看看 callHook 代码:前端
export function callHook (vm: Component, hook: string) { const handlers = vm.$options[hook] // 获取Vue选项中的生命周期钩子函数 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) } }
好比触发 mounted
钩子的方法:vue
callHook(vm, 'mounted')
先上一张图看下Vue的生命周期,咱们能够在相应的生命周期中定义一些事件。node
先看看这两个方法调用的时间。git
beforeCreate
在实例初始化以后,数据观测 (data observer) 和 event/watcher 事件配置以前被调用。
created
在实例建立完成后被当即调用。在这一步,实例已完成如下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
具体代码以下github
// src/core/instance/init.js Vue.prototype._init = function (options?: Object) { …… initLifecycle(vm) // 初始化生命周期 initEvents(vm) // 初始化事件 initRender(vm) // 初始化渲染 callHook(vm, 'beforeCreate') initInjections(vm) // 初始化Inject initState(vm) // 初始化数据 initProvide(vm) // 初始化Provide callHook(vm, 'created') …… if (vm.$options.el) { vm.$mount(vm.$options.el) // 若是有el属性,将内容挂载到el中去。 } }
beforeMount
在挂载开始以前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。
mounted
el 被新建立的 vm.$el 替换,并挂载到实例上去以后调用该钩子。若是 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
贴出代码逻辑web
// 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 } callHook(vm, 'beforeMount') let updateComponent 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 }
那么这个 mountComponent
在哪里用了呢?就是在Vue的 $mount 方法中使用。数组
// src/platforms/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) }
最后会在Vue初始化的时候,判断是否有 el,若是有则执行 $mount 方法。服务器
// src/core/instance/init.js if (vm.$options.el) { vm.$mount(vm.$options.el) // 若是有el属性,将内容挂载到el中去。 }
至今生命周期逻辑应该是 beforeCreate - created - beforeMount -mounted前端工程师
beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁以前。这里适合在更新以前访问现有的 DOM,好比手动移除已添加的事件监听器。
updated
因为数据更改致使的虚拟 DOM 从新渲染和打补丁,在这以后会调用该钩子。
找代码逻辑~ beforeUpdate 和 updated 在两个地方调用。
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this // 若是是已经挂载的,就触发beforeUpdate方法。 if (vm._isMounted) { callHook(vm, 'beforeUpdate') } …… // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
在执行 _update
方法的时候,若是 DOM 已经挂载了,则调用 beforeUpdate
方法。
在 _update 方法的最后做者也注视了调用 updated hook 的位置:updated
钩子由 scheduler
调用来确保子组件在一个父组件的 update
钩子中。
咱们找到 scheduler
,发现有个 callUpdateHooks
方法,该方法遍历了 watcher
数组。
// src/core/observer/scheduler.js 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') } } }
这个 callUpdatedHooks
在 flushSchedulerQueue
方法中调用。
/** * 刷新队列并运行watcher */ function flushSchedulerQueue () { flushing = true let watcher, id queue.sort((a, b) => a.id - b.id) for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null watcher.run() } const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // 调用组件的updated和activated生命周期 callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) }
继续找下去
export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true // 此参数用于判断watcher的ID是否存在 …… if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
最终在 watcher.js
找到 update
方法:
// src/core/observer/watcher.js update () { // lazy 懒加载 // sync 组件数据双向改变 if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) // 排队watcher } }
等因而队列执行完 Watcher 数组的 update
方法后调用了 updated
钩子函数。
beforeDestroy
实例销毁以前调用。在这一步,实例仍然彻底可用。该钩子在服务器端渲染期间不被调用。
destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的全部东西都会解绑定,全部的事件监听器会被移除,全部的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
看代码~
// src/core/instance/lifecycle.js // 销毁方法 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) // 触发 destroyed 钩子 callHook(vm, 'destroyed') // turn off all instance listeners. vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } }
这是一个销毁 Vue 实例的过程,将各类配置清空和移除。
activated
keep-alive 组件激活时调用。
deactivated
keep-alive 组件停用时调用。
找到实现代码的地方
// src/core/instance/lifecycle.js export function activateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = false if (isInInactiveTree(vm)) { return } } else if (vm._directInactive) { return } if (vm._inactive || vm._inactive === null) { vm._inactive = false for (let i = 0; i < vm.$children.length; i++) { activateChildComponent(vm.$children[i]) } callHook(vm, 'activated') } } export function deactivateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = true if (isInInactiveTree(vm)) { return } } if (!vm._inactive) { vm._inactive = true for (let i = 0; i < vm.$children.length; i++) { deactivateChildComponent(vm.$children[i]) } callHook(vm, 'deactivated') } }
以上两个方法关键就是修改了 vm._inactive
的值,而且乡下遍历子组件,最后触发钩子方法。
当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子能够返回 false 以阻止该错误继续向上传播。
这是 2.5 以上版本有的一个钩子,用于处理错误。
// src/core/util/error.js export function handleError (err: Error, vm: any, info: string) { if (vm) { let cur = vm // 向上冒泡遍历 while ((cur = cur.$parent)) { // 获取钩子函数 const hooks = cur.$options.errorCaptured if (hooks) { for (let i = 0; i < hooks.length; i++) { try { // 执行 errorCaptured 钩子函数 const capture = hooks[i].call(cur, err, vm, info) === false if (capture) return } catch (e) { globalHandleError(e, cur, 'errorCaptured hook') } } } } } globalHandleError(err, vm, info) }
代码很简单,看代码便可~
除了生命周期钩子外,vue还提供了生命周期方法来直接调用。
若是 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可使用 vm.$mount() 手动地挂载一个未挂载的实例。
若是没有提供 elementOrSelector 参数,模板将被渲染为文档以外的的元素,而且你必须使用原生 DOM API 把它插入文档中。
这个方法返回实例自身,于是能够链式调用其它实例方法。
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) if (el === document.body || el === document.documentElement) { return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { // 获取template let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) } } else if (template.nodeType) { template = template.innerHTML } else { return this } } else if (el) { template = getOuterHTML(el) } // 编译template if (template) { const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns } } // 执行 $mount 方法 return mount.call(this, el, hydrating) }
其实很简单,先获取html代码,而后执行 compileToFunctions
方法执行编译过程(具体编译过程在学习Render的时候再说)。
迫使 Vue 实例从新渲染。注意它仅仅影响实例自己和插入插槽内容的子组件,而不是全部子组件。
Vue.prototype.$forceUpdate = function () { var vm = this; if (vm._watcher) { vm._watcher.update(); } };
这是强制更新方法,执行了 vm._watcher.update()
方法。
将回调延迟到下次 DOM 更新循环以后执行。在修改数据以后当即使用它,而后等待 DOM 更新。它跟全局方法 Vue.nextTick 同样,不一样的是回调的 this 自动绑定到调用它的实例上。
找了找 vm.$nextTick
的代码
// src/core/instance/render.js Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) }
找到这个 nextTick
方法:
// src/core/util/next-tick.js export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
具体功能逻辑等学习完 render
再更新……
彻底销毁一个实例。清理它与其它实例的链接,解绑它的所有指令及事件监听器。
触发 beforeDestroy 和 destroyed 的钩子。
关于$destroy 咱们以前再说 destroyed 钩子的时候提到过了,这里就再也不赘述。
Vue.prototype.$destroy = function () { …… }
首先说下过年博客计划,过年学习Vue各个模块的源码,并发布相应博客。另外还会发布一些前端知识的整理,便于下个月找工做~
而后,小结下本身看源码的一些小技巧:
OK,今天就这么多~ 明天去学习下Vue的事件源码!加油!明天见!
鉴于前端知识碎片化严重,我但愿可以系统化的整理出一套关于Vue的学习系列博客。
本文源码已收入到GitHub中,以供参考,固然能留下一个star更好啦^-^。
https://github.com/violetjack/VueStudyDemos
VioletJack,高效学习前端工程师,喜欢研究提升效率的方法,也专一于Vue前端相关知识的学习、整理。
欢迎关注、点赞、评论留言~我将持续产出Vue相关优质内容。
新浪微博: http://weibo.com/u/2640909603
掘金:https://gold.xitu.io/user/571...
CSDN: http://blog.csdn.net/violetja...
简书: http://www.jianshu.com/users/...
Github: https://github.com/violetjack