原生js实现检测对象变化

首先,js中的属性分为俩种,一种是数据属性,一种是访问器属性。

    var data = {};
    data.name = '田二黑';

上面这种就是数据属性。当然和下面效果一样:

    Object.defineProperty(obj, 'name', {  
        value: '田二黑',       // 属性的值  
        writable: true,     // 是否可写  
        enumerable: true,   // 是否能够通过for in 枚举  
        configurable: true  // 是否可使用 delete删除  
    })

当然我们可以定义访问器属性 get  set,当你读取age属性时,会自动调用get,设置属性时会调用set

    Object.defineProperty(obj, 'age', {  
        get: function(){  
            return 20;  
        },  
        set: function(newVal){  
            this.age += 20;  
        }  
    })

其中,vue就是利用访问器实现的数据双向绑定,像下面这个例子(可能你家没满月的孩子都会写了)

    new Vue({  
       data:{  
          name:'田二黑',  
            age:21  
        }  
    })

如果我们把data对象的属性全部转化为访问器属性,那我们不就可以检测变化了,修改时候会调用set访问器,在里面回调通知不就行了?

    const OP = Object.prototype;  
    const types = {  
      obj:'[object Object]',  
      array:'[object Array]'  
    }  
    export default class Jsonob{  
        constructor(obj,cb){  
            if(OP.toString.call(obj) !== types.obj){  
                console.log('请传入一个对象');  
                return false;  
            }  
            this._callback = cb;  
            this.observe(obj);  
        }  
        observe(obj){  
            Object.keys(obj).forEach((key)=>{  
                let val = obj[key];  
                Object.defineProperty(obj,key,{  
                    get:function(){  
                        return val;  
                    },  
                    set:(function(newVal){  
                        this._callback(newVal)  
                        val = newVal  
                    }).bind(this)  
                })  
            },this)  
        }  
    }  

上面代码声明了类Jsonob,接收要监听的对象和回调函数;observe方法,遍历该对象,并依次将对象属性转为访问器属性,在set中回调通知。

接下来我们测试一下

    import Jsonob from './jsonOb'  
    var data = {  
        a: 200,  
        level1: {  
            b: 'str',  
            c: [1, 2, 3],  
            level2: {  
                d: 90  
            }  
        }  
    }  
    var cb = (val)=>{  
        console.log(val)  
    }  
    new Jsonob(data,cb);  
    data.level1.level2.d = 50  

当修改对象data中属性时,回调打印出新的值。这样还没结束,我的旧值去哪了,我想获取旧值咋办?并且如果我设置的新值又是个对象咋办

    let val = obj[key];  
    Object.defineProperty(obj,key,{  
    get:function(){  
         return val;  
        },  
    set:(function(newVal){  
         this._callback(newVal)  
         val = newVal  
        }).bind(this)  
    })

上面的val = obj[key];存储的不就是旧值吗?于是修改代码如下

    Object.keys(obj).forEach((key)=>{  
                let oldVal = obj[key];  
                Object.defineProperty(obj,key,{  
                    get:function(){  
                        return oldVal;  
                    },  
                    set:(function(newVal){  
                        if(oldVal !== newVal){  
                            if(OP.toString.call(newVal) === '[object Object]'){  
                                    this.observe(newVal);  
                                }  
                            this._callback(newVal,oldVal)  
                            oldVal = newVal  
                        }  
                    }).bind(this)  
                })  
                if(OP.toString.call(obj[key]) === types.obj){  
                    this.observe(obj[key])  
                }  
            },this)

判断修改的值是否为对象,如果是对象,则继续转换新增的值的属性为访问器属性。在回调中就能接收新值和旧值。当然相信你已经发现了,

data.leavel.c是个数组,当我们push,shift等操作时还监听不到,首先,当我们调用数组的push等方法时,是执行的数组原型上的方法,那我们重

