写文章不容易,点个赞呗兄弟 专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】数组
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧函数
今天探索的是 mixins 的源码,mixins 根据不一样的选项类型会作不一样的处理测试
篇幅会有些长,你知道的,有不少种选项类型的嘛,但不是很难。只是涉及源码不免会有些烦,this
不过这篇文章也不是给你直接看的,是为了可让你学习源码的时候提供微薄帮助而已3d
若是不想看源码的,能够看个人白话版code
【Vue原理】Mixin - 白话版 component
咱们也是要带着两个问题开始对象
一、何时开始合并blog
二、怎么合并
若是你以为排版难看,请点击下面原文连接 或者 关注公众号【神仙朱】
合并分为两种
一、全局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实例都须要和这全局选项合并
二、全局options和 自定义options合并
在调用Vue 的时候,首先进行的就是合并
function Vue(options){ vm.$options = mergeOptions( { 全局component, 全局directive, 全局filter 等....}, options , vm ); // ...处理选项,生成模板,挂载DOM 等.... }
options 就是你本身传进去的对象参数,而后跟 全局options 合并,全局options 是哪些,也已经说过了
上面的代码一直出现一个函数 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 }
这段代码看上去有点绕,其实无非就是
一、先遍历合并 parent 中的key,保存在变量options
二、再遍历 child,合并补上 parent 中没有的key,保存在变量options
三、优先处理 mixins ,可是过程跟上面是同样的,只是递归处理而已
在上面实例初始化时的合并, parent 就是全局选项,child 就是组件自定义选项,由于 parent 权重比 child 低,因此先处理 parent 。
“公司开除程序猿,也是先开始做用较低。。”
重点其实在于 各式各样的处理函数 strat,下面将会一一列举
这段函数言简意赅,意思就是优先使用组件的options
组件options>组件 mixin options>全局options
var defaultStrats= function(parentVal, childVal) { return childVal === undefined ? parentVal : childVal };
咱们先默认 data 的值是一个函数,简化下源码 ,可是其实看上去仍是会有些复杂
不过咱们主要了解他的工做过程就行了
一、两个data函数 组装成一个函数
二、合并 两个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 }
把全部的钩子函数保存进数组,重要的是数组子项的顺序
顺序就是这样
[ 全局 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; // ... 等其余钩子
我一直以为这个是比较好玩的,这种类型的合并方式,我是历来没有在项目中使用过的
原型叠加
两个对象并无进行遍历合并,而是把一个对象直接当作另外一个对象的原型
这种作法的好处,就是为了保留两个相同的字段且能访问,避免被覆盖
学到了学到了.....反正我是学到了
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 }
就是下面这种,层层叠加的原型
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 };
这几个东西,是不容许重名的,合并成对象的时候,不是你死就是我活
重要的是,以谁的为主?必然是组件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 };
其实在白话版里面,就已经测试了不少例子,整个执行的流程也描述很清楚了,这里就是放个源码供参考