浅谈Vue2和Vue3的数据响应

一、数据响应是什么?

“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。

二、Vue2中如何实现的呢?

在Vue2中使用的是Object.defineProperty实现的数据劫持响应。

/**
 * 使用 Object.defineProperty 测试响应式
 */
(function () {
    const data = {
        name: "admin",
        age: 10,
        info: {
            address: "上海" // 深度监听
        },
        friends: ["西奥", '(*^_^*)', '吃饭'] // 监听数据的测试数据
    }

    Object.defineProperty

    // 开启对数组的监听
    // 复制数组的原型链,这里是为了不污染原有的原型链
    const sourceArrayProto = Array.prototype;
    const customArrayProto = Object.create(sourceArrayProto);

    // 重写数组的基本方法
    ['push', 'shift', 'pop', 'unshift', 'splice'].forEach(methodName => {
        customArrayProto[methodName] = function () {
            // 修改原数组的数据,这里需要请求源数据原型链的支持
            sourceArrayProto[methodName].call(this, ...arguments);
            // 通知更新
            updateView();
        }
    })


    // 更新视图
    function updateView() {
        console.log("update data");
    }

    // 重新定义属性,监听对象的每一个属性
    function defineReactive(target, key, value) {

        Object.defineProperty(target, key, {
            get() {
                // fix 开启深度监听
                observer(value);
                return value;
            },
            set(newVal) {
                if (value !== newVal) {
                    // fix 开启深度监听
                    observer(value);
                    // 注意,value一直都是闭包,此处设置完成后,在get到value
                    value = newVal;
                    updateView();
                }
            }
        })
    }

    function observer(target) {
        // 不是对象或数组
        if (typeof target !== "object" || target === null) {
            return target;
        }

        // 针对数组特殊处理,因为 Object.defineProperty 无法监听数组
        if (Array.isArray(target)) {
            // 修改成自定义的原型链,达到监听数组变化的目的
            target.__proto__ = customArrayProto;
            return;
        }

        // 重新定义各个属性
        for (let key in target) {
            defineReactive(target, key, target[key]);
        }
    }

    // 监听数据的响应式
    observer(data);

    // 修改数据
    // 简单对象
    // data.name = "jack";
    // data.age = 52;

    // 深度监听
    // data.info.address = "上海市"

    // 修改数组数据,完成数组的监听
    data.friends.push("卡哇伊");

    console.log(data);

})();

三、Vue3中如何实现的呢?

在Vue3中使用的是Proxy实现的代理数据响应。

(function () {

    const data = {
        name: "admin",
        age: 10,
        info: {
            address: "上海" // 深度监听
        },
        friends: ["西奥", '(*^_^*)', '吃饭'] // 监听数据的测试数据
    }

    function reactive(target) {

        // 不是对象或数组,则返回
        if (typeof target !== "object" || target === null) {
            return target;
        }

        const proxyConfig = {
            get(target, key, receiver) {
                const result = Reflect.get(target, key, receiver);
                console.log('get', key);
                console.log('result', result);

                // 深度监听
                return reactive(result);
            },
            set(target, key, value, receiver) {
                // 重复数据,不处理,提高性能
                if (value === target[key]) {
                    return true;
                }
                const result = Reflect.set(target, key, value, receiver);
                console.log('set', key, value);
                console.log('result', result);
                return result;
            },
            deleteProperty(target, key) {
                const result = Reflect.deleteProperty(target, key);
                console.log('delete', key);
                console.log('result', result);
                return result;
            }
        }

        const observed = new Proxy(target, proxyConfig);
        return observed;
    }

    const proxyData = reactive(data);

    // 修改数据
    // 简单对象
    // proxyData.name = "jack";
    // proxyData.age = 52;

    // 深度监听
    proxyData.info.address = "上海市"

    // 修改数组数据,完成数组的监听
    // proxyData.friends.push("卡哇伊");

    // console.log(proxyData.name);

    console.log(proxyData);

})();

总结

无论是Vue2的Object.defineProperty,还是Vue3的Proxy,皆有自己的优点和缺点

Vue2的优点就是胜在兼容性强,能兼容ie。
至于缺点也不少,使用Object.defineProperty 进行监听数据变化,如果需要深度监听,需要递归到底,一次性计算量大,并且无法监听新增属性/删除属性,这也是为什么Vue会提供Vue.deleteVue.set的原因。而且使用Object.defineProperty还有一个致命的缺点,它无法原生监听数组,需要特殊处理,这个可以参考上面的案列。


Vue3最大的优点就是Proxy能规避Object.defineProperty 的问题,它能监听数组变化、新增/删除 属性,也可以深度监听,性能更好(什么时候用到,什么时候监听)
但是Vue3的Proxy也有一个致命的缺点,完全放弃对ie的兼容性,这也是为什么Vue3又推出了一个版本,针对ie的兼容版(使用Object.defineProperty

原创不易,转载请标名出处

上一篇:【Vue学习记录 3 】Webpack、Vue2框架介绍、router路由


下一篇:vue2 - composition-api(未完)