Skip to content

选项合并策略

Vue添加全局方法initGlobalApiMixin

js
import { initMixin } from "./init";
import { lifecyleMixin } from "./lifecycle";
import { renderMixin } from "./render";
import {initGlobalApiMixin} from './globalApiMixin/index'

function Vue(options) {
  this._init(options);
}

initMixin(Vue); // 添加初始化原型方法
lifecyleMixin(Vue); // 添加vm._update方法 -> 将虚拟节点转为展示DOM
renderMixin(Vue); // 添加vm._render方法 -> 将render函数转为虚拟DOM


// 初始化全局方法
initGlobalApiMixin(Vue)

export default Vue;

globalApiMixin.js

js
import { mergeOptions } from "../utils/index";

// 初始化全局Api
export function initGlobalApiMixin(Vue) {
  // 存放全局API
  Vue.options = {};

  Vue.mixin = function (mixin) {
   // 选项合并
    this.options = mergeOptions(this.options, mixin);
  };
}

选项合并策略mergeOptions

mergeOptions是一个工具方法, 因此放到util

js
const strategy = Object.create(null); // merge策略

// 默认合并策略
function defaultMergeStrategy(parent, child) {
  return child === undefined ? parent : child;
}

/**
 * 合并选项方法
 * 合并选项以child中的属性为准, 若child中没有的属性,则以parent为准
 * @param {*} parent 父级选项
 * @param {*} child 子级选项
 * @return {object} 合并后的选项
 */
export function mergeOptions(parent, child) {
  const options = {};
  // 将parent里面的字段copy到child里面
  for (const key in parent) {
    mergeField(key);
  }

  for (let key in child) {
    if (!parent.hasOwnProperty(key)) {
      mergeField(key);
    }
  }

  // 合并字段
  function mergeField(k) {
   
 // 若是不存在某个字段的合并策略, 则使用默认策略进行合并
 // 可以很方便的进行扩展, 若后续需要来扩展computed  watch  data等合并策略
 // 只需要在strategy内进行添加对应策略即可
    const strat = strategy[k] || defaultMergeStrategy;
    options[k] = strat(parent[k], child[k]);
  }

  return options; // 将合并得到的options返回
}

生命周期合并策略mergeLifecyleHook

js
// 生命周期钩子函数Hooks
const LIFECYCLE_HOOKS = [
  "beforeCreate",
  "created",
  "beforeMount",
  "mounted",
  "beforeUpdate",
  "updated",
  "beforeDestory",
  "destoryted",
];

// 策略中添加生命周期钩子函数合并策略
LIFECYCLE_HOOKS.forEach((hook) => {
  strategy[hook] = mergeLifecyleHook;
});

// 合并生命周期钩子函数策略
// 多个同名的钩子函数合并为一个数组, oldhook在前, newhook在后
// 到时候执行的时候依次执行, 先执行old, 载执行new
function mergeLifecyleHook(oldHook, newHook) {
  if (newHook) {
    if (oldHook) {
      return oldHook.concat(newHook);
    }
    return [newHook];
  }
  return oldHook;
}

开始合并的时候, Vue.options中肯定没有对应的生命周期钩子函数, 因此第一次运行mergeLifecyleHook, oldhookundefined,用户传入的有生命周期钩子的时候, 第一次newHook肯定是有值的,需要将其变成数组, 因为mixin的时候可能会有多个组件, 其中都可能有同名生命周期钩子,这些钩子都需要被执行

使用mergeOptions

特别注意

选项合并用 vm.constructor.options 而不是用 Vue.options 是因为后面Vue可能出现被继承的现象,若被继承后,应该是用Vue.options就不一定是父级选项, 就不符合逻辑, 而通过vm.constructor取到options一定是父级的选项

在初始化数据的时候, 拿到用户传入的options, 需要进行合并

js
  // 初始化
  Vue.prototype._init = function (option) {
    const vm = this;
    // vm.$options = option || {};
    // TODO: 选项合并用 vm.constructor.options 而不是用 Vue.options 是因为后面Vue可能出现被继承的现象,
    // 若被继承后,应该是用Vue.options就不一定是父级选项, 就不符合逻辑, 而通过vm.constructor取到options一定是父级的选项
    vm.$options = mergeOptions(vm.constructor.options, option) || {};


    // 数据观测(初始化状态)
    initState(vm);


    // 挂载(在el存在的时候进行挂载)
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };

提示

目前选项合并进处理的生命周期的合并策略, 对于data等属性还没有对应的合并策略, 且看后续

增加生命周期Hook调用callHook

目前页面可以正常渲染了, 但是在页面渲染的过程中, 目前我们还不能添加一些业务逻辑在渲染过程中做一些事, 比如在数据初始化完成后发送Ajax请求服务端数据, 页面渲染完成后进行一些DOM操作, 这就需在每一个关键节点添加生命周期钩子函数

  • beforeCreate created钩子
js
  Vue.prototype._init = function (option) {
    const vm = this;
    // vm.$options = option || {};
    // TODO: 选项合并 vm.constructor.options而不是用Vue.options是因为后面Vue可能出现被继承的现象,
    // 若被继承后,应该是用Vue.options就不一定是父级选项, 就不符合逻辑, 而通过vm.constructor取到options一定是父级的选项
    vm.$options = mergeOptions(vm.constructor.options, option) || {};

    callHook(vm, "beforeCreate"); // 初始化状态之前

    // 数据观测(初始化状态)
    initState(vm);

    callHook(vm, "created"); // 初始化状态之后

    // 挂载(在el存在的时候进行挂载)
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  • beforeMount, mounted钩子函数
js
  // 挂载
  Vue.prototype.$mount = function (el) {
    const vm = this;
    const opts = vm.$options;
    // 取到el元素
    el = typeof el === "string" ? document.querySelector(el) : el;

    // 挂载优先级 render函数 > temlate  > el
    if (!opts.render) {
      // 取到Html模板
      if (!opts.template && el) {
        opts.template = el.outerHTML;
      }
      // 开始编译模板得到render函数opts.render
      opts.render = compileTemplate(opts.template);
    }
    // 组件挂载前
    callHook(vm, "beforeMount");
    // 组件挂载, 将vm上的数据挂载到el上面
    mountComponent(vm, el);
    // 组件挂载后
    callHook(vm, "mounted");
  };

callHook(vm, 'hook')

js
// 触发钩子函数(同步依次执行)
export function callHook(vm, hook) {
  const hooks = vm.$options[hook] || [];
  for (let i = 0; i < hooks.length; i++) {
    hooks[i].call(vm);
  }
}

小结

  1. initGlobalApiMixin扩展静态全局API, 例如: mixin
  2. 利用策略模式编写选项合并逻辑, 有默认合并策略
  3. 扩展策略模式添加生命周期的合并策略, 多个同名生命周期钩子会按顺序放入数组, 后面触发一次执行, 且是同步执行
  4. 在合适的时机调用callHook触发生命周期钩子, 初始化前/后, 挂载前/后