详解 Vue 生命周期实现
栏目: JavaScript · 发布时间: 5年前
内容简介:在我们平时使用各种框架的时候,都避免不了使用到一种特性,就是我们最主要讲的是今天,我就来分析一下,
在我们平时使用各种框架的时候,都避免不了使用到一种特性,就是 生命周期 钩子,这些钩子,可以给我们提供很多便利,让我们在数据更新的每一个阶段,都可以捕捉到它的变化。
我们最主要讲的是 vue
的生命周期,先来一份大纲:
- beforeCreate(初始化界面前)
- created(初始化界面后)
- beforeMount(渲染dom前)
- mounted(渲染dom后)
- beforeUpdate(更新数据前)
- updated(更新数据后)
- beforeDestroy(卸载组件前)
- destroyed(卸载组件后)
今天,我就来分析一下, vue
在调用到每一个生命周期前,到底都在做了什么?
正文
来看看官方的生命周期流程图:
这张图其实已经大概的告诉了我们,每个阶段做了什么,但是我觉得还有必要详细的去分析一下,这样在未来如果我们要实现类似于 vue
这种框架的时候,可以知道在什么时间,应该去做什么,怎么去实现。
beforeCreate(初始化界面前)
function initInternalComponent (vm, options) { var opts = vm.$options = Object.create(vm.constructor.options); // doing this because it's faster than dynamic enumeration. var parentVnode = options._parentVnode; opts.parent = options.parent; opts._parentVnode = parentVnode; opts._parentElm = options._parentElm; opts._refElm = options._refElm; var vnodeComponentOptions = parentVnode.componentOptions; opts.propsData = vnodeComponentOptions.propsData; opts._parentListeners = vnodeComponentOptions.listeners; opts._renderChildren = vnodeComponentOptions.children; opts._componentTag = vnodeComponentOptions.tag; if (options.render) { opts.render = options.render; opts.staticRenderFns = options.staticRenderFns; } } function resolveConstructorOptions (Ctor) { var options = Ctor.options; if (Ctor.super) { var superOptions = resolveConstructorOptions(Ctor.super); var cachedSuperOptions = Ctor.superOptions; if (superOptions !== cachedSuperOptions) { // super 选项已更改,需要解决新选项。 Ctor.superOptions = superOptions; // 检查是否有任何后期修改/附加选项 var modifiedOptions = resolveModifiedOptions(Ctor); // 更新基本扩展选项 if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions); } options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions); if (options.name) { options.components[options.name] = Ctor; } } } return options } if (options && options._isComponent) { initInternalComponent(vm, options); } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); } if (process.env.NODE_ENV !== 'production') { initProxy(vm); } else { vm._renderProxy = vm; } vm._self = vm; initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); 复制代码
在一开始,先做了一个属性的合并处理,如果 options
存在并且 _isComponent
为 true
,那么就调用 initInternalComponent
方法,这个方法最主要是优化内部组件实例化,因为动态选项合并非常缓慢,并且没有内部组件选项需要特殊处理;
如果不满足上述条件,就调用 mergeOptions
方法去做属性合并,最后的返回值赋值给 $options
, mergeOptions
的实现原理,在 Vue 源码解析(实例化前) - 初始化全局API(一) 这里做过详细的讲解,有不了解的朋友,可以跳转这里去看;
做一个渲染拦截,这里的拦截,最主要是为了在调用 render
方法的时候,通过 vm.$createElement
方法进行 dom
的创建;
function initLifecycle (vm) { var options = vm.$options; // 找到第一个非抽象父级 var parent = options.parent; if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent; } parent.$children.push(vm); } vm.$parent = parent; vm.$root = parent ? parent.$root : vm; vm.$children = []; vm.$refs = {}; vm._watcher = null; vm._inactive = null; vm._directInactive = false; vm._isMounted = false; vm._isDestroyed = false; vm._isBeingDestroyed = false; } 复制代码
初始化了一些参数;
function initEvents (vm) { vm._events = Object.create(null); vm._hasHookEvent = false; // init父级附加事件 var listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); } } function updateComponentListeners ( vm, listeners, oldListeners ) { target = vm; updateListeners(listeners, oldListeners || {}, add, remove$1, vm); target = undefined; } 复制代码
初始化事件,如果 _parentListeners
存在的话,更新组件的事件监听;
function initRender (vm) { vm._vnode = null; // 子树的根 vm._staticTrees = null; // v-once缓存的树 var options = vm.$options; var parentVnode = vm.$vnode = options._parentVnode; // 父树中的占位符节点 var renderContext = parentVnode && parentVnode.context; vm.$slots = resolveSlots(options._renderChildren, renderContext); vm.$scopedSlots = emptyObject; // 将createElement fn绑定到此实例,以便我们在其中获得适当的渲染上下文。 vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }; // 规范化始终应用于公共版本,在用户编写的渲染函数中使用。 vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); }; // 暴露了$ attrs和$ listeners以便更容易创建HOC。 // 他们需要被动反应,以便使用它们的HOC始终更新 var parentData = parentVnode && parentVnode.data; /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () { !isUpdatingChildComponent && warn("$attrs is readonly.", vm); }, true); defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () { !isUpdatingChildComponent && warn("$listeners is readonly.", vm); }, true); } else { defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true); defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true); } } 复制代码
初始化渲染, defineReactive
的使用和作用,在 Vue 源码解析(实例化前) - 响应式数据的实现原理 这里有讲解,大家想了解可以看一下;
到了这里执行完毕后,就调用到了 beforeCreate
方法。
created(初始化界面后)
initInjections(vm); // 在数据/道具之前解决注入 initState(vm); initProvide(vm); // 解决后提供的数据/道具 callHook(vm, 'created'); 复制代码
function resolveInject (inject, vm) { if (inject) { // 因为流量不足以弄清楚缓存 var result = Object.create(null); var keys = hasSymbol ? Reflect.ownKeys(inject).filter(function (key) { return Object.getOwnPropertyDescriptor(inject, key).enumerable }) : Object.keys(inject); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var provideKey = inject[key].from; var source = vm; while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { result[key] = source._provided[provideKey]; break } source = source.$parent; } if (!source) { if ('default' in inject[key]) { var provideDefault = inject[key].default; result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault; } else if (process.env.NODE_ENV !== 'production') { warn(("Injection \"" + key + "\" not found"), vm); } } } return result } } var shouldObserve = true; function toggleObserving (value) { shouldObserve = value; } function initInjections (vm) { var result = resolveInject(vm.$options.inject, vm); if (result) { toggleObserving(false); Object.keys(result).forEach(function (key) { if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], function () { warn( "Avoid mutating an injected value directly since the changes will be " + "overwritten whenever the provided component re-renders. " + "injection being mutated: \"" + key + "\"", vm ); }); } else { defineReactive(vm, key, result[key]); } }); toggleObserving(true); } } 复制代码
在这里,其实最主要就是用来做不需要响应式的数据,官方文档:provide / inject ;
function initState (vm) { vm._watchers = []; var 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); } } 复制代码
在处理完 inject
后,紧接着就做了 props
、 methods
、 data
、 computed
和 watch
的初始化处理;
function initProvide (vm) { var provide = vm.$options.provide; if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide; } } 复制代码
Provide
和 Inject
作用其实是一样的,只是处理的方式不一样,具体区别请看官方文档:provide / inject ;
到这里执行完毕后,就要走到 created
钩子了。
beforeMount(渲染dom前)
if (vm.$options.el) { vm.$mount(vm.$options.el); } 复制代码
在渲染 dom
,先检查了是否存在渲染位置,如果不存在的话,也就不会注册了;
Vue.prototype.$mount = function ( el, hydrating ) { el = el && inBrowser ? query(el) : undefined; return mountComponent(this, el, hydrating) }; function mountComponent ( vm, el, hydrating ) { vm.$el = el; if (!vm.$options.render) { vm.$options.render = createEmptyVNode; } callHook(vm, 'beforeMount'); } 复制代码
在 beforeMount
这里,基本没做什么事情,只是做了一个 render
方法如果存在就绑定一下 createEmptyVNode
函数;
绑定完毕后,就执行了 beforeMount
钩子;
mounted(渲染dom后)
var updateComponent; if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = function () { var name = vm._name; var id = vm._uid; var startTag = "vue-perf-start:" + id; var endTag = "vue-perf-end:" + id; mark(startTag); var 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 = function () { vm._update(vm._render(), hydrating); }; } // 我们在观察者的构造函数中将其设置为vm._watcher,因为观察者的初始补丁可能会调用$ forceUpdate(例如,在子组件的挂载挂钩内),这依赖于已定义的vm._watcher new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */); hydrating = false; // 手动挂载的实例,在自己挂载的调用挂载在其插入的挂钩中为渲染创建的子组件调用 if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, 'mounted'); } 复制代码
在 new Watcher
的时候,调用了 _render
方法,实现了 dom
的渲染,具体 _render
都做了什么,点击查看 vue 源码解析(实例化前) - 初始化全局 API(最终章) ;
在执行完实例化 Watcher
以后,如果 $node
不存在,就说明是初始化渲染,执行 mounted
钩子;
beforeUpdate(更新数据前)
Vue.prototype._update = function (vnode, hydrating) { var vm = this; if (vm._isMounted) { callHook(vm, 'beforeUpdate'); } }; 复制代码
如果当前的 vue
实例的 _isMounted
为 true
的话,直接调用 beforeUpdate
钩子;
_isMounted 在 mounted
钩子执行前就已经设置为 true 了。
执行 beforeUpdate
钩子;
updated(更新数据后)
function callUpdatedHooks (queue) { var i = queue.length; while (i--) { var watcher = queue[i]; var vm = watcher.vm; if (vm._watcher === watcher && vm._isMounted) { callHook(vm, 'updated'); } } } 复制代码
因为有多个组件的时候,会有很多个 watcher
,在这里,就是检查当前的得 watcher
是哪个,是当前的话,就直接执行当前 updated
钩子。
beforeDestroy(卸载组件前)
Vue.prototype.$destroy = function () { var vm = this; if (vm._isBeingDestroyed) { return } callHook(vm, 'beforeDestroy'); }; 复制代码
在卸载前,检查是否已经被卸载,如果已经被卸载,就直接 return
出去;
执行 beforeDestroy
钩子;
destroyed(卸载组件后)
vm._isBeingDestroyed = true; // 从父级那里删除自己 var parent = vm.$parent; if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm); } // 拆解观察者 if (vm._watcher) { vm._watcher.teardown(); } var i = vm._watchers.length; while (i--) { vm._watchers[i].teardown(); } // 从冻结对象的数据中删除引用可能没有观察者。 if (vm._data.__ob__) { vm._data.__ob__.vmCount--; } // 准备执行最后一个钩子 vm._isDestroyed = true; // 在当前渲染的树上调用destroyed hook vm.__patch__(vm._vnode, null); callHook(vm, 'destroyed'); 复制代码
其实这里就是把所有有关自己痕迹的地方,都给删除掉;
执行 destroyed
钩子。
总结
到这里,其实每一个生命周期的钩子做了什么,我们已经了解的差不多了,那这样大量的代码看起来可能不是很方便,所以我们做一个总结的 list
:
-
beforeCreate
:初始化了部分参数,如果有相同的参数,做了参数合并,执行beforeCreate
; -
created
:初始化了Inject
、Provide
、props
、methods
、data
、computed
和watch
,执行created
; -
beforeMount
:检查是否存在el
属性,存在的话进行渲染dom
操作,执行beforeMount
; -
mounted
:实例化Watcher
,渲染dom
,执行mounted
; -
beforeUpdate
:在渲染dom
后,执行了mounted
钩子后,在数据更新的时候,执行beforeUpdate
; -
updated
:检查当前的watcher
列表中,是否存在当前要更新数据的watcher
,如果存在就执行updated
; -
beforeDestroy
:检查是否已经被卸载,如果已经被卸载,就直接return
出去,否则执行beforeDestroy
; -
destroyed
:把所有有关自己痕迹的地方,都给删除掉;
结束语
Vue
生命周期实现,就先讲到这里了,里面有些地方,细节讲的不是很多,因为这个文章和之前的源码解析方向和目的不一样,源码讲解的目的是为了让大家一步一步的去了解,都写了什么,而这篇文章的目的是为了让大家了解到每个生命周期的阶段,都做了什么。
如果大家有觉得有问题的地方,或者写的不好的地方,还请直接下方评论指出,谢谢了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
你必须知道的213个C语言问题
范立锋、李世欣 / 人民邮电出版社 / 2010-6 / 45.00元
《你必须知道的213个C语言问题》精选了213个在C语言程序设计中经常遇到的问题,目的是帮助读者解决在C语言学习和开发中遇到的实际困难,提高读者学习和开发的效率。这些问题涵盖了C语言与软件开发、C语言基础、编译预处理、字符串、函数、键盘操作、文件、目录和磁盘、数组、指针和结构、DOS服务和BIOS服务、日期和时间、重定向I/O和进程命令、C语言开发常见错误及程序调试等内容,均是作者经过充分的调研,......一起来看看 《你必须知道的213个C语言问题》 这本书的介绍吧!