vue 可以侦听data中的数据变化的原理

本文详细讲解了Vue中`Object.defineProperty`的作用,包括属性定义、getter/setter的运用,以及如何通过该方法实现数据绑定。通过实例演示了Vue如何利用`defineProperty`实现属性的不可枚举、不可配置和不可写,以及观察者模式在属性绑定中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Object.defineProperty()

要想搞懂 vue 侦听数据变化的源理, 我们就必须要了解一下 defineProperty();
它的作用是给对象添加一个属性, 并且可以灵活的对属性进行设置 如:是否可以遍历, 是否可以修改 删除等等
当然, 给对象添加属性有很多方式, 为什么就要用这一种呢?
下面用代码来说明一下

    let obj1 = {
        name:"huang",
        sex:"男"
    }
    obj1.age = 20;                  //这里给obj1.添加了一个 age 属性
    console.log(obj1);              //打印出 obj1 发现age属性添加上去了
    let obj2 = {
        name:"huang",
        sex:"男"
    }                               //这里给obj2 添加了一个age属性
    Object.defineProperty(obj2,'age',{
        enumerable:false,           //默认是true 作用是指定添加的这个age 属性能否被遍历, 也就是说 for ... in 语句中会不会有这个属性
        configurable:false,         //默认为true 作用是指定这个属性可不可以被删除
        writable:false,             //默认为true 作用是这个属性可以不以被修改
        value:20
    })
    console.log(obj2);
    obj1.age=40;                    //这里对obj1.age 属性做了修改
    console.log(obj1);
    obj2.age=30;                    //这里对obj2.age 属性做了修改, 打印后发现是不可以修改的
    console.log(obj2);

我们看一下 console.log的结果
在这里插入图片描述
同样 因为obj2中的 age 属性为 enumerable:false , 所以当我们使用 for-in来遍历时, 也没有age属性显示
上面的不是重点, 我们重点是想说, defineProperty中的 getter 和 setter 方法

    let obj2 = {
        name:"huang",
        sex:"男",
        _age:0                     //原对象上有一个 _age 属性
    }
    Object.defineProperty(obj2,"age",{   //新增一个属性 age 使用 get , set 方法, 使它的值取决于 _age 属性
        enumerable:true,    //因为有了 get 和 set 方法, 所里这里面不可以写  writeable 和 value 的配置, 否则会报错
        get() {
            return this._age;
        },
        set(value){
            this._age = value;
        }
    })
    obj2.age = 100;
    console.log(obj2);      
    obj2._age = 200;
    console.log(obj2);
    //这样子做的结果就是  当 _age 或者  age发生改变时, 这两个属性的返回值都可以发生变化

上面的例子, 我们做一下衍生, 有两个对象, 我希望 其中一个对象的一个属性, 与另一个对象的一个属性捆绑, 也就是说任一个对象的属性发生了变化, 两个对象都会受到影响
先说一下, 错误的做法

let obj2 = {
        name:"huang",
        sex:"男",
        _age:0                     
    }
    let obj3 = obj2;
    obj3._age = 40;
    console.log(obj2);

上面的做法是错误的, 因为这可不是两个对象, 上面的做法, 其实是同一个对象, 这是一个浅 copy
下面看一下正确的

    let obj2 = {
        name:"huang",
        sex:"男"
    }
    let obj3 = {
        num:10,
        count:100
    }
    //我现在想在 obj2中添加一个 age 属性并且这个age属生 和  obj3中的 num 属性进行绑定,
    // 不管是修改 obj2.age  还是修改 obj3.num 两个对象都可以得到更改后的结果
    Object.defineProperty(obj2,"age",{
        get() {
            return obj3.num;
        },
        set(value){
            obj3.num = value;
        }
    })
    obj2.age = 1000;
    console.log(obj3);
    console.log(obj2);

    setTimeout(()=>{        //这里我为什么要 延时5秒执行, 完全是因为 console.log 控制台的 age(...) 我点不急 哈哈
        obj3.num = 2000;
        console.log(obj3);
        console.log(obj2);
    },5000)

