Vue源码分析拓展 - Vue生命周期钩子函数执行原理分析

文章详细阐述了Vue实例在创建过程中经历的生命周期钩子,如created、mounted等,并展示了如何自定义这些钩子的合并策略。同时,通过代码解析了Vue.js源码中的optionMergeStrategies,说明了Vue如何处理组件和实例的选项合并,以及如何确保数据响应性和组件的正确初始化。
摘要由CSDN通过智能技术生成

目录

实例生命周期钩子

 vue.2.5.1.生命周期钩子.js

demo.html

index.html

 献上一张AI生成图~


实例生命周期钩子

每个Vue实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到DOM并在数据变化时更新DOM等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

比如created钩子可用用来在一个实例被创建之后执行代码,也有一些其它的钩子,在实例生命周期的不同阶段被调用,如mounted、updated和destroyed。生命周期钩子的this上下文指向调用它的Vue实例。

 vue.2.5.1.生命周期钩子.js

// 整体结构是立即执行函数。
// 带两个参数,一个global用作全局变量存放,一个factory工厂函数制造一些初始化方法。
// typeof判断变量类型,以此校验执行环境
// 需要理解逻辑运算符之间的优先级,
// 其中用到了几个不理解的变量,属性和方法,分别是 module、module.exports、define、define.amd
(function (global, factory) {// global = window, factory
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global = global || self, global.Vue = factory());
  // 暴露出factory工厂函数
}(this, function () {
  // ASSET_TYPES 常量复用
  // Vue.options.components
  // Vue.component..   Vue.directive..
  var ASSET_TYPES = [
    'component',
    'directive',
    'filter'
  ];
  // 生命周期钩子常量组
  var LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated',
    'errorCaptured',
    'serverPrefetch'
  ];
  // Vue 全局配置对象
  // 最终config会暴露为Vue.config
  var config = {
    /**
     * 选项合并策略(用于core/util/options)
     */
    //$flow-disable-line 禁用行
    optionMergeStrategies: Object.create(null),
  }
  /**
     * 检查对象是否具有该属性
     */
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  function hasOwn(obj, key) {
    return hasOwnProperty.call(obj, key)
  }
  /** 自定义策略
   * 选项覆盖策略是处理
   * 如何合并父选项值和子选项
   * 将值转换为最终值。
   */
  // 自定义处理用在了很多地方,如el,props,watch....
  var strats = config.optionMergeStrategies;
  // 自定义策略处理
  strats.data = function (parentVal, childVal, vm) {
    // 组件的基本原理
    // 聚焦到vm,判别是根实例,还是组件
    if (!vm) { // 组件,不是根实例
      if (childVal && typeof childVal !== "function") {
        console.error("data选项应该为函数 返回组件中每个实例的值")
      }
    }
  }
  // 所有钩子函数的自定义策略   parentVal === undefined    childVal === function(){}
  function mergeHook(parentVal, childVal) {
    return childVal
      ? parentVal // 数组
        ? parentVal.concat(childVal) // 合并parentVal、childVal
        : Array.isArray(childVal) // 是不是数组
          ? childVal
          : [childVal] // [function(){}]
      : parentVal;
  }
  LIFECYCLE_HOOKS.forEach(hook => {
    strats[hook] = mergeHook;
  })
  /**
   * "所有"选项默认策略
   */
  var defaultStrat = function (parentVal, childVal) {
    return childVal === undefined
      ? parentVal
      : childVal
  };
  function mergeOptions(parent, child, vm) {
    /* 选项规范检测 Components Props Inject Directives */
    var options = {};
    var key;
    for (key in parent) {
      //parent-> 'components', 'directives', 'filters'
      mergeField(key);
    }
    for (key in child) {
      if (!hasOwn(parent, key)) { // 父组件没有这个key,才会扩展key
        mergeField(key);
      }
    }
    // 选项的策略处理;要考虑el,data,生命周期的钩子函数...
    // 自定义策略挂载在(strats对象)  默认策略
    function mergeField(key) { // 组件也会调用,组件包含vue的除根实例特有的el等外的实例
      console.log(key);
      // 属性值 || 默认策略
      var strat = strats[key] || defaultStrat;
      // 给options扩展key属性
      options[key] = strat(parent[key], child[key], vm, key);
    }
    return options;
  }
  // 执行钩子函数方法
  function callHook(vm, hook) {
    var handlers = vm.$options[hook];
    if (handlers) {
      for (var i = 0, j = handlers.length; i < j; i++) {
        handlers[i].call(vm)
      }
    }
  }
  function initMixin(Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
      // a uid
      // 选项合并,给$options插入方法,参数
      vm.$options = mergeOptions(
        // 返回vue选项的引用
        Vue.options, // Vue.options
        options || {}, // options
        vm // Vue
      );
      // 执行钩子函数beforeCreate
      callHook(vm, 'beforeCreate')
    };
  }
  // config ,Vue全局配置对象
  function initGlobalAPI(Vue) {
    var configDef = {};
    configDef.get = function () { return config; };
    {
      configDef.set = function (newVal) {
        console.error('不要尝试修改Vue.config的引用',
          'Do not replace the Vue.config object, set individual fields instead.'
        );
      };
    }
    // 让Vue可访问config
    // 不直接写Vue.config而是监听,就是防止Vue.config引用被篡改
    Object.defineProperty(Vue, 'config', configDef);// 监听你对Vue.config属性的访问,暴漏出自定义策略接口
  }
  function initExtend(Vue) {
    /**
      *每个实例构造函数(包括Vue)都有一个唯一的
      *cid。这使我们能够创建包装的“子对象”
      *“构造函数”用于原型继承并缓存它们。
      */
    //  用于原型继承   缓存构造函数
    Vue.cid = 0;
    var cid = 1;
    /**
     * 类继承
     */
    Vue.extend = function (extendOptions) {
      extendOptions = extendOptions || {};
      var Super = this; // Super === Vue
      var SuperId = Super.cid;
      // 缓存检测 cachedCtors
      var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
      // 缓存处理cachedCtors[0] = 子类的引用
      if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
      }
      var name = extendOptions.name || Super.options.name;
      if (name) {
        // validateComponentName(name); // 组件名称规范检查
      }
      // 子类 构造函数
      var Sub = function VueComponent(options) {
        this._init(options);
      };
      // 把Vue的原型引用赋给Sub
      // {}.__proto__ = Super.prototype = Vue.prototype
      Sub.prototype = Object.create(Super.prototype);
      Sub.prototype.constructor = Sub;
      Sub.cid = cid++;
      // 组件在初始化 mergeOptions 选项的合并 => 规范的检测 => 策略的处理
      Sub.options = mergeOptions(
        Super.options, // Vue.options
        extendOptions // 组件的选项对象
      );
      Sub['super'] = Super;
      //对于props和computed属性,我们在
      //在扩展原型上扩展时的Vue实例。这
      //避免为创建的每个实例调用Object.defineProperty。
      if (Sub.options.props) {
        initProps$1(Sub);
      }
      if (Sub.options.computed) {
        initComputed$1(Sub);
      }
      // 允许进一步扩展/混合/插件使用 extension/mixin/plugin
      // Sub.extend = Super.extend;
      // Sub.mixin = Super.mixin;
      // Sub.use = Super.use;
      //创建资产寄存器,以便扩展类
      //也可以拥有他们的私人资产。
      // Super == Vue Vue.component【注册全局组件的一种方法】 不等于  Vue.options.components【挂载内置抽象组件】
      ASSET_TYPES.forEach(function (type) {
        Sub[type] = Super[type];
      });
      //启用递归自查找
      // if (name) {
      //   Sub.options.components[name] = Sub;
      // }
      //在扩展时保留对超级选项的引用。
      //稍后在实例化时,我们可以检查Super的选项是否有
      //已更新。
      // Sub.superOptions = Super.options;
      // Sub.extendOptions = extendOptions;
      // Sub.sealedOptions = extend({}, Sub.options);
      // 缓存构造函数
      cachedCtors[SuperId] = Sub;
      return Sub
    };
  }
  // 拿到空对象的引用
  Vue.options = Object.create(null);
  // 添加内置组件'components',指令'directives',过滤器'filters',他们都加一个空对象的引用
  ASSET_TYPES.forEach(function (type) {
    // ASSET_TYPES 常量复用
    Vue.options[type + 's'] = Object.create(null);// Vue.options.components
  });
  function Vue(options) {
    if (!(this instanceof Vue)) { // 得new一个Vue,不然就报错
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    // 初始化
    this._init(options);
  }
  initMixin(Vue);
  initGlobalAPI(Vue);
  initExtend(Vue)
  return Vue;
}));

