最近在学习Vue,学习过Vue的小伙伴应该都会知道Vue在数据发生变化之后就会重新生成虚拟DOM,然后进行新旧虚拟DOM的对比,然后进行替换,页面就进而也就发生了改变。

那么Vue是怎么监测到数据变化呢,Vue底层也是基于Object.defineProperty这个api进行监听数据的变化的,不懂的小伙伴可以百度学习一下这个api,这个api可以监听某个对象某个属性值的变化。

但是数组对象是以下标的形式存取值的,那么想监测数组里某个下标的值的改变,就只能使用设计模式代理的思想,设计一个数组的代理对象,这个代理对象重写能改变原数组元素的方法,例如Array类中的这几个方法

  • push() //在数组尾部添加一个元素
  • pop() //删除数组的最后一个元素
  • shift() //删除数组第一个元素的
  • unshift() //在数组前面插入一个元素
  • splice() //删除数组指定范围的元素
  • sort() //对原数组进行排序
  • reverse() //对原数组进行反转
    在代理对象中的这些方法又调用父类Array的这些方法,并且可以加入了数据监听的功能。即实现了监听的功能,又不改变功能。下面这个是Array的代理类。
/**
 * 创建一个Array的代理类,后续的数组创建都使用这个代理类,才能实现数组值的监听
 */
class ArrayProxy extends Array {
    constructor(arraySize) {
        if (arraySize === undefined) {
            super();
        } else {
            super(arraySize);
        }
    }

    push(...items) {
        let newLength = super.push(...items);
        console.log("监测到了执行push方法")
        return newLength;
    }

    pop() {
        let popValue = super.pop();
        console.log("监测到了执行pop方法");
        return popValue;
    }

    shift() {
        let shiftValue = super.shift();
        console.log("监测到了执行shift方法");
        return shiftValue;
    }

    unshift(...items) {
        let newLength = super.unshift(...items);
        console.log("监测到了执行unshift方法");
        return newLength;
    }

    splice(start, deleteCount, ...items) {
        let removeValueArray = super.splice(start, deleteCount, ...items);
        console.log("监测到了执行splice方法");
        return removeValueArray;
    }

    sort(compareFunc) {
        super.sort(compareFunc);
        console.log("监测到了执行sort方法");
    }

    reverse() {
        let reversedArray = super.reverse();
        console.log("监测到了执行reverse方法");
        return reversedArray;
    }
}

下面就是数据代理类的实现,取名为Observer,实例化传入的是一个键值对的对象。里面主要是递归的判断每个子对象的类型,根据不同的类型(主要是对象、数组、普通类型),然后又给每个层级的数据设置他们的数据代理,主要依靠的就是Object.defineProperty这个api。

还实现了一个$set(key,value)方法,用于为已经创建好的数据代理对象注入新的属性以及他的值,这样注入的新属性也会被监听值的变化,下面是数据代理类Observer代码实现:

/**
 * 数据代理类
 */
class Observer {
    dataObj = null;

    constructor(dataObj) {
        this.dataObj = dataObj;
        const keys = Object.keys(dataObj);
        // 生成数据对象的数据代理
        this.generateListening(this, dataObj, keys);
    }

    /**
     * 动态的为数据代理对象添加新的数据,通过这个函数添加的数据也是可监测的
     * @param key 添加的属性名
     * @param value 这个属性的值
     */
    $set(target, key, value) {
        const obj = {
            [key]: value
        };
        const keys = Object.keys(obj);
        this.generateListening(target, obj, keys);
    }