控制台的输出
在这里插入图片描述
从上面可以看到, 我们成功的把 obj2中的属性, 另外的一个对象obj3绑定上关系了, 不管是修改 obj2.age 还是修改 obj3.num 都是可以得到修改的结果的

现在来看一下这个东西和我们要说的 Vue 有什么关系 , 我们vue 的代码如下

	let vm = new Vue({
		el:"#root",
		data:{
			name:"huang",
			age:"30"
		}
	})

上面的代码是不是很熟, 我们知道 , vm 中有一个 vm._data 这个属性, 它其实就是我们 声明实例时的 data 对象, 并且 _data中的属性发生变化, data中的也同样发生变化
所以, 我们可以猜到 vue中的 data 和 vm._data 就是两个不同的对象, 但是他们通过 Object.defineProperty 的方式做了捆绑


Object.defineProperty 的一个误区

    let obj2 = {
        name:"huang"
    }
    Object.defineProperty(obj2,"name",{
        get() {
            return this.name;
        },
        set(v) {
            this.name = v;
        }
    })
    console.log(obj2);

上面的代码, 意义上都是有错误的, Object.defineProperty 是新增一个属性了, 而上面的用法, 不是新增一个属性, 而是覆盖了一个属性, 并且我们看一下代码, 当 得到name 属性时, 会去调用 get 方法, 而get 方法中又对得到 name 属性, 就又要去调get 方法, 这是一个没有头的无限递归, 所以上面的代码会报错的, 同样, set 方法和get 方法是一样的会出错


下面写一个 Vue 的侦听属性更改的原理代码, 代码很多功能没有考虑, 只是说有 侦听属性是怎么实现的

	let data = {
        name:"huang"
    }
    //定义一个类  传入一个对象, 把传入的对象,和 这个类的属性做捆绑
    function Observer(obj){
        if(!(this instanceof Observer)){  //如果对象不是使用的 new 关键字就报错
            return "请使用 new 关键字,不然的话没有 this"
        }

        let keys = Object.keys(obj);
        keys.forEach((key)=>{
            //这里就把传入的对象的属性,都添加到了 Observer 类的对象中, 实现了捆绑
            Object.defineProperty(this,key,{
                get() {
                    return obj[key];
                },
                set(v) {
                    //在 set 方法中, vue 就可以做以下的事情, 我侦听到了一个 属性发生变化了, 我要去把页面重新渲染一下了
                    //vue 渲染页面的话, 就会有一系列的反应, 如 虚拟dom 的匹配什么, 就会使页面发生改变
                    obj[key] = v;
                }
            })
        })
    }

    let observer = new Observer(data); //我们创建一个Observer的实例对象, 把data中的所有属性和 observer 的属性捆绑了

    //同时, 我们再创建一个 vm 对象, 让它的 _data的属性, 等于 observer,就可以了

    let vm = {};
    vm._data = observer;
    data = observer; //注意原来的data没有丢,仍然在被observer引用着,只是data这个变量名被observer占用了,原来的data对象无法用除observer以外的手段找回
    //有了上面的代码, 就可以随意更改  vm._data  或者 data 中的数据, 都会触发 set 方法, 也就触发了页面的渲染
   

以上就是一个简单的 Vue 侦听属性变化的例子
对于这段话的理解 注意原来的data没有丢,仍然在被observer引用着,只是data这个变量名被observer占用了,原来的data对象无法用除observer以外的手段找回, 我们可以使用下面的这段代码证实

   let a = {
        aa:1,
        bb:2
    }
    class A{
        constructor(param) {
            this.c = param;
        }
    }
    const m = new A(a)   //这里把  a 当做参数传入后, 再去修改  a 的值,  
    a = "111";
    console.log(m.c);

看一下输入结果 m.c 到底是什么,就知道了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A黄俊辉A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值