vue响应式原理(2)将一个简单对象变成响应式的

80 阅读2分钟

上篇文章讲解了 Object.defineProperty() 方法现在我们用它来将一个简单对象变成响应式的。 getter可以监测对象的属性被访问了,setter可监测对象的属性被改变了,我们通过这两个方法就可以监测对象的变化。

let obj = {};
Object.defineProperty(obj,"name",{
        get(){
            console.log("你访问了name属性");
            return "小明";
        },
        set(newValue){
            console.log("你试图修改name属性");
        }
    })
    console.log(obj.name) // 输出 "小明" 还会输出你访问了name属性
    obj.name = "小红" // 会输出:你试图修改name属性

这里存在一个问题 当你执行完上面的代码 再去打印obj.name时 会发现 结果依然是 输出 "小明" 和 "你访问了name属性"。obj.name = "小红"; 并不能真的改变obj.name 的值。 这是因为set 方法虽然可以接收对象属性的新值但并不能改变该属性的值,get方法的返回值才会被赋值给对象该属性。下面要做的就很简单了

let obj = {}, value = "小明";
Object.defineProperty(obj,"name",{
        get(){
            console.log("你访问了name属性");
            return value;
        },
        set(newValue){
            value = newValue;
            console.log("你试图修改name属性");
        }
    })
    console.log(obj.name) // 输出 "小明" 还会输出你访问了name属性
    obj.name = "小红" // 会输出:你试图修改name属性
    console.log(obj.name) // 输出 "小红" 还会输出你访问了name属性

这样我们就可以正常的修改该对象的值,并且能监测到该对象的变化了,但是这样通过外部变量的方式似乎不够优雅,我们可以通过更优雅的闭包来决绝。让我们封装一个方法:

/* 将一个对象变成响应式的
 *@param { object } data 你要定义的对象
 *param { string } key 你要定义的属性名
 *@param { * } val  该属性的值
 */ 
function defineReactive (data,key,val){
    Object.defineProperty(data,key,{
        // 可枚举
        enumerable:true,
        // 可以被配置
        configurable:true,
        //getter
        get(){
            console.log("你访问了"+key+"属性");
            return val;
        },
        //setter
        set(newValue){
            val = newValue;
            console.log("你试图修改"+key+"属性");
        }
    })
}

这样当你再想定义一个响应式的属性时:

let obj = {};
defineReactive(obj,"name","小明");

在实际应用场景中我们所要定义响应式的对象往往不是空的,所以我们的defineReactive方法还应该能够把对象中已存在的属性变成响应式的,这并不困难:

/* 将一个对象变成响应式的
 *@param { object } data 你要定义的对象
 *param { string } key 你要定义的属性名
 *@param { * } val  该属性的值
 */ 
function defineReactive (data,key,val){
    if(arguments.length == 2){
        val = data[key];
    }
    Object.defineProperty(data,key,{
        // 可枚举
        enumerable:true,
        // 可以被配置
        configurable:true,
        //getter
        get(){
            console.log("你访问了"+key+"属性");
            return val;
        },
        //setter
        set(newValue){
            val = newValue;
            console.log("你试图修改"+key+"属性");
        }
    })
}

现在当你想把一个对象变成响应式时只需要:

let obj ={
    name:"小明",
    age:21
};
for (let key in obj) {
   defineReactive(obj,key)
}