Vue原理:数据代理

1,359 阅读2分钟

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

简单总结一下前面三篇内容:数据初始化并对其进行数据劫持,对象和数组都进行了处理

利用此时的Vue进行实例化

let vm = new Vue({
    data() {
        return {
            name: 'nordon',
            age: 12
        }
    }
});

此时想要获取到data中的数据,需要借助_data获取,因为data并不是直接传递的对象,而是一个函数,此时是不能直接通过$options获取

vm._data.name;

这种获取方式明显是不够简洁方便的,期望的是可以直接通过vm.name获取

只需要在数据初始化的时候将数据进行一层代理即可,即vm.name等价于vm._data.name

proxy函数实现

function proxy(vm, source, key) {
    Object.defineProperty(vm, key, {
        get() {
            return vm[source][key];
        },
        set(newVal) {
            vm[source][key] = newVal;
        },
    });
}

src/state.js文件中找到initData函数

function initData(vm) {
    // 数据初始化工作
    let data = vm.$options.data;

    // 对data进行处理
    data = vm._data = typeof data === "function" ? data.call(vm) : data;

    // 为了让用户更好的使用,直接vm.xxx 取值
    for (const key in data) {
        proxy(vm, "_data", key);
    }

    // 通过 Objet.defineProperty() 为属性增加get和set方法
    observe(data);
}

此时可以正常使用,但是上面的代码却有一个比较严重的隐患,使用过Vue的朋友都知道,在Vue中不仅可以直接通过vm获取到data的数据,还可以获取到propsmethods等的数据,因此data在代理数据时,需要注意命名冲突的发生

在解决问题之前,先看看两个工具小方法,后面会使用到

检测对象obj是否存在属性key

const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn (obj, key) {
    return hasOwnProperty.call(obj, key)
}

检测字符串str 是否已 $、_ 开始的,因为Vue内部的属性和方法常常这么命名,不建议开发者在使用的过程中这么命名

export function isReserved (str) {
    const c = (str + '').charCodeAt(0)
    return c === 0x24 || c === 0x5F
}

此时对initData函数代理部分进行重写,最终为

function initData(vm) {
    // 数据初始化工作
    let data = vm.$options.data;

    // 对data进行处理
    data = vm._data = typeof data === "function" ? data.call(vm) : data;

    // 新增代码 开始
    const keys = Object.keys(data);
    const props = vm.$options.props;
    const methods = vm.$options.methods;
    let i = keys.length;

    while (i--) {
        const key = keys[i];

        // methods中存在了相同的key, 将会被data中的数据覆盖
        if (methods && hasOwn(methods, key)) {
            console.warn(`方法 "${key}" 已经作为 "data" 属性存在`);
        }

        // props中存在相同的key,则不走代理的逻辑,直接使用props的数据
        if (props && hasOwn(props, key)) {
            console.warn(`"data" 的属性 "${key}" 已经在 "props"中声明,会被 "props"代替`);
        } else if (!isReserved(key)) {
            proxy(vm, `_data`, key);
        }
    }
    // 新增代码 结束

    // 通过 Objet.defineProperty() 为属性增加get和set方法
    observe(data);
}

需要注意:

props中定义的数据优先级高于datamethods中定义的函数优先级低于data,若是存在相同的key,最终通过vm.key获取到的是数据优先级依次为propsdatamethods,在开发过程中需要注定命名的冲突问题