美文网首页Vue.js前端全栈开发前端Vue专辑
让你们更清楚的了解Vue 生命周期实现

让你们更清楚的了解Vue 生命周期实现

作者: a333661d6d6e | 来源:发表于2019-04-29 22:08 被阅读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;
          }//在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
        }
      }
      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');
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    在一开始,先做了一个属性的合并处理,如果 options 存在并且 _isComponenttrue ,那么就调用 initInternalComponent 方法,这个方法最主要是优化内部组件实例化,因为动态选项合并非常缓慢,并且没有内部组件选项需要特殊处理;

    如果不满足上述条件,就调用 mergeOptions 方法去做属性合并,最后的返回值赋值给 $options

    做一个渲染拦截,这里的拦截,最主要是为了在调用 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;
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
      vm.$children = [];
      vm.$refs = {};
    
      vm._watcher = null;
      vm._inactive = null;
      vm._directInactive = false;
      vm._isMounted = false;
      vm._isDestroyed = false;
      vm._isBeingDestroyed = false;
    }
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    初始化了一些参数;

    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;
    }
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    初始化事件,如果 _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);
      }
    }
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    初始化渲染,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);
      }
    }
    

    在这里,其实最主要就是用来做不需要响应式的数据,官方文档: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 后,紧接着就做了 propsmethodsdatacomputedwatch 的初始化处理;

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

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

    到这里执行完毕后,就要走到 created 钩子了。

    beforeMount(渲染dom前)

    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    在渲染 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');
      }
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    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');
        }
    
    };
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    如果当前的 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');
        }
      }
    }
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    因为有多个组件的时候,会有很多个 watcher ,在这里,就是检查当前的得 watcher 是哪个,是当前的话,就直接执行当前 updated 钩子。

    beforeDestroy(卸载组件前)

    Vue.prototype.$destroy = function () {
        var vm = this;
        if (vm._isBeingDestroyed) {
          return
        }
        callHook(vm, 'beforeDestroy');
    };
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

    在卸载前,检查是否已经被卸载,如果已经被卸载,就直接 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');
    //在此我向大家推荐一个前端全栈开发交流圈:582735936  突破技术瓶颈,提升思维能力
    

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

    执行 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 :把所有有关自己痕迹的地方,都给删除掉;

    结语

    感谢您的观看,如有不足之处,欢迎批评指正。
    获取资料👈👈👈
    本次给大家推荐一个免费的学习群,里面概括移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。
    对web开发技术感兴趣的同学,欢迎加入Q群:👉👉👉582735936 👈👈👈,不管你是小白还是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时每天更新视频资料。
    最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。

    相关文章

      网友评论

        本文标题:让你们更清楚的了解Vue 生命周期实现

        本文链接:https://www.haomeiwen.com/subject/ikfunqtx.html