demo.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue.2.5.1.生命周期</title>
</head>
<body>
  <div id="app"></div>
  <script src="vue.2.5.1.生命周期.js"></script>
  <script>
    // // count 自定义策略处理   parentVal == Vue.options.count = undefined, childVal === 1 
    // Vue.config.optionMergeStrategies.count = function (parentVal, childVal, vm) {
    //   return childVal >= 10 ? childVal : 10
    // }
    // // Vue 构造函数
    // var vm = new Vue({
    //   el: "#app",
    //   data: {
    //     root: "max"
    //   },
    //   count: 1,
    //   beforeCreate: [
    //     function () { console.log("hello OMG") },
    //     function () { console.log("hello KX") },
    //   ]
    // })
    // // 组件的这些选项,组件时可复用的Vue的实例data,computed,watch,methods以及生命周期钩子等,仅有的例外是el这样根实例特有的选项
    // //
    // console.log(vm.$options)
    // 响应式系统的搭建
    Vue.extend({
      data: { // data的选项必须是函数  创建组件
      }
    })
  </script>
</body>
</html>

index.html


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue-v2.6.14</title>
</head>
<body>
  <div id="app">
    <componen-a></componen-a>
  </div>
  <script src="vue-v2.6.14.js"></script>
  <script>
    // 组件选项的对象——注册局部组件的方式
    var ComponentA = {
      name: "ComponentA",
      template: "<div>123</div>"
    }
    new Vue({
      name: 'App',
      el: "#app",
      components: {
        "componen-a": ComponentA
      }
    })
    // json 对象    组件选项对象  Vue.extend()  创建一个子类  实例化子类(createComponentInstanceForVnode) => 组件
    // createComponentInstanceForVnode 实例化组件
    // 组件系统
  </script>
</body>
</html>

 献上一张AI生成图~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值