精华内容
参与话题
问答
  • 具体合并策略: 1、data mixins中的data会合并到data中,有冲突的话,data中数据覆盖mixins中的数据。 2、钩子函数 mixins中的钩子函数也会执行,先执行mixins中的钩子函数。 3、methods、components和...

    mixins

    混合 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。
    混合对象可以包含任意组件选项。
    当组件使用混合对象时,所有混合对象的选项将被混入该组件本身的选项。

    具体合并策略: 

    1、data

    mixins中的data会合并到data中,有冲突的话,data中数据覆盖mixins中的数据。

    2、钩子函数

    mixins中的钩子函数也会执行,先执行mixins中的钩子函数。

    3、methodscomponents 和 directives

    methodscomponents 和 directives会执行,但有冲突时,原methodscomponents 和 directives会覆盖mixins中的methodscomponents 和 directives

     

    应用场景:

    多个实例引用了相同或相似的方法或属性等,可将这些重复的内容抽取出来作为mixins的js,export出去,在需要引用的vue文件通过mixins属性注入

    展开全文
  • Vue2.x选项合并策略一(源码解读) 根据上篇文章对选项合并策略源码的解读,可以利用Vue内部提供的API实现自定义的合并策略以及生命周期钩子函数。 Vue.config.optionMergeStrategies tips:以下父组件选项可...

    Vue2.x选项合并策略一(源码解读)

    根据上篇文章对选项合并策略源码的解读,可以利用Vue内部提供的API实现自定义的合并策略以及生命周期钩子函数。

    Vue.config.optionMergeStrategies

    在这里插入图片描述

    tips:以下父组件选项可理解为mixins混入的数据;子组件选项为当前自身组件选项。

    自定义选项合并策略

    /src/utils/customMergeHook.js
    合并策略:

    • 若子组件选项不存在,则返回父组件选项;
    • 否则,判断父组件选项是否存在,如果存在则合并父子组件选项;不存在则返回子组件选项,以数组的形式。
    /**
     * Hooks are merged as arrays.
     */
    function mergeHook(parentVal, childVal) {
      const res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal;
      return res ? dedupeHooks(res) : res;
    }
    
    // 去重函数
    function dedupeHooks (hooks) {
      const res = []
      for (let i = 0; i < hooks.length; i++) {
        // 判断数据是否已存在
        if (res.indexOf(hooks[i]) === -1) {
          res.push(hooks[i])
        }
      }
      return res
    }
    

    自定义生命周期函数

    一、开发核心业务
    /src/utils/customLifeCycle.js

    • 导入相关依赖
    import Vue from 'vue';
    // 以上自定义的选项合并策略
    import { mergeHook } from '@/utils/customMergeHook'
    
    • 初始化函数,设置自定义生命周期函数名称及选项合并策略
    function init() {
      const strats = Vue.config.optionMergeStrategies
      /**
       * 自定义生命周期函数
       * 将其生命周期合并策略设置为以上自定义的选项合并策略
       * 也可以设置与内置生命周期合并策略一致;(例:strats.XXX = strats.created)
       */
      strats.pageVisible = mergeHook
      strats.pageHidden = mergeHook
      strats.pageResize = mergeHook
    }
    
    // 执行初始化函数
    init()
    
    • 编写监听触发生命周期函数的方法
    // 通过已触发该生命周期函数
    const notifyTriggerCustomHooks = (lifeCycleName, vm) => {
      // 生命周期存在$options中,通过$options[lifeCycleName]获取生命周期
      const lifeCycles = vm.$options[lifeCycleName]
    
      // 使用以上自定义选项合并策略,返回的是一个数组格式
      if (lifeCycles && lifeCycles.length) {
        // 遍历lifeStyleName对应的生命周期函数列表,依次执行
        lifeCycles.forEach(lifeCycle => {
          lifeCycle.call(vm)
        })
      }
    
      // 遍历所有的子组件,然后依次递归执行
      if (vm.$children && vm.$children.length) {
        vm.$children.forEach(child => {
          notifyTriggerCustomHooks(lifeCycleName, child)
        })
      }
    }
    
    • 绑定事件
    /**
     * 将事件变化绑定到根节点上
     * @param {*} rootVm
     */
    export function bindVm(rootVm) {
      let lifeCycleName  = undefined
      // 监听窗口页面的显示隐藏
      window.addEventListener('visibilitychange', () => {
        // 判断当前是哪个生命周期函数
        if (document.visibilityState == 'hidden') {
          lifeCycleName = 'pageHidden'
        } else if (document.visibilityState == 'visible') {
          lifeCycleName = 'pageVisible'
        }
        if (lifeCycleName) {
          // 通知所有组件生命周期发生变化了
          notifyTriggerCustomHooks(lifeCycleName, rootVm)
        }
      })
    
      // 监听窗口大小变化
      window.addEventListener('resize', () => {
        lifeCycleName = 'pageResize'
        notifyTriggerCustomHooks(lifeCycleName, rootVm)
      })
    }
    

    二、绑定事件
    /src/main.js

    ...
    import{ bindVm } from '@/utils/customLifeCycle'
    const vm = new Vue({
      ...
    })
    // 执行方法,将事件绑定根节点上
    bindVm(vm)
    

    三、使用

    • 编写mixins混入
      /src/mixins/index.js
    export default {
      pageHidden() {
      	// mixins混入,触发隐藏页面;执行相关业务
      	...
      },
      pageVisible() {
        // mixins混入,触发显示页面;执行相关业务
      	...
      },
      pageResize() {
        // mixins混入,触发窗口大小发生变化;执行相关业务
      	...
      }
    }
    
    • 页面调用
      /src/views/page1/index.vue(以该页面为例)
    <template>
      ...
    </template>
    <script>
    ...
    import mixins from '@/mixins'
    export default {
      // mixins混入
      mixins: [mixins],
      pageHidden() {
        // 组件内触发隐藏页面;执行相关业务
      	...
      },
      pageVisible() {
        // 组件内触发显示页面;执行相关业务
      	...
      },
      pageResize() {
        // 组件内触发窗口大小发生变化;执行相关业务
      	...
      }
    }
    </script>
    <style lang="scss" scoped>
      ...
    </style>
    
    展开全文
  • 我们 之前 谈 学习vue源码(5) ...如图所示这次我们就来深入研究下,因此也就离不开Vue中的一个重要思想:合并策略了。我们有时面试时可能会遇到这样的问题:引入的mixin的data中 有 name这个属性,自己注册的组...

    5697b0324d7203e3d3157b4fcffc693d.png

    我们 之前 谈 学习vue源码(5) 手写Vue.use、Vue.mixin、Vue.compile的时候 谈到了Vue.mixin的源码实现,然后谈到了mergeOptions,那时并没有深入解说 这个函数 的原理。如图所示

    85f54b34d133ec3589aca1db2cbe0d87.png

    这次我们就来深入研究下,因此也就离不开Vue中的一个重要思想:合并策略了。

    我们有时面试时可能会遇到这样的问题:

    • 引入的mixin的data中 有 name这个属性,自己注册的组件的data中也有name,那哪个会生效?
    • 引入的mixin中有created,自己注册的组件中也有created,那哪个create会先执行?
    • 。。。。

    这样的问题其实还很多,而想要弄清楚这些问题,其实就是 合并策略的问题。

    现在先大概地谈下合并策略,是让大家有兴趣去研究源码的时候,可以提前理清一下思路。暂时没时间了解源码的,也可以先了解下内部流程,对解决一些奇奇怪怪的问题也是很有作用的

    mixins 我觉得可能大家很少用把,但是这个东西真的非常有用。相当于封装,提取公共部分。

    显然,今天我不是来教大家怎么用的,怎么用看文档就好了,我是讲解 生命的真谛 内部的工作原理。如果不懂用的,请去官网看怎么用,兄弟

    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 的时候就马上合并了,然后这一步完成 以后,举个栗子

    b4c2d681bae8db37d4a71627c65afada.png

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

    f8402d1c03f3c24cad93743b11ee236b.png

    可能你会奇怪,我明明没写keep-alive这些组件,为什么可以用这些组件呢?

    其实原因就在这,在我们new Vue,之前 ,源码 就给 Vue 的 option中的 components数组 添加了keep-alive这些组件。

    基础组件形成后 就 和 全局的mixin合并生成新的option。

    然后,我们new Vue()时,会传入自己的option,然后就把自己的option和上面那个新的option合并。

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

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

    function Vue(options){
    
    
    
        vm.$options = mergeOptions(
            { 全局component,
                     全局directive,
                     全局filter 等....},
            options , vm
        );
    
        ...处理选项,生成模板,挂载DOM 等....
    
    }
    

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

    626fbc86f5a59f9f571ca34c47d5e326.png

    2.怎么合并

    下面开始讲解各种合并策略

    1、函数合并叠加

    包括选项:data,provide

    把两个函数合并加到一个新函数中,并返回这个函数。在函数里面,会执行 那两个方法。

    按照这个流程,使用代号

    data 中数据有重复的,权重大的优先,比如下面

    var test_mixins={
        data(){        
            return {name:34}
        }
    }
    var a=new Vue({    
        mixins:[test_mixins],
        data(){        
            return {name:12}
        }
    })

    可以看到,mixin 和 组件本身 的 data 都有 name 这个数据,很显然会以组件本身的为主,因为组件本身权重大

    2、数组叠加

    包括生命周期函数和watch

    生命周期函数

    权重越大的越放后面,会合并成一个数组,比如created

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

    执行流程是

    edbb16e6fa8d86424a6130f9c5e9ca28.png

    生命周期,权重小的 先执行

    watch

    合并成一个下面这样的数组,权重越大的越放后面

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

    执行流程是

    d9a407d3b3bc7c5a9cdbf2f6ceb00c77.png

    监听回调,权重小的 先执行

    3、原型叠加

    包括选项:components,filters,directives

    两个对象合并的时候,不会相互覆盖,而是 权重小的 被放到 权重大 的 的原型上

    这样权重大的,访问快些,因为原型链短了

    A.__proto__ = B  
    B.__proto__ = C  
    C.__proto__ = D

    两个对象合并的时候,不会

    以 filter 为例,下面是四种 filter 合并

    // 全局 filter
    Vue.filter("global_filter",function (params) {})
    // mixin 的 mixin
    var mixin_mixin={    
        filters:{
            mixin_mixin_filter(){}
        }
    }
    // mixin filter
    var test_mixins={
        mixins:[mixin_mixin],    
        filters:{
            mixin_filter(){}
        }
    }
    // 组件 filter
    var a=new Vue({    
        mixins:[test_mixins],    
        filters:{
            self_filter(){}
        }
    })
    

    结果就系这样..

    f8379120432c867efc75ed61aaec9f5e.png

    4、覆盖叠加

    包括选项:props,methods,computed,inject

    两个对象合并,如果有重复key,权重大的覆盖权重小的

    比如

    组件的 props:{ name:""}
    组件mixin 的 props:{ name:"", age: "" }

    那么 把两个对象合并,有相同属性,以 权重大的为主,组件的 name 会替换 mixin 的name。

    这个其实就是跟第一种(函数合并叠加)是一样的,只不过第一种是函数

    5、直接替换

    这是默认的处理方式,当选项不属于上面的处理方式的时候,就会像这样处理,包含选项:el,template,propData 等

    两个数据只替换,不合并,权重大的,会一直替换 权重小的,因为这些属于只允许存在一个,所有只使用权重大的选项

    组件 设置 template,mixin 也设置 template,不用怕,组件的肯定优先

    这个好像跟 覆盖叠加 很像,其实不一样,覆盖叠加会把两个数据合并,重复的才覆盖。而这个不会合并,直接替换掉整个选项

    好了,现在我们结合 源码 来谈谈这五种合并策略。

    来看看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,下面将会一一列举

    注意,不要对strat这个词迷糊,其实就是strategy策略一词的缩写,它就是一个对象,这个对象里 保存了上面说的各种策略。如下

    strat = {
       // 合并data的策略
      data: function(parentVal, childVal, vm) {    
           return mergeDataOrFn(
    
            parentVal, childVal, vm
          ) 
        }
      // 合并生命勾子的策略
      created: mergeHook;
    
      mounted: mergeHook;
      
      。。。。。
    }
    

    下面讲各种合并策略

    1. 默认策略 defaultStrats

    该策略 不保存在 strat对象中,也就是说在strat对象找不到key对应的策略时就会使用该策略,上面 mergeOptions函数中 的

      function mergeField(key) {    
         // strats 保存着各种字段的处理函数,否则使用默认处理
        var strat = strats[key] || defaultStrat;    
        // 相应的字段处理完成之后,会完成合并的选项
    
        options[key] = strat(parent[key], child[key], vm, key);
      }    
        
        
    

    这段代码就是这个意思。

    好了,看下默认策略的源码实现

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

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

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

    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
    
    }
    

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

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

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

    2、合并 两个data函数执行返回的 数据对象

    3.生命钩子

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

    顺序就是这样

    [    
        全局 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;
    ... 等其他钩子
    

    4. 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
    
    }
    

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

    e9f3c2258839761046ca3bfacd0cdb99.png

    5. 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
    
    };
    

    6. props、computed、methods

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

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

    比如

    组件的 props:{ name:""}
    
    组件mixin 的 props:{ name:"", age: "" }

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

    strats.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
    
    };
    
    
    

    最后

    合并策略大概就这么多,初看有点复杂,其实很简单。就这几种。

    有错误的地方,欢迎更正。

    展开全文
  • vue options合并流程 格式化数据 格式化props为小驼峰命名的对象 格式化inject为{key : { ...合并策略: propsData props methods inject computed component directive filter相同key优先child的值 如果data和provi
  • 主要介绍了Vue.extend 和 data的合并策略 ,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
  • 源码vue在实例化对象、vue子类声明的时候会对父实例和子实例的参数使用设定好的合并策略合并父、子实例的参数。以及实例化前期、数据绑定时均有使用到合并策略合并参数。 定义合并策略的js文件路径是:\vue-dev\src...

    源码vue在实例化对象、vue子类声明的时候会对父实例和子实例的参数使用设定好的合并策略合并父、子实例的参数。以及实例化前期、数据绑定时均有使用到合并策略合并参数。

    定义合并策略的js文件路径是:\vue-dev\src\core\util\options.js

    在合并策略中对不同类型的参数使用了不同的合并策略。例如:strat.data合并data、defaultStrat合并[el、propsData和name]、mergrHook 合并生命周期的钩子函数、mergeAssets合并[component、directives、filter]等。

    这些合并策略通过入口函数mergeOptions (parent, child, vm)中对合并参数对象中的不同属性进行合并策略选择。

     1 export function mergeOptions (
     2   parent: Object,
     3   child: Object,
     4   vm?: Component
     5 ): Object {
     6   if (process.env.NODE_ENV !== 'production') {
     7     checkComponents(child)
     8   }
     9 
    10   if (typeof child === 'function') {
    11     child = child.options
    12   }
    13 
    14   normalizeProps(child, vm)//格式化prop为基于对象的格式
    15   normalizeInject(child, vm)//格式化Inject为基于对象的格式
    16   normalizeDirectives(child)//格式化directives为对象的格式
    17   const extendsFrom = child.extends
    18   if (extendsFrom) {
    19     parent = mergeOptions(parent, extendsFrom, vm)
    20   }
    21   if (child.mixins) {
    22     for (let i = 0, l = child.mixins.length; i < l; i++) {
    23       parent = mergeOptions(parent, child.mixins[i], vm)
    24     }
    25   }
    26   const options = {}
    27   let key
    28   for (key in parent) {
    29     mergeField(key)
    30   }
    31   for (key in child) {
    32     if (!hasOwn(parent, key)) {
    33       mergeField(key)
    34     }
    35   }
    36   function mergeField (key) {
    37     const strat = strats[key] || defaultStrat
    38     options[key] = strat(parent[key], child[key], vm, key)
    39   }
    40   return options
    41 }

    从上面mergeField函数中可以看出,Strats绑定处理参数中的各种数据的方法,统一在入口方法mergeOptions中被调用。源码在定义strats的时的注释也做了相应的说明,如下:

    1 /**
    2  * Option overwriting strategies are functions that handle
    3  * how to merge a parent option value and a child option
    4  * value into the final value.
    5  */
    6 const strats = config.optionMergeStrategies
    1. 合并生命周期的钩子函数和props参数的方法为mergeHook
     1 export const LIFECYCLE_HOOKS = [
     2   'beforeCreate',
     3   'created',
     4   'beforeMount',
     5   'mounted',
     6   'beforeUpdate',
     7   'updated',
     8   'beforeDestroy',
     9   'destroyed',
    10   'activated',
    11   'deactivated',
    12   'errorCaptured'
    13 ]
    14 LIFECYCLE_HOOKS.forEach(hook => { 15 strats[hook] = mergeHook 16 })

     mergeHook方法实现思路及源码如下:

     

     用人话总结这个合并规则就是:只有父时返回父,只有子时返回数组类型的子。父、子都存在时,将子添加在父的后面返回组合而成的数组。这也是父子均有钩子函数的时候,先执行父的后执行子的的原因。源码如下:

     1 /**
     2  * Hooks and props are merged as arrays.
     3  */
     4 function mergeHook (
     5   parentVal: ?Array<Function>,
     6   childVal: ?Function | ?Array<Function>
     7 ): ?Array<Function> {
     8   return childVal
     9     ? parentVal
    10       ? parentVal.concat(childVal)
    11       : Array.isArray(childVal)
    12         ? childVal
    13         : [childVal]
    14     : parentVal
    15 }

    2.strats.data合并data数据,代码逻辑如下:

     

    源码如下:

     1 strats.data = function (
     2   parentVal: any,
     3   childVal: any,
     4   vm?: Component
     5 ): ?Function {
     6   if (!vm) {
     7     if (childVal && typeof childVal !== 'function') {
     8       process.env.NODE_ENV !== 'production' && warn(
     9         'The "data" option should be a function ' +
    10         'that returns a per-instance value in component ' +
    11         'definitions.',
    12         vm
    13       )
    14 
    15       return parentVal
    16     }
    17     return mergeDataOrFn.call(this, parentVal, childVal)
    18   }
    19 
    20   return mergeDataOrFn(parentVal, childVal, vm)
    21 }

      由源码最后可知无论是vm存在与否最后都调用了mergeDataOrFn函数。这个函数根据vm是否存在,对parentVal和childVal做出不同的处理。但是无论vm存在不存在最终都会调用mergeData函数,将parentVal和childVal合并成最终值。所以介绍mergeDataOrFn函数之前先介绍mergeData这个函数。源码如下:

     1 function mergeData (to: Object, from: ?Object): Object {
     2   if (!from) return to
     3   let key, toVal, fromVal
     4   const keys = Object.keys(from)
     5   for (let i = 0; i < keys.length; i++) {
     6     key = keys[i]
     7     toVal = to[key]
     8     fromVal = from[key]
     9     if (!hasOwn(to, key)) {
    10       set(to, key, fromVal)
    11     } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
    12       mergeData(toVal, fromVal)
    13     }
    14   }
    15   return to
    16 }

      用人话总结这个合并规则就是:

        1.如果from【childVal】中的某个属性to【parentVal】中也有,保留to中的,什么也不做

        2.如果to中没有,将这个属性添加到to中

        3.如果to和from中的某个属性值都是对象,则递归调用,进行深度合并。

      无论vm存在不存在mergeDataOrFn最终都会调用mergeData函数,将parentVal和childVal合并成最终值。那么接下来看mergeDataOrFn中对parentVal和childVal做了什么处理。

      逻辑图如下:

      源码如下:

     1 export function mergeDataOrFn (
     2   parentVal: any,
     3   childVal: any,
     4   vm?: Component
     5 ): ?Function {
     6   if (!vm) {
     7     // in a Vue.extend merge, both should be functions
     8     if (!childVal) {
     9       return parentVal
    10     }
    11     if (!parentVal) {
    12       return childVal
    13     }
    14     // when parentVal & childVal are both present,
    15     // we need to return a function that returns the
    16     // merged result of both functions... no need to
    17     // check if parentVal is a function here because
    18     // it has to be a function to pass previous merges.
    19     return function mergedDataFn () {
    20       return mergeData(
    21         typeof childVal === 'function' ? childVal.call(this) : childVal,
    22         typeof parentVal === 'function' ? parentVal.call(this) : parentVal
    23       )
    24     }
    25   } else if (parentVal || childVal) {
    26     return function mergedInstanceDataFn () {
    27       // instance merge
    28       const instanceData = typeof childVal === 'function'
    29         ? childVal.call(vm)
    30         : childVal
    31       const defaultData = typeof parentVal === 'function'
    32         ? parentVal.call(vm)
    33         : parentVal
    34       if (instanceData) {
    35         return mergeData(instanceData, defaultData)
    36       } else {
    37         return defaultData
    38       }
    39     }
    40   }
    41 }

     3.strats.provide = mergeDataOrFn。provide使用mergeDataOrFn进行合并

    4.strats.watch源码如下:

     1 /**
     2  * Watchers.
     3  *
     4  * Watchers hashes should not overwrite one
     5  * another, so we merge them as arrays.
     6  */
     7 strats.watch = function (
     8   parentVal: ?Object,
     9   childVal: ?Object,
    10   vm?: Component,
    11   key: string
    12 ): ?Object {
    13   // work around Firefox's Object.prototype.watch...
    14   if (parentVal === nativeWatch) parentVal = undefined
    15   if (childVal === nativeWatch) childVal = undefined
    16   /* istanbul ignore if */
    17   if (!childVal) return Object.create(parentVal || null)
    18   if (process.env.NODE_ENV !== 'production') {
    19     assertObjectType(key, childVal, vm)
    20   }
    21   if (!parentVal) return childVal
    22   const ret = {}
    23   extend(ret, parentVal)
    24   for (const key in childVal) {
    25     let parent = ret[key]
    26     const child = childVal[key]
    27     if (parent && !Array.isArray(parent)) {
    28       parent = [parent]
    29     }
    30     ret[key] = parent
    31       ? parent.concat(child)
    32       : Array.isArray(child) ? child : [child]
    33   }
    34   return ret
    35 }

      注释里说了:watchers不应该重写,应该保存在一个数组里。这就是watch数据合并的策略核心。

        1.定义ret并让ret获得parentVal的所有属性。

        2.遍历 childVal的所有属性,如果ret(即parentVal)中也有的话,就把ret的属性值弄成一个数组,把childVal的同名属性值放在ret同名值得后面。如果不存在就把childVal弄成一个数组。

        3.最后都将数组的值赋值给ret,拓展ret的属性和属性值。

      这个策略其实就是,子组件、父组件都存在的时候,把watch相同值得方法放在一个数组里,父前子后。

    5.strats.component、strats.directive、strats.filter源码如下:

     1 export const ASSET_TYPES = [
     2   'component',
     3   'directive',
     4   'filter'
     5 ]
     6 /**
     7  * Assets
     8  *
     9  * When a vm is present (instance creation), we need to do
    10  * a three-way merge between constructor options, instance
    11  * options and parent options.
    12  */
    13 function mergeAssets (
    14   parentVal: ?Object,
    15   childVal: ?Object,
    16   vm?: Component,
    17   key: string
    18 ): Object {
    19   const res = Object.create(parentVal || null)
    20   if (childVal) {
    21     process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    22     return extend(res, childVal)
    23   } else {
    24     return res
    25   }
    26 }
    27 
    28 ASSET_TYPES.forEach(function (type) {
    29   strats[type + 's'] = mergeAssets
    30 })

      这个合并策略的核心就是:将childVal的全部属性通过原型委托在parentVal上。parentVal成为了childVal的原型对象。

      所以需要查找某个component、directive、filter,首先会在childVal中查找,如果没有就在其原型对象上查找。

      即子组件有就用子组件的,子组件没有向上在父组件中寻找。

    6.strats.props 、strats.methods 、strats.inject 、strats.computed源码如下:

     1 /**
     2  * Other object hashes.
     3  */
     4 strats.props =
     5 strats.methods =
     6 strats.inject =
     7 strats.computed = function (
     8   parentVal: ?Object,
     9   childVal: ?Object,
    10   vm?: Component,
    11   key: string
    12 ): ?Object {
    13   if (childVal && process.env.NODE_ENV !== 'production') {
    14     assertObjectType(key, childVal, vm)
    15   }
    16   if (!parentVal) return childVal
    17   const ret = Object.create(null)
    18   extend(ret, parentVal)
    19   if (childVal) extend(ret, childVal)
    20   return ret
    21 }

      这种合并策略的特点就是子会覆盖父。

        1.先将parentVal的所有属性扩展给res

        2.再将childVal的所有属性扩展给res。此时,若是parentVal和childVal拥有同名属性的话,子的属性就会覆盖父的。也就是同名方法只会执行子的。

    7.其他的属性使用的就是默认合并策略:defaultStrat。源码如下:

    1 /**
    2  * Default strategy.
    3  */
    4 const defaultStrat = function (parentVal: any, childVal: any): any {
    5   return childVal === undefined
    6     ? parentVal
    7     : childVal
    8 }

      默认策略就是:子组件的选项不存在,才会使用父组件的选项,如果子组件的选项存在,使用子组件自身的。

      因为是不会对parentVal和childVal进行分解的。所以默认策略一般用于合并比较简单,不包含函数的属性,例如el。

    1 const defaultStrat = function (parentVal: any, childVal: any): any {
    2 return childVal === undefined
    3 ? parentVal
    4 : childVal
    5 }

     

    注:本文章中学习的源码版本为vue  2.5.2. 文章中涉及的观点和理解均是个人的理解,如有偏颇或是错误还请大神指出,不吝赐教,万分感谢~

     

     

    转载于:https://www.cnblogs.com/mlFronter/p/7718600.html

    展开全文
  • 全局mixin 全局的mixin影响到所有页面及组件的...Vue.mixin({ data() { return { name: "tgc" } }, created() { console.log('这是全局mixin') }, methods: { foo() { console.log('这是一个全局方法') ...
  • 最近为了给自己充电,在腾讯课堂报了一个vue源码学习的课程,跟着课堂上的老师学习vue源码以此来增加自己对于vue的理解。这篇文章主要是记录一下自己学习vue源码时,学习到了什么,并把自己对vue源码的理解通过语音...
  • * 上一节深入剖析Vue源码 - 选项合并(上)的末尾,我们介绍了Vue中处理合并选项的思路,概括起来主要有两点,一是当选项存在定义好的默认配置策略时,优先选择默认配置策略,并且根据不同的配置项来合并子父选项;...
  • 这主要与Vue中的选项合并策略有关,不同的选项有不同的合并策略。 例如data、props、methods都是同属性直接覆盖合并;像created、mounted等生命周期函数都是直接合并,同名的函数存放在一个数组中,依次进行调用。 ...
  • 这篇重点学习strat realizes目录下的方法,主要就是学习Vue合并策略 首先推荐两位大神的文章,讲解的十分到位 vue2.0源码解读之选项合并策略 optionMergeStrategies vue源码学习–合并策略对象mergeOptions ...
  • 我们 之前 谈 学习vue源码(5) 手写Vue.use、...如图所示这次我们就来深入研究下,因此也就离不开Vue中的一个重要思想:合并策略了。我们有时面试时可能会遇到这样的问题:引入的mixin的data中 有 name这个属性,自...
  • 这次我们就来深入研究下,因此也就离不开Vue中的一个重要思想:合并策略了。 我们有时面试时可能会遇到这样的问题: 引入的mixin的data中 有 name这个属性,自己注册的组件的data中也有name,那哪个会生效? 引入的...
  • 一个混入对象可以包含任意的组件选项,可根据需求随意“封装”组件的可复用单元,并在使用时根据一定的策略合并到组件的选项当中,使用时与组件自身选项无异。 官方文档对mixin介绍比较少,不能了解甚少,于是便想...
  • 生命周期的钩子:合并为数组,执行有先后顺序   watch:合并为数组,执行有先后顺序   assets(components、filters、directives):合并为原型链式结构   其他,基本上就是数据的合并,data为function,...

空空如也

1 2 3 4 5 ... 8
收藏数 155
精华内容 62
关键字:

vue合并策略

vue 订阅