Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【源码】mixin 源码解析 #41

Open
YIngChenIt opened this issue Jul 3, 2020 · 0 comments
Open

【源码】mixin 源码解析 #41

YIngChenIt opened this issue Jul 3, 2020 · 0 comments

Comments

@YIngChenIt
Copy link
Owner

【源码】mixin 源码解析

前言

今天开始探索mixin的源码,我们可以知道mixin是什么时候进行合并的以及对各个类型是如何进行合并的。

从源码看 Vue 中的 Mixin

我们先从Vue.mixin入手看源码

// vue/src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

我们在使用Vue.mixin的时候传入了一个对象,也就是源码中的形参mixin,然后调用mergeOptions方法将全局基础options和传入的mixin进行合并

那么全局的基础options有什么呢?

// vue/src/core/global-api/index.js
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})

其中ASSET_TYPES['component', 'directive', 'filter'],也就是全局的基础options是'component', 'directive', 'filter'

我们继续来看下mergeOptions方法是如何进行合并的(代码有删减)

// vue/src/core/util/options.js
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {

if (child.mixins) { // 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并
    for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
    }
}

  const options = {} 
  let key
  for (key in parent) {
    mergeField(key) // 先遍历parent的key 调对应的strats[XXX]方法进行合并
  }
  for (key in child) {
    if (!hasOwn(parent, key)) { // 如果parent已经处理过某个key 就不处理了
      mergeField(key) // 处理child中的key 也就parent中没有处理过的key
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并
  }
  return options
}

上述代码的作用主要是这3点

  • 优先递归处理 mixins

  • 先遍历合并 parent 中的key,调用mergeField方法进行合并,然后保存在变量options

  • 再遍历 child,合并补上 parent 中没有的key,调用mergeField方法进行合并,保存在变量options

其实核心在于strats中对应的不同类型的处理方法,我们接下来分为几种类型来看下对应的合并策略

替换型

propsmethodsinjectcomputed属于替换式,我们来看下代码

strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal
  const ret = Object.create(null) // 创建一个第三方对象 ret
  extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中
  if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
  return ret
}
strats.provide = mergeDataOrFn

从上述代码可以看到propsmethodsinjectcomputed的合并策略都是将新的同名参数替代旧的参数,也就是所说的替换型

合并型

data属于合并型,我们将源码简写一下

strats.data = function(parentVal, childVal, vm) {    
    return mergeDataOrFn(
        parentVal, childVal, vm
    )
};

function mergeDataOrFn(parentVal, childVal, vm) {    
    return function mergedInstanceDataFn() {        
        var childData = childVal.call(vm, vm) // 执行data挂的函数得到对象
        var parentData = parentVal.call(vm, vm)        
        if (childData) {            
            return mergeData(childData, parentData) // 将2个对象进行合并                                 
        } else {            
            return parentData // 如果没有childData 直接返回parentData
        }
    }
}

function mergeData(to, from) {    
    if (!from) return to    
    var key, toVal, fromVal;    
    var keys = Object.keys(from);   
    for (var i = 0; i < keys.length; i++) {
        key = keys[i];
        toVal = to[key];
        fromVal = from[key];    
        // 如果不存在这个属性,就重新设置
        if (!to.hasOwnProperty(key)) {
            set(to, key, fromVal);
        }      
        // 存在相同属性,合并对象
        else if (typeof toVal =="object" && typeof fromVal =="object") {
            mergeData(toVal, fromVal);
        }
    }    
    return to
}

从代码可以看到这里遍历了要合并的 data 的所有属性,然后根据不同情况进行合并:

  • 当目标 data 对象不包含当前属性时,调用 set 方法进行合并,后面讲 set。
  • 当目标 data 对象包含当前属性并且当前值为纯对象时,递归合并当前对象值,这样做是为了防止对象存在新增属性。

set方法其实就是一些合并重新赋值的方法,这里就不展开来说了

队列型

全部生命周期函数和watch都是队列型合并策略

function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

代码比较简单,Vue 实例的生命周期钩子被合并为一个数组,然后正序遍历一次执行。watch的策略差不多,这里就不展开了

叠加型

componentdirectivesfilters属于叠加型的策略,但是业务中用的是真的少

strats.components=
strats.directives=

strats.filters = function mergeAssets(
    parentVal, childVal, vm, key
) {    
    var res = Object.create(parentVal || null);    
    if (childVal) { 
        for (var key in childVal) {
            res[key] = childVal[key];
        }   
    } 
    return res
}

通过原型链进行层层的叠加

总结

代码有点多,如果还是不理解的话没有关系,我们来大致总结一下就好了

  • 在我们调用Vue.mixin的时候会通过mergeOptions方法将全局基础options(component', 'directive', 'filter)进行合并

  • mergeOptions内部优先进行mixins的递归合并,然后先父再子调用mergeField进行合并,不同的类型走不同的合并策略

  • 替换型策略有propsmethodsinjectcomputed, 就是将新的同名参数替代旧的参数

  • 合并型策略是data, 通过set方法进行合并和重新赋值

  • 队列型策略有生命周期函数和watch,原理是将函数存入一个数组,然后正序遍历依次执行

  • 叠加型有componentdirectivesfilters,将回调通过原理链联系在一起

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant