美文网首页
小白也能秒懂Vue源码中那些精细设计(选项处理)

小白也能秒懂Vue源码中那些精细设计(选项处理)

作者: 前端老司机 | 来源:发表于2020-06-08 21:24 被阅读0次

    我"崩"不住了,在彭凡同志锲而不舍的催促下这篇文章终于"蛋"生了。 说正经的这篇文章不好写,不好写的原因是我不太擅长写这些类比文,但它还是写出来了。 相信大部分人都有开发过功能插件,在写插件的时候普遍应用基本思想是以"默认配置为优先,以用户配置为覆盖"。如果你觉得简单先别着急穿裤子走人继续往下看看。

    Validator插件

    $("form").Validator();
    
    

    之前写过一个轻量级数据校验插件使用非常简单,你只需要找到form表单节点调用调用Validator 方法即可,就能在文本框中输入值进行自定校验。


    $("form").Validator({
        initEvent: "change", //自定义校验事件
        password: "* 密码必须是6-12个字符且包含大小写字母" //自定义密码校验失败错误信息
    });
    
    

    当然也会给用户相对的自由度如"校验事件、错误提示信息 ..."你只需要按照规定来配置就好了。

    *********************分割线**********************

    在写Vue代码的小伙伴同样要写很多的选项:"el、data、props、template、render、mounted..." 那Vue在处理这些选项也是使用"非黑即白"的处理思想吗?明确的说没有 Vue 在处理选项有非常多限制如:

    • el 只在用 new 创建实例时生效。
    • data 组件的定义只接受function。
    • 直接在 DOM中使用组件时,组件名只有是 kebab-case 才有效。
    • 生命周期钩子...

    也就是说在Vue 中"非黑即白"的思想并不适用,Vue需要针对特殊选项做不同的处理,有的选项处理逻辑是再此能不能用,有的选项处理逻辑是校验Value合法性,有的选项的逻辑是需要合并处理。.... 这种处理方式比较官方的说法叫"选项自定义策略处理"。

    选项自定义策略处理

    在讲选择自定义策略处理之前先说说vm.$option实例属性,它是用于当前 Vue 实例的初始化选项。需要在选项中包含自定义属性时会有用处:

    var vm = new Vue({
        el: "#app",
        data: {
            message: "hello Vue",
        },
        count: 9,
    })
    
    

    输出vm.$option:

    {
        el: "#app",
        data: function mergedInstanceDataFn(){},
        count: 9
    }
    
    

    有没有感觉奇怪在实例初始化选项中data的值是一个对象,为什么vm.$option中data的值变成一个函数了?开始揭秘...

    Vue构造函数

    function Vue(options) {
        if (!(this instanceof Vue)) {
            warn('Vue is a constructor and should be called with the `new` keyword');
        }
        this._init(options);
    }
    
    

    在创建Vue实例的时候你传递进来的自定义选项对象会传递给this._init这个方法。

    Vue.prototype._init = function(options) {
        var vm = this;
        //省略...
        vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
        );
        //省略...
    }
    
    

    vm.options 属性定义在Vue.prototype._init 原型方法中。 vm.options的值是调用mergeOptions函数的返回值。

    mergeOptions

    function mergeOptions(parent, child, vm) {
        //省略...
        var options = {};
        var key;
        for (key in parent) {
            mergeField(key);
        }
        for (key in child) {
            if (!hasOwn(parent, key)) {
                mergeField(key);
            }
        }
    
        function mergeField(key) {
            var strat = strats[key] || defaultStrat;
            options[key] = strat(parent[key], child[key], vm, key);
        }
        return options
    }
    
    

    先来看看调用mergeOptions的三个参数。

    • resolveConstructorOptions - 这个函数的作用是用来获取当前实例构造者的 options 属性(注:涉及到组件相关的内容暂时不解释,在此你可以默认他传递是一个纯对象)
    • options - 自定义选项对象
    • vm - Vue实例

    mergeOptions 最终返回的是在函数内置的options纯对象。 options 所拥有的属性就是调用mergeField函数传递进来的key。

    举个栗子:

    你在创建Vue的根实例,并且传递了一个自定义选项对象。

    var vm = new Vue({
        el: "#app",
        data: {
            message: "hello Vue",
        },
        count: 9,
    })
    
    

    自定义选项对象会作为实参传递给mergeOptions函数的child形参。

    for (key in child) {
        if (!hasOwn(parent, key)) {
            mergeField(key);
        }
    }
    
    

    通过for..in.. 语句把child对象上可枚举的属性名作为参数传递给mergeField。

    hasOwn是检测关于组件中父实例中是否key属性如果有将不会重复的调用mergeField,因为父子组件实例中相同的属性只需要做一次策略处理就可以了。(注:不扩展讲解)

    当前栗子中"el"、"data"、"count" 这三个属性名作为字符串会作为参数传递给mergeField函数。那在mergeField函数中会给options 扩展 options.el = undefined 、 options.data = undefined 、 options.count = undefined 三个属性,为什么这三个属性的值都是undefined的呢?

    原因是他们的value都需要通过调用 strat(parent[key], child[key], vm, key) 函数返回值来确定所以都暂定undefined,那strat 是什么?

    var strat = strats[key] || defaultStrat;
    
    

    strats是自定义策略对象,strats[key]是检测在这个自定义策略对象上有没有[key]这个属性,如果有就表示针对[key]属性写了策略函数反之就没写。

    defaultStrat 默认策略函数

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

    默认策略函数要弄懂它你只需要明白parentVal 、childVal 这两个参数。

    • parentVal 就是在调用strat 传递进来的parent[key],因为我们默认parent为纯函数所以parentVal 永远为undefined。
    • childVal 就是在调用strat 传递进来的childVal[key],也就是自定义选项对象中的[key]属性的值。

    strats 自定义策略对象

    var strats = config.optionMergeStrategies;
    
    

    strats 是获取了config 全局配置对象上optionMergeStrategies属性的值。

    var config = {
        optionMergeStrategies: Object.create(null)
        //省略...
    }
    
    

    你是不是觉得奇怪为什么不直接通过字面量方式创建一个纯对象赋值给strats,而要通过Object.create(null)创建纯对象。这样不麻烦了吗? 问题暂时保留。往下看Vue中自定义的策略函数。

    strats.el = strats.propsData = function(parent, child, vm, key) {
        if (!vm) {
            warn(
                "option \"" + key + "\" can only be used during instance " +
                'creation with the `new` keyword.'
            );
        }
        return defaultStrat(parent, child)
    };
    
    strats.data = function(parentVal, childVal, vm) {
        if (!vm) {
            if (childVal && typeof childVal !== 'function') {
                warn(
                    'The "data" option should be a function ' +
                    'that returns a per-instance value in component ' +
                    'definitions.',
                    vm
                );
    
                return parentVal
            }
            return mergeDataOrFn(parentVal, childVal)
        }
    
        return mergeDataOrFn(parentVal, childVal, vm)
    };
    
    //省略...
    
    

    可以看到Vue中对"el", "data","watch","props"等等....选项都写了策略函数。 在回归到mergeField函数你是否能顿悟了。

    function mergeField(key) {
        var strat = strats[key] || defaultStrat;
        options[key] = strat(parent[key], child[key], vm, key);
    }
    
    

    如果某个选项写了策略函数那么就会调用这个策略函数,返回值会成为options[key] 的value。最后:mergeOptions 函数执行完毕返回options引用给到vm.$options。

    现在来解释刚刚的那个问题。为什么不直接通过字面量方式创建一个纯对象赋值给strats,而要通过Object.create(null)创建纯对象?原因是Vue想给用户自定义选项自由度,也能添加策略函数。

    举个栗子:

    你在创建Vue的根实例,并且传递了一个自定义选项对象。

    var vm = new Vue({
        el: "#app",
        data: {
            message: "hello Vue",
        },
        count: 9,
    })
    

    我想针对count写个添加策略函数怎么办。

    Vue.config.optionMergeStrategies._my_option = function(parentVal, childVal, vm) {
        return childVal >= 99 ? childVal : 99
    }
    
    

    这个策略函数很简单,监听childVal的值: 如果大于99返回本身,小于99 返回99。count策略函数返回值给到vm.$options.count。

    那Vue.config是怎么访问到的呢?

    function initGlobalAPI(Vue) {
        // config
        var configDef = {};
        configDef.get = function() {
            return config;
        };
        configDef.set = function() {
            warn(
                'Do not replace the Vue.config object, set individual fields instead.'
            );
        };
    
        Object.defineProperty(Vue, 'config', configDef);
    }
    initGlobalAPI(Vue);
    
    

    你细品这个代码。 看不懂留言请相信我一定会假装看不见。

    推荐:

    • 020 持续更新,精品小圈子每日都有新内容,干货浓度极高。
    • 结实人脉、讨论技术 你想要的这里都有!
    • 抢先入群,跑赢同龄人!(入群无需任何费用)
    • 群号:779186871
    • 点击此处,与前端开发大牛一起交流学习

    申请即送:

    • BAT大厂面试题、独家面试工具包,

    • 资料免费领取,包括 各类面试题以及答案整理,各大厂面试真题分享!

    相关文章

      网友评论

          本文标题:小白也能秒懂Vue源码中那些精细设计(选项处理)

          本文链接:https://www.haomeiwen.com/subject/ksmdtktx.html