Vue源码解析(二)、mergeOptions

369 阅读3分钟

紧接上文mergeOptions

回顾一下之前在哪里用到的mergeOptions

    Vue.prototype._init = function (options) {
        ...
        vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
        )
        ...
    }

先来看一下resolveConstructorOptions

    function resolveConstructorOptions (Ctor) {
        let options = Ctor.options
        if (Ctor.super) {
            ...
        }
        return options
    }

获取Ctor的options, 根据第一个代码片段我们可以得出Ctor目前是 Vue 构造函数。很显然Vue就已经是根实例了,我们现在不考虑有组件的情况,有组件存在,组件上就拥有super了, 因为他继承了Vue的根实例。不存在super,所以就直接返回Ctor.options,也就是Vue构造函数的options。

接下来是真正的重头戏, mergeOptions, 我们找到mergeOptions源码所在。 src/utils/options

mergeOptions
    /*
     *  Merge two option object into a new one. 合并两个option对象到一个新的对象中去
     *  Core utility used in both instantiation and inheritance. 用于实例化和继承的核心程序
    */
    // 对应上面传入的内容哈
    function mergeOptions (
        parent, // Vue.options => {components: {}, directives: {}, filters: {}}
        child, // options || {}
        vm // vm vue实例
    ) {
        normalizeProps(child, vm) // 格式化props
        normalizeInject(child, vm) // 格式化inject
        normalizeDirectives(child, vm) // 格式化指令 这些都不是我们研究的重点
        if (!child._base) {
            if (child.extends) {
                parent = mergeOptions(parent, child.extends, vm)
            }
            if (child.mixins) {
                for (let i = 0; l = child.mixins.length; i < l; i++) {
                    parent = mergeOptions(parent, child.mixins[i], vm)
                }
            }
        } // 判断有没有extends mixins 如果有就递归调用mergeOptions 将他们合并到parent上。
        const options = {} // 创建一个新对象,用来存储parent和child的合并结果
        let key
        for (key in parent) {
            mergeField(key)
        }
        for (key in child) {
            if (!hasOwn(parent, key)) {
                mergeField(key)
            }
        }
        function mergeField (key) {
            const strat = starts[key] || defaultStarat
            options[key] = start(parent[key], child[key], vm, key)
        }
        return options
    }

讲道理合并过程还是真挺繁琐的,因为涉及到data、computed、methods、watch、components、filters、mixins、extends、directives、生命周期等的合并,这里我们挑几个做讲解就好,其他的可以到src/utils/options中查看,不难。 拿合并data、生命周期两个例子来说吧。

mergeOptions之合并data
    // data的合并
    strats.data = function (
        parentVal,
        childVal,
        vm
    ) {
        if (!vm) {
            if (childVal && typeof childVal !== "function") {
                process.env.NODE_ENV !== 'production' && warn(
                    'The "data" option shoule 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的时候出的错了, 知道为什么data必须是一个函数的报错提醒原来在这里。哇哦!surprise。注意这里data必须是一个函数,指的是组件中的dataVue根实例既可以是一个简单的对象也可以是一个函数。 废话不多说,直接看mergeDataOrFn这个函数

    function mergeDataOrFn(
        parentVal,
        childVal,
        vm
    ) {
           // ... 这里先不考虑有组件情况。
            return function mergeInstanceDataFn () {
                const instanceData = typeof childVal = "function"
                    ? childVal.call(vm, vm)
                    : childVal
                const defaultData = typeof parentVal = "function"
                    ? parentVal.call(vm, vm)
                    : parentVal
                if (instanceData) {
                    return mergeData(instanceData, defaultData)
                } else {
                    return defaultData
                }
            }
    
    }

原来如此, 我们最后合并data的操作, 最终返回了一个函数mergeInstanceDataFn, 这个函数用来将两者合并。问题来了, 这个函数在哪里用到呢? 后面介绍。data的合并过程就完事了。

mergeOptions之 合并生命周期

接着我们看一下合并生命周期

    LIFYCYCLE_HOOKS.forEach(hook => {
        strats[hook] = mergeHook
    })

LIFY_HOOKS的定义在 shared/constants.js

看一下mergeHook,理解为合并钩子,中式翻译,哈哈

    function mergeHook (
        parentVal,
        childVal
    ) {
        const res = childVal
            ? parentVal
                ? parentVal.concat(childVal)
                : Array.isArray(childVal)
                    ? childVal
                    : [childVal]
            : parentVal
        return res ? dedupeHooks(res) : res
    }

判断 childValparentVal都存在 就让parentVal.concat(childVal), 如果parentVal不存在,那就看childVal是否是数组,如果是直接返回,如果不是就把他变成数组,如果childVal不存在那就直接返回parentVal,然后赋值给res,最后看res是否存在,若不存在就返回res, 若存在就调用dedupeHooks,这里我们知道生命周期的钩子都是以数组的形式保存的。然后看一下dedupeHooks,去重钩子

    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
    }

这段代码就不解释了吧, 简单的数组去重操作。就介绍到这吧。把这写合并好了, 其实都是合并到之前我们在mergeOptions中的定义的那个新的options空对象,最后再将这个options对象返回回去。就完成了合并的工作。

    function mergeOptions (
        parentVal,
        childVal,
        vm
    ) {
        ...
        const options = {}
        ...
        return options 
    }

github地址: https://github.com/ComponentTY/vue-sourcecode 写得不好, 还望各位大佬指教。