写原型上的这些方法,在这些方法里面监听不就ok了,像这样

    Array.prototype.push = function(){  
        /********/  
    Array.prototype.shift= function(){  
        /********/  
    }


数组有push,shift,pop,unshift等等,你要重写那么多方法并实现其功能,就算你实现了,并且不影响其他代码中数组的使用,性能上来说也是不

能相提并论的。那我们怎么实现?我们可不可以让数组实例的原型指向一个我们自定义的对象fakeprototype,当我们调用push方法时,调用的是该

对象上的push方法,在方法里面监听变化,然后在调用Array.prototype真正原型对象上的push方法不就行了。代码实现如下:

    const OP = Object.prototype;  
    const types = {  
      obj:'[object Object]',  
      array:'[object Array]'  
    }  
    const OAM =['push','pop','shift','unshift','short','reverse','splice']  
    export default class Jsonob{  
        constructor(obj,cb){  
            if(OP.toString.call(obj) !== types.obj && OP.toString.call(obj) !== types.array){  
                console.log('请传入一个对象或数组');  
                return false;  
            }  
            this._callback = cb;  
            this.observe(obj);  
        }  
        observe(obj){  
            if(OP.toString.call(obj) === types.array){  
                this.overrideArrayProto(obj);  
            }  
            Object.keys(obj).forEach((key)=>{  
                let oldVal = obj[key];  
                Object.defineProperty(obj,key,{  
                    get:function(){  
                        return oldVal;  
                    },  
                    set:(function(newVal){  
                        if(oldVal !== newVal){  
                            if(OP.toString.call(newVal) === '[object Object]'){  
                                this.observe(newVal);  
                            }  
                            this._callback(newVal,oldVal)  
                            oldVal = newVal  
                        }  
                    }).bind(this)  
                })  
                if(OP.toString.call(obj[key]) === types.obj || OP.toString.call(obj[key]) === types.array){  
                    this.observe(obj[key])  
                }  
            },this)  
        }  
        overrideArrayProto(array){  
                // 保存原始 Array 原型  
            var originalProto = Array.prototype,  
                // 通过 Object.create 方法创建一个对象,该对象的原型是Array.prototype  
                overrideProto = Object.create(Array.prototype),  
                self = this,  
                result;  
                // 遍历要重写的数组方法  
                OAM.forEach((method)=>{  
                    Object.defineProperty(overrideProto,method,{  
                        value:function(){  
                            var oldVal = this.slice();  
                            //调用原始原型上的方法  
                            result = originalProto[method].apply(this,arguments);  
                            //继续监听新数组  
                            // self.observe(this);  
                            self._callback(this,oldVal);  
                            return result;  
                        }  
                    })  
                });  
            // 最后 让该数组实例的 __proto__ 属性指向 假的原型 overrideProto  
            array.__proto__ = overrideProto;  
              
        }  
    }

当我们再去对data.leave1.c.push()的时候,就能监听到变化。然而还没有完,我们现在只是知道了修改的新值和旧值,我们修改的哪个属性啊?我们

现在的程序还无法知道,像vue,在模板中<div>{{name}}</div><div>{{age}}</div> 如果name变化,只是修改第一个div,这就是知道修改哪个属

性的好像,不然只能对模板重新全部刷新,性能肯定是不如局部修改的。因此我们还要在代码的基础上加个路径变量,表示是data的哪个属性。

    const OP = Object.prototype;  
    const types = {  
      obj:'[object Object]',  
      array:'[object Array]'  
    }  
    const OAM =['push','pop','shift','unshift','short','reverse','splice']  
    export default class Jsonob{  
        constructor(obj,cb){  
            if(OP.toString.call(obj) !== types.obj && OP.toString.call(obj) !== types.array){  
                console.log('请传入一个对象或数组');  
                return false;  
            }  
            this._callback = cb;  
            this.observe(obj);  
        }  
        observe(obj,path){  
            if(OP.toString.call(obj) === types.array){  
                this.overrideArrayProto(obj,path);  
            }  
            Object.keys(obj).forEach((key)=>{  
                let oldVal = obj[key];  
                let pathArray = path&&path.slice();  
                if(pathArray){  
                    pathArray.push(key);  
                }  
                else{  
                    pathArray = [key];  
                }  
                Object.defineProperty(obj,key,{  
                    get:function(){  
                        return oldVal;  
                    },  
                    set:(function(newVal){  
                        if(oldVal !== newVal){  
                            if(OP.toString.call(newVal) === '[object Object]'){  
                                this.observe(newVal,pathArray);  
                            }  
                            this._callback(newVal,oldVal,pathArray)  
                            oldVal = newVal  
                        }  
                    }).bind(this)  
                })  
                if(OP.toString.call(obj[key]) === types.obj || OP.toString.call(obj[key]) === types.array){  
                    this.observe(obj[key],pathArray)  
                }  
            },this)  
        }  
        overrideArrayProto(array,path){  
                // 保存原始 Array 原型  
            var originalProto = Array.prototype,  
                // 通过 Object.create 方法创建一个对象,该对象的原型是Array.prototype  
                overrideProto = Object.create(Array.prototype),  
                self = this,  
                result;  
                // 遍历要重写的数组方法  
                OAM.forEach((method)=>{  
                    Object.defineProperty(overrideProto,method,{  
                        value:function(){  
                            var oldVal = this.slice();  
                            //调用原始原型上的方法  
                            result = originalProto[method].apply(this,arguments);  
                            //继续监听新数组  
                            self.observe(this,path);  
                            self._callback(this,oldVal,path);  
                            return result;  
                        }  
                    })  
                });  
            // 最后 让该数组实例的 __proto__ 属性指向 假的原型 overrideProto  
            array.__proto__ = overrideProto;  
              
        }  

当第一次调用observe时,path为空,则pathArray将当前key传入,如果不为空,则继续追加path。好了,我们现在的程序算是比较完整了,知道

要修改的属性,新值和就旧值。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Vue 中,可以使用 `watch` 属性来监听数据的变化。 使用方法如下: ``` new Vue({ data: { message: 'Hello' }, watch: { message: function (newValue, oldValue) { console.log('message changed from', oldValue, 'to', newValue) } } }) ``` 在上面的代码中,我们定义了一个监听器,当 `message` 的值发生变化时,就会触发监听器函数。在监听器函数中,可以访问新的值和旧的值。 你也可以使用 `watch` 对象的简写语法,像这样: ``` new Vue({ data: { message: 'Hello' }, watch: { message (newValue, oldValue) { console.log('message changed from', oldValue, 'to', newValue) } } }) ``` ### 回答2: 在Vue中,我们可以使用`watch`选项来监听`data`中数据的变化。以下是Vue中使用`watch`的步骤: 1. 在Vue实例中定义一个`watch`选项,该选项是一个对象。 2. 对象的属性是要监听的数据,可以是`data`中的某个属性,也可以是计算属性。 3. 对象的值是一个函数,用于处理数据变化时的回调函数。 4. 在回调函数中,我们可以访问到新值和旧值,可以根据变化来进行相应的操作。 以下是一个示例,演示了如何使用`watch`来监听`data`中某个属性的变化: ``` new Vue({ data: { counter: 0 }, watch: { counter(newValue, oldValue) { console.log('counter的值从', oldValue, '变为', newValue); } } }) ``` 在上述示例中,我们定义了一个名为`counter`的属性,并设置初始值为0。在`watch`选项中,我们定义了一个`counter`属性,设置了一个回调函数。当`counter`属性的值发生变化时,该回调函数会被触发,并传入新值和旧值作为参数。在这个回调函数中,我们可以打印出新值和旧值,或者执行其他的操作。 通过使用`watch`选项,我们可以很方便地监听`data`中数据的变化,并在变化时执行相应的逻辑。这对于响应式地更新视图或执行其他操作非常有用。 ### 回答3: 在Vue中,我们可以使用`watch`属性来监听`data`中数据的变化。在Vue组件中,可以在`watch`对象中添加一个或多个属性,并指定一个回调函数来处理数据变化的逻辑。 首先,我们需要在组件的`watch`选项中定义所要监听的属性。例如,假设我们有一个`data`属性名为`message`,我们想要监听它的变化,可以在组件的`watch`中添加如下代码: ```javascript watch: { message(newVal, oldVal) { // 处理数据变化的逻辑 } } ``` 在上面的代码中,`message`是要监听的数据属性名。当`message`属性的值发生变化时,将会触发回调函数。回调函数中有两个参数,`newVal`和`oldVal`分别表示数据变化后的新值和变化前的旧值。 在回调函数中,您可以编写任何逻辑来处理数据变化,比如更新其他的数据、发送网络请求等。 另外,如果你想监听多个属性的变化,只需在`watch`对象中添加多个属性即可。例如: ```javascript watch: { message(newVal, oldVal) { // 处理message属性变化的逻辑 }, name(newVal, oldVal) { // 处理name属性变化的逻辑 } } ``` 总的来说,使用`watch`可以帮助我们实时捕捉数据的变化,并执行对应的处理逻辑,从而提供更好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值