    /**
     * 生成obj的数据代理
     * @param parent 数据代理的父级
     * @param obj 源数据对象
     * @param keys 数据对象的key数组
     */
    generateListening(parent, obj, keys) {
        // 遍历obj对象的键,然后判断这个子元素的类型做对应处理
        keys.forEach((key) => {
                // 是object类型但不是数组
                if (typeof obj[key] === 'object' && !(obj[key] instanceof Array)) {
                    // 创建数据代理
                    obj[key] = new Observer(obj[key]);
                    Object.defineProperty(parent, key, {
                        get() {
                            return obj[key];
                        },
                        set(newValue) {
                            console.log(key + '对象整体被赋值了')
                            obj[key] = new Observer(newValue);
                        }
                    })
                } else if (typeof obj[key] === 'object' && (obj[key] instanceof Array)) {
                    // 子对象是数组
                    let arr = new ArrayProxy();
                    obj[key].forEach((e, i) => {
                            // 对每个数组的元素进行判断
                            if (typeof obj[key][i] === 'object' && !(obj[key][i] instanceof Array)) {
                                // 数组元素是对象时
                                arr.push(new Observer(obj[key][i]));
                            } else if (typeof obj[key][i] === 'object' && (obj[key][i] instanceof Array)) {
                                // 数组元素还是一个数组时
                                let childArr = new ArrayProxy();
                                let childArrKeys = Object.keys(obj[key][i])
                                // 递归生成这个子数组的所有子孙的数据代理
                                this.generateListening(childArr, obj[key][i], childArrKeys);
                                arr.push(childArr);
                            } else {
                                // 数组元素是普通的数据类型时
                                arr.push(e);
                            }
                        }
                    );
                    // 把生成好的数据代理重新赋值给子元素
                    obj[key] = arr;
                    Object.defineProperty(parent, key, {
                        get() {
                            return obj[key];
                        },
                        set(newValue) {
                            console.log("数组" + key + "被整体赋值了");
                            const newValueKeys = Object.keys(newValue);
                            // 重新根据新数组生成数据代理
                            this.generateListening(obj[key], newValue, newValueKeys);
                        }
                    })
                } else {
                    if (parent instanceof Array) {
                        // 如果是数组里面的普通元素,直接push就可以
                        parent.push(obj[key]);
                    } else {
                        // 不是数组里面的普通元素,需要设置监听
                        Object.defineProperty(parent, key, {
                            get() {
                                return obj[key];
                            },
                            set(newValue) {
                                console.log(key + "被重新赋值了");
                                obj[key] = newValue;
                            }
                        });
                    }
                }
            }
        );
    }
}

下面模拟一个实例Vue的data数据对象,来使用Observer创建他的数据代理

// 准备数据,模拟创建Vue实例的data
const data = {
    schoolName: "云南民族大学",
    schoolAddress: "九龙池",
    numberPeople: 7000,
    library: {
        name: "学校图书馆",
    },
    classes: [
        {
            className: "1班", teacher: "张三",
            students: [
                {studentName: "学生1", hobby: ["吃饭", "睡觉", "打游戏"]},
                {studentName: "学生2", hobby: ["看书", "写作业", "跑步"]},
            ]
        },
        {className: "2班", teacher: "李四"},
        {className: "3班", teacher: "王五"},
    ],
    courses: ["计算机网络", "Java程序设计", "数据结构", "数据库设计"],
    a: [
        [1, 2, 3],
        [4, 5, 6],
        [{a: 9}, {b: 10}]
    ]
}
// 创建数据代理
let obs = _data = new Observer(data);
console.log(obs);

下面用浏览器的代码片段工具Script snippet执行上面的全部代码,查看控控制台打印的obj对象。可以看到第一层的属性都有对应的get和set方法

js如何一直监听session中属性值 js监听数据变化_vue.js

接下来看一下底层的对象有没有对应的get和set,可以从下面的图片看出来下面几级的属性都有对应的get和set方法的

js如何一直监听session中属性值 js监听数据变化_数据_02


接下来我们来更新library属性的值,就可以看到已经监测到属性值的变化了

js如何一直监听session中属性值 js监听数据变化_javascript_03


我们使用$set方法来为library属性添加新属性,看这个属性能不能被监听

js如何一直监听session中属性值 js监听数据变化_前端_04


从图片可以看到,通过$set方法添加的属性也是可以监听的。