乐趣区

Vue原理Mixins-源码版

写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue 版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧

【Vue 原理】Mixins – 源码版

今天探索的是 mixins 的源码,mixins 根据不同的选项类型会做不同的处理

篇幅会有些长,你知道的,有很多种选项类型的嘛,但不是很难。只是涉及源码难免会有些烦,

不过这篇文章也不是给你直接看的,是为了可以让你学习源码的时候提供微薄帮助而已

如果不想看源码的,可以看我的白话版

【Vue 原理】Mixin – 白话版

我们也是要带着两个问题开始

1、什么时候开始合并

2、怎么合并

如果你觉得排版难看,请点击下面原文链接 或者 关注公众号【神仙朱】


什么时候合并

合并分为两种

1、全局 mixin 和 基础全局 options 合并

这个过程是先于你调用 Vue 时发生的,也是必须是先发生的。这样 mixin 才能合并上你的自定义 options

Vue.mixin = function(mixin) {
    this.options = mergeOptions(this.options, mixin);
    return this
};

基础全局 options 是什么?

就是 components,directives,filters 这三个,一开始就给设置在了 Vue.options 上。所以这三个是最先存在全局 options

Vue.options = Object.create(null);

['component','directive','filter'].forEach(function(type) {Vue.options[type + 's'] = Object.create(null);
});

这一步,是调用 Vue.mixin 的时候就马上合并了,然后这一步完成 以后,举个栗子

全局选项就变成下面这样,然后每个 Vue 实例都需要和这全局选项合并

2、全局 options 和 自定义 options 合并

在调用 Vue 的时候,首先进行的就是合并

function Vue(options){
    vm.$options = mergeOptions(
        { 全局 component,全局 directive,全局 filter 等....},
        options , vm
    );

    // ... 处理选项,生成模板,挂载 DOM 等....
}

options 就是你自己传进去的对象参数,然后跟 全局 options 合并,全局 options 是哪些,也已经说过了


怎么合并

上面的代码一直出现一个函数 mergeOptions,他便是合并的重点

来看源码

1、mergeOptions

function mergeOptions(parent, child, vm) {    

    // 遍历 mixins,parent 先和 mixins 合并,然后在和 child 合并
    if (child.mixins) {for (var i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm);
        }
    }    
    
    var options = {}, key;    

    // 先处理 parent 的 key,for (key in parent) {mergeField(key);
    }    

    // 遍历 child 的 key,排除已经处理过的 parent 中的 key
    for (key in child) {if (!parent.hasOwnProperty(key)) {mergeField(key);
        }
    }    

    // 拿到相应类型的合并函数,进行合并字段,strats 请看下面
    function mergeField(key) {    

        // strats 保存着各种字段的处理函数,否则使用默认处理
        var strat = strats[key] || defaultStrat;    

        // 相应的字段处理完成之后,会完成合并的选项
        options[key] = strat(parent[key], child[key], vm, key);
    }    
    return options
}

这段代码看上去有点绕,其实无非就是

1、先遍历合并 parent 中的 key,保存在变量 options

2、再遍历 child,合并补上 parent 中没有的 key,保存在变量 options

3、优先处理 mixins,但是过程跟上面是一样的,只是递归处理而已

在上面实例初始化时的合并,parent 就是全局选项,child 就是组件自定义选项,因为 parent 权重比 child 低,所以先处理 parent。

“公司开除程序猿,也是先开始作用较低。。”

重点其实在于 各式各样的处理函数 strat,下面将会一一列举

2、defaultStrats

这段函数言简意赅,意思就是优先使用组件的 options

组件 options> 组件 mixin options> 全局 options

var defaultStrats= function(parentVal, childVal) {        
    return childVal === undefined ?        
            parentVal :
            childVal
};

3、data

我们先默认 data 的值是一个函数,简化下源码,但是其实看上去还是会有些复杂

不过我们主要了解他的工作过程就好了

1、两个 data 函数 组装成一个函数

2、合并 两个 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) 

        var parentData = parentVal.call(vm, vm)        

        if (childData) {return mergeData(childData, parentData)

        } else {return 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
}

4、生命钩子

把所有的钩子函数保存进数组,重要的是数组子项的顺序

顺序就是这样

[    
    全局 mixin - created,组件 mixin-mixin - created,组件 mixin - created,组件 options - created
]

所以当数组执行的时候,正序遍历,就会先执行全局注册的钩子,最后是 组件的钩子

function mergeHook(parentVal, childVal) {    

    var arr;

    arr = childVal ?  

        // concat 不只可以拼接数组,什么都可以拼接
        ( parentVal ?  
            // 为什么 parentVal 是个数组呢

            // 因为无论怎么样,第一个 parent 都是{component,filter,directive}
            // 所以在这里,合并的时候,肯定只有 childVal,然后就变成了数组
            parentVal.concat(childVal) : 

            (Array.isArray(childVal) ? childVal: [childVal] )
        ) :
        parentVal  

    return arr

}

strats['created'] = mergeHook;
strats['mounted'] = mergeHook;
// ... 等其他钩子

5、component、directives、filters

我一直觉得这个是比较好玩的,这种类型的合并方式,我是从来没有在项目中使用过的

原型叠加

两个对象并没有进行遍历合并,而是把一个对象直接当做另一个对象的原型

这种做法的好处,就是为了保留两个相同的字段且能访问,避免被覆盖

学到了学到了 ….. 反正我是学到了

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
}

就是下面这种,层层叠加的原型

6、watch

watch 的处理,也是合并成数组,重要的也是合并顺序,跟 生命钩子一样

这样的钩子

[    
    全局 mixin - watch,组件 mixin-mixin - watch,组件 mixin - watch,组件 options - watch
]

按照正序执行,最后执行的 必然是组件的 watch

strats.watch = function(parentVal, childVal, vm, key) {if (!childVal) {return Object.create(parentVal || null)
    }    

    if (!parentVal)  return childVal

    var ret = {};    

    // 复制 parentVal 到 ret 中
    for (var key in parentVal) {ret[key] = parentVal[key];
    }    

    for (var key$1 in childVal) {var parent = ret[key$1];        
        var child = childVal[key$1];        

        if (!Array.isArray(parent)) {parent = [parent];
        }
        ret[key$1] = parent ? parent.concat(child) : 
                (Array.isArray(child) ? child: [child] );

    }    
    return ret
};

7、props、computed、methods

这几个东西,是不允许重名的,合并成对象的时候,不是你死就是我活

重要的是,以谁的为主?必然是组件 options 为主了

比如

组件的 props:{name:””}

组件 mixin 的 props:{name:””, age: “”}

那么 把两个对象合并,有相同属性,组件的 name 会替换 mixin 的 name

trats.props = 
strats.methods = 
strats.inject = 

strats.computed = function(parentVal, childVal, vm, key) {if (!parentVal) return childVal

    var ret = Object.create(null);   


    // 把 parentVal 的字段 复制到 ret 中
    for (var key in parentVal) {ret[key] = parentVal[key];
    }    

    if (childVal) {for (var key in childVal) {ret[key] = childVal[key];
        }
    }    

    return ret

};

其实在白话版里面,就已经测试了很多例子,整个执行的流程也描述很清楚了,这里就是放个源码供参考

退出移动版