博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
详解 Vue 生命周期实现
阅读量:6432 次
发布时间:2019-06-23

本文共 11271 字,大约阅读时间需要 37 分钟。

前言

在我们平时使用各种框架的时候,都避免不了使用到一种特性,就是 生命周期 钩子,这些钩子,可以给我们提供很多便利,让我们在数据更新的每一个阶段,都可以捕捉到它的变化。

我们最主要讲的是 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 存在并且 _isComponenttrue ,那么就调用 initInternalComponent 方法,这个方法最主要是优化内部组件实例化,因为动态选项合并非常缓慢,并且没有内部组件选项需要特殊处理;

如果不满足上述条件,就调用 mergeOptions 方法去做属性合并,最后的返回值赋值给 $optionsmergeOptions 的实现原理,在 这里做过详细的讲解,有不了解的朋友,可以跳转这里去看;

做一个渲染拦截,这里的拦截,最主要是为了在调用 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 的使用和作用,在 这里有讲解,大家想了解可以看一下;

到了这里执行完毕后,就调用到了 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);  }}复制代码

在这里,其实最主要就是用来做不需要响应式的数据,官方文档: ;

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 后,紧接着就做了 propsmethodsdatacomputedwatch 的初始化处理;

function initProvide (vm) {  var provide = vm.$options.provide;  if (provide) {    vm._provided = typeof provide === 'function'      ? provide.call(vm)      : provide;  }}复制代码

ProvideInject 作用其实是一样的,只是处理的方式不一样,具体区别请看官方文档: ;

到这里执行完毕后,就要走到 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 都做了什么,点击查看 ;

在执行完实例化 Watcher 以后,如果 $node 不存在,就说明是初始化渲染,执行 mounted 钩子;

beforeUpdate(更新数据前)

Vue.prototype._update = function (vnode, hydrating) {    var vm = this;    if (vm._isMounted) {      callHook(vm, 'beforeUpdate');    }};复制代码

如果当前的 vue 实例的 _isMountedtrue 的话,直接调用 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 hookvm.__patch__(vm._vnode, null);callHook(vm, 'destroyed');复制代码

其实这里就是把所有有关自己痕迹的地方,都给删除掉;

执行 destroyed 钩子。

总结

到这里,其实每一个生命周期的钩子做了什么,我们已经了解的差不多了,那这样大量的代码看起来可能不是很方便,所以我们做一个总结的 list

  • beforeCreate :初始化了部分参数,如果有相同的参数,做了参数合并,执行 beforeCreate
  • created :初始化了 InjectProvidepropsmethodsdatacomputedwatch,执行 created
  • beforeMount :检查是否存在 el 属性,存在的话进行渲染 dom 操作,执行 beforeMount
  • mounted :实例化 Watcher ,渲染 dom,执行 mounted
  • beforeUpdate :在渲染 dom 后,执行了 mounted 钩子后,在数据更新的时候,执行 beforeUpdate
  • updated :检查当前的 watcher 列表中,是否存在当前要更新数据的 watcher ,如果存在就执行 updated
  • beforeDestroy :检查是否已经被卸载,如果已经被卸载,就直接 return 出去,否则执行 beforeDestroy
  • destroyed :把所有有关自己痕迹的地方,都给删除掉;

结束语

Vue 生命周期实现,就先讲到这里了,里面有些地方,细节讲的不是很多,因为这个文章和之前的源码解析方向和目的不一样,源码讲解的目的是为了让大家一步一步的去了解,都写了什么,而这篇文章的目的是为了让大家了解到每个生命周期的阶段,都做了什么。

如果大家有觉得有问题的地方,或者写的不好的地方,还请直接下方评论指出,谢谢了。

转载地址:http://emxga.baihongyu.com/

你可能感兴趣的文章
sdl2.0示例
查看>>
数学 --- 高斯消元 POJ 1830
查看>>
Ejabberd源码解析前奏--集群
查看>>
[ZHUAN]Flask学习记录之Flask-SQLAlchemy
查看>>
【转】Install SmartGit via PPA in Ubuntu 13.10/13.04/12.04/Linux Mint
查看>>
PNG怎么转换成32位的BMP保持透明
查看>>
经验分享:CSS浮动(float,clear)通俗讲解
查看>>
WTL中最简单的实现窗口拖动的方法(转)
查看>>
数据结构—队列
查看>>
BZOJ4241 : 历史研究
查看>>
(LeetCode)两个队列来实现一个栈
查看>>
jquery封装常用方法
查看>>
什么是ICE (Internet Communications Engine)
查看>>
移动web开发之屏幕三要素
查看>>
求按小时统计的语句,该怎么处理
查看>>
TRUNCATE,DORP,DELETE
查看>>
Chrome的开发必备小技巧
查看>>
can-i-win(好)
查看>>
Centos6.5下安装protobuf及简单使用
查看>>
[SharePoint] SharePoint 错误集 3
查看>>