前端开发系列115-进阶篇之对象和数组的读写劫持

本文讨论如何监听对象中所有属性的读和写操作,以及对于数组的劫持特殊处理,本文将从侧面来介绍 Vue2.X版本中响应式数据监听的原理。本文将用到 [Object.defineProperty]()方法,该方法以及[getter 和 setter] 方法的具体使用方式,可以参考另一篇博客文章。
对象劫持
    function isObject(o) {
       return typeof o === "object" && o != null;
   }
   /*核心函数:通过Object.defineProperty方法实现劫持*/
   function defineReactive(data, key, value) {
       /* 递归调用:解决value也是对象的情况 */
       observe(value);
       Object.defineProperty(data, key, {
           get() {
               console.log(`${key}--读`)
               return value;
           },
           set(newValue) {
               console.log(`${key}--写`)
                   /* 如果数据的值没有改变那么就直接返回 */
               if (value === newValue) return;
               /* 如果设置的新数据是对象,那么也应该进行监听 */
               observe(newValue);
               value = newValue;
           }
       })
   }
   /* Observer 类(构造函数) */
   class Observer {
       constructor(val) {
           this.walk(val)
       }
       walk(data) {
           /* 获取当前对象所有可枚举的 key */
           let keys = Object.keys(data);

           /* 遍历所有的 keys,通过 Object.defineProperty 给所有的 key 都添加 getter和 setter */
           keys.forEach(key => defineReactive(data, key, data[key]));
       }
   }

   function observe(o) {
       if (!isObject(o)) return; /* 排除对象的情况 */
       return new Observer(o); /* 获取Observer的实例 */
   }

   /* 测试数据 */
   let data = {
       person: {
           name: "zs",
           age: 18
       }
   };
   observe(data);

上面代码同的核心方法是defineReactive函数,在该函数的内部我们通过Object.defineProperty方法实现了对对象中属性的读(get)和写(set)操作的监听。Observer类用于构建 observe实例对象,该实例的walk方法通过遍历的方式为对象中所有的属性都实现了getter 和 setter 方法

接下来,我们给出一组测试数据并贴出对应的显示结果。

通过对代码的研究和对数据的测试,我们验证了上面代码基本上能够完成对对象数据读写操作的监听,但仍然存在一些不足。

① 如果是新增加属性,那么则无法监听。
② 如果是数组的结构,那么也无法监听。

数组劫持
 function isObject(o) {
    return typeof o === "object" && o != null;
  }

function defineReactive(data, key, value) {
    /* 递归调用:解决value也是对象的情况 */
    observe(value);
    Object.defineProperty(data, key, {
        get() {
            console.log(`${key}--读`)
            return value;
        },
        set(newValue) {
            console.log(`${key}--写`)
                /* 如果数据的值没有改变那么就直接返回 */
            if (value === newValue) return;
            /* 如果设置的新数据是对象,那么也应该进行监听 */
            observe(newValue);
            value = newValue;
        }
    })
}

/* 获取数组原型的方法 */
let oldArrayMethods = Array.prototype;
/* 把oldArrayMethods作为原型对象创建一个新的空的对象 */
let newArrayMethods = Object.create(oldArrayMethods);
/* 整理数组中需要重写的方法 */
let methods = ["pop", "push", "shift", "unshift", "sort", "reverse", "splice"];
methods.forEach(method => {
    newArrayMethods[method] = function(...args) {
        let __ob__ = this.__ob__;
        let result = oldArrayMethods[method].apply(this, args);
        console.log(`监听到${method}方法`, this, args);

        /* 注意:新添加的数据可能是对象也需要监听读写操作 */
        /* 1.先获取新添加的数据参数 */
        let insetData;
        switch (method) {
            case "push":
            case "unshift":
                insetData = args;
                break;
            case "splice":
                insetData = args.slice(2);
            default:
                break;
        }
        console.log("insetData", insetData);

        /* 2.对新添加的数据进行监听 */
        if (insetData) __ob__.observerArr(insetData);

        return result;
    }
})

/* Observer 类(构造函数) */
class Observer {
    constructor(val) {
            /* 区分对象和数组的情况 */
        if (Array.isArray(val)) {
                /* 给当前的对象定义__ob__属性,该属性指向的是自己 */
            Object.defineProperty(val, "__ob__", {
                configurable: false,
                enumerable: false,
                value: this
            })
            /* 重写数组的原型方法,在这些重写的方法内部进行监听 */
            Reflect.setPrototypeOf(val, newArrayMethods);
            this.observerArr(val);
        } else {
            this.walk(val)
        }
    }
    walk(data) {
        /* 获取当前对象所有可枚举的 key */
        let keys = Object.keys(data);

        /* 遍历所有的 keys,通过 Object.defineProperty 给所有的 key 都添加 getter和 setter */
        keys.forEach(key => defineReactive(data, key, data[key]));
    }
    observerArr(arr) {
        arr.forEach(item => observe(item));
    }
}

function observe(o) {
    if (!isObject(o)) return; /* 排除对象的情况 */
    return new Observer(o); /* 获取Observer的实例 */
}

/* 测试数据 */
let data = {
    person: {
        name: "zs",
        age: 18
    },
    friends: [{
        name: "佩琪",
        age: 3
    }, {
        name: "巧虎",
        age: 5
    }]
};
observe(data);

根据测试数据贴出对应的显示结果如下所示。

上述代码完成了对数组数据的读写监听(劫持),但仍然存在一些无法处理的情况,下面简单列出。

问题1:如果我们通过数组的下标来访问和修改数据,那么无法监听。
问题2:如果我们通过数组的 length 属性来操作(删除)数组,那么也无法监听。

上面的这些问题在 Vue 2.X 版本中主要通过 Vue.set() 或者是 vm.$set() 来进行实现。

posted on 2022-12-17 13:31  文顶顶  阅读(29)  评论(0编辑  收藏  举报

导航