Vue2核心原理(简易版)-Mixin混入

884 阅读4分钟

Vue2核心原理(简易版)-Mixin混入

什么是Mixin-混入?

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。文档地址

简单来说,可以把mixin看成是一种vue-style的组合,一个mixin对象包含vue实例的所有组件选项,并且各个选项将按照一定的规则进行合并。从而实现了逻辑复用,达到精简代码的目的。

举例:

// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义一个使用混入对象的组件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"

我们甚至可以全局混入,让每一个vue实例都可以享用此mixin。

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption);
      console.log(this.myData);
    }
  }
})

new Vue({
  myOption: 'option1!',
  data: () => {
    return {
      myData: "data1"  
    }
  }
})
// => "hello1!"
new Vue({
  myOption: 'option2!',
  data: () => {
    return {
      myData: "data2" 
    }
  }
})
// => "option2!" "data2"

请你实现Mixin(手动狗头脸)

核心其实就下面两小段,只不过我们实现mergeOptions这个方法稍费些功夫。

// init.js
Vue.prototype._init = function(options) {
  const vm = this;  
  // vm.constructor === Vue ~ vm实例的构造函数是Vue  
  vm.$options = mergeOptions(vm.constructor.options, options); // 后面会对options进行扩展操作
  // 对数据进行初始化 watch computed props data ...
  initState(vm); // vm.$options.data  数据劫持
  if (vm.$options.el) {
      // 将数据挂载到这个模板上
      vm.$mount(vm.$options.el);
  }
}
    
// global-api/mixin.js
import { mergeOptions } from '../util/index'
export function initMixin (Vue) {
  // 创建一个严格空对象,可以理解为{}
  Vue.options = Object.create(null);
  // 用来存所有vue子类的初始构造函数大Vue
  Vue.options._base = Vue;
  
  Vue.mixin = function (mixinOptions) {
    // 将属性合并到Vue.options上
    // 这里的this是,谁调用它就指向谁,那么往上看两个例子,Vue.mixin(xxx),所以这里指向的是大Vue
    this.options = mergeOptions(this.options, mixinOptions)
    return this;
  }
}

结合上面全局混入的例子,我们可以看到,在Vue构造上调用mixin方法的时候,我们给这个大Vue首先增加了一个options属性并且赋值为一个空对象。接下来我们执行了this.options = mergeOptions(this.options, mixinOptions),那么也就是说,无论Vue.mixin调用了多少次,我们都会依次把mixinOptions给它合并。接下来,我们new Vue()的时候,构造函数调了_init方法,我们把刚刚定义的全局mixinOptions又与最新的真正的vue options进行混合,得到最后的options,最后挂载到vm.$options上。

接下来,就是核心的函数mergeOptions的实现了。

// utils/index
let lifeCycleHooks = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
]
let strats = {}; // 存放各种策略
// {} {beforeCreate:Fn} => {beforeCreate:[fn]}
// {beforeCreate:[fn]} {beforeCreate:fn} => {beforeCreate:[fn,fn]}

function mergeHook(parentVal, childVal) {
    if (childVal) {
        if (parentVal) {
            return parentVal.concat(childVal); // 后续
        } else {
            return [childVal]; // 第一次
        }
    } else {
        return parentVal
    }
}
lifeCycleHooks.forEach(hook => {
    strats[hook] = mergeHook
});

strats.components = function(parentVal, childVal) {
    // Vue.options.components
    let options = Object.create(parentVal); // 根据父对象构造一个新对象 options.__proto__= parentVal
    if (childVal) {
        for (let key in childVal) {
            options[key] = childVal[key];
        }
    }
    return options
}

export function mergeOptions(parent, child) {
    const options = {}; // 合并后的结果
    for (let key in parent) {
        mergeField(key);
    }
    for (let key in child) {
        if (parent.hasOwnProperty(key)) {
            continue;
        }
        mergeField(key);
    }

    function mergeField(key) {
        let parentVal = parent[key];
        let childVal = child[key];
        // 策略模式
        if (strats[key]) { // 如果有对应的策略就调用对应的策略即可
            options[key] = strats[key](parentVal, childVal)
        } else {
            if (isObject(parentVal) && isObject(childVal)) {
                options[key] = { ...parentVal, ...childVal }
            } else {
                // 父亲中有,儿子中没有
                options[key] = child[key] || parent[key];
            }
        }
    }
    return options
}
  1. 首先我们要明确的是,进入mergeOptions的两个参数parent和child,是以child优先级为第一的,也就是说如果有覆盖的情况,那么一定是child覆盖parent。
  2. 执行mergeOptions,要保全parent和child里面的所有key都不丢失,所以遍历了两个object,但是又不能重复,所以在第二次遍历的时候判断之前没有遍历过的才去mergeField
  3. 接下来就是mergeField合并策略,我们提前定义好每一种不同的key名,可能会用到的不同的合并方法,如果匹配到了那我们就用这些对应的策略。
  4. 如果是非策略模式,那么我们就认为这是一个普通的选项。此时,如果父子元素值都是对象的话,那么我们就以childVal为优先,合并;如果不是对象,那么还是以儿子为优先,childVal没有值才设置为parentVal。

原文链接

如果你在非掘金地址看到这篇文章,这里有原文传送门,您的点赞是对我最大的支持!

完 🎉

下一讲,组件初始化流程