主题
选项合并策略
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
, oldhook
为undefined
,用户传入的有生命周期钩子的时候, 第一次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);
}
}
小结
initGlobalApiMixin
扩展静态全局API, 例如:mixin
- 利用策略模式编写选项合并逻辑, 有默认合并策略
- 扩展策略模式添加生命周期的合并策略, 多个同名生命周期钩子会按顺序放入数组, 后面触发一次执行, 且是同步执行
- 在合适的时机调用
callHook
触发生命周期钩子, 初始化前/后, 挂载前/后