【Vue原理】Mixins - 源码版

代码拓霓人
• 阅读 2938
写文章不容易,点个赞呗兄弟
专注 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原理】Mixins - 源码版

全局选项就变成下面这样,然后每个Vue实例都需要和这全局选项合并
【Vue原理】Mixins - 源码版

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

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

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

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

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

【Vue原理】Mixins - 源码版


怎么合并

上面的代码一直出现一个函数 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
}

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

【Vue原理】Mixins - 源码版

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

};

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

【Vue原理】Mixins - 源码版

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
4年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这