前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【手写Vue】-Vue双向数据绑定原理

【手写Vue】-Vue双向数据绑定原理

原创
作者头像
BNTang
修改2023-11-17 08:03:08
5195
修改2023-11-17 08:03:08
举报
文章被收录于专栏:杨不易呀杨不易呀

Vue响应式的原理(数据改变界面就会改变)是什么?

时时监听数据变化, 一旦数据发生变化就更新界面, 这就是Vue响应式的原理。

Vue是如何实现时时监听数据变化的

通过原生JS的defineProperty方法, 通过get和set方法来监听数据的变化。

defineProperty方法的特点

可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

defineProperty用法

可以直接在一个对象上定义一个新属性

假设我有这么一个需求,给obj对象动态新增一个name属性, 并且name属性的取值必须是BNTang,通过 defineProperty 方法实现。

代码语言:javascript
复制
<script>
    let obj = {};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang'
    });

    console.log(obj);
</script>

defineProperty 是属于 Object 对象的方法,所以我们可以直接通过 Object.defineProperty 来调用。

  • 第一个参数是要定义属性的对象(obj: 需要操作的对象)
  • 第二个参数是要定义或修改的属性的名称(prop: 需要操作的属性)
  • 第三个参数是将被定义或修改的属性描述符(descriptor: 属性描述符)

最终语法如下:

代码语言:javascript
复制
Object.defineProperty(obj, prop, descriptor)

在上面提出的需求中,我们需要动态新增一个 name 属性,所以第一个参数就是 obj 对象,第二个参数就是 name 属性,第三个参数就是 name 属性的描述符。

我在第三个参数中定义了一个 value 属性,这个 value 属性的值就是 BNTang,这样我们就实现了动态新增一个 name 属性,并且 name 属性的值是 BNTang。

value 属性描述符的作用可以通过value来告诉defineProperty方法新增的属性的取值是什么。

默认情况下通过defineProperty新增的属性的取值是不能修改的。

代码语言:javascript
复制
<script>
    let obj = {};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang'
    });
    obj.name = 'test';
    console.log(obj);
</script>

如果想修改, 那么就必须显示的告诉defineProperty方法,这个属性是可修改的,通过writable属性描述符来实现。

代码语言:javascript
复制
<script>
    let obj = {};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang',
        writable: true,
    });
    obj.name = 'test';
    console.log(obj);
</script>

默认情况下通过defineProperty新增的属性是不能删除的。

代码语言:javascript
复制
<script>
    let obj = {};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang',
        writable: true,
    });
    obj.name = 'test';
    delete obj.name;
    console.log(obj);
</script>

如果想删除, 那么就必须显示的告诉defineProperty方法,这个属性是可删除的,通过configurable属性描述符来实现。

代码语言:javascript
复制
<script>
    let obj = {};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang',
        writable: true,
        configurable: true,
    });
    obj.name = 'test';
    delete obj.name;
    console.log(obj);
</script>

默认情况下通过defineProperty新增的属性是不能遍历(迭代的)。

代码语言:javascript
复制
<script>
    let obj = {};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang'
    });

    for (let key in obj) {
        console.log(key, obj[key]);
    }
</script>

如果想迭代, 那么就必须显示的告诉defineProperty方法,这个属性是可迭代的,通过enumerable属性描述符来实现。

代码语言:javascript
复制
<script>
    let obj = {};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang',
        enumerable: true
    });

    for (let key in obj) {
        console.log(key, obj[key]);
    }
</script>

到此为止,定义一个新属性的方法就介绍完了,接下来我们来看看如何修改一个对象的现有属性。

其实非常的简单,只需要在我们定义对象的时候初始化一个属性,并且给这个属性一个初始值,然后在调用 defineProperty 方法的时候,将这个属性的值修改为我们想要的值就可以了。

代码如下:

代码语言:javascript
复制
<script>
    let obj = {name: 'Example'};

    Object.defineProperty(obj, 'name', {
        value: 'BNTang'
    });

    console.log(obj);
</script>

defineProperty方法

defineProperty除了可以动态修改/新增对象的属性以外, 还可以在修改/新增的时候给该属性添加get/set方法, 从而实现数据劫持。

defineProperty get/set方法特点

只要通过defineProperty给某个属性添加了get/set方法,那么以后只要获取这个属性的值就会自动调用get, 设置这个属性的值就会自动调用set。

但是有一个注意点, 如果设置了get/set方法, 那么就不能通过value直接赋值, 也不能编写writable:true。

defineProperty get 方法

代码语言:javascript
复制
<script>
    let obj = {};
    let oldValue = "Example"
    Object.defineProperty(obj, 'name', {
        get() {
            console.log('get方法被执行了');
            return oldValue;
        }
    });

    console.log(obj.name);
</script>

defineProperty set 方法

代码语言:javascript
复制
<script>
    let obj = {};
    let oldValue = "Example"
    Object.defineProperty(obj, 'name', {
        set(newValue) {
            if (oldValue !== newValue) {
                console.log("set方法被执行了");
                oldValue = newValue;
            }
        }
    });
    obj.name = 'BNTang'
</script>

总结

通过上面的例子可以看出, 只要给某个属性添加了get/set方法, 那么以后只要获取这个属性的值就会自动调用get, 设置这个属性的值就会自动调用set。还有就是如果设置了get/set方法, 那么就不能通过value直接赋值, 也不能编写writable:true。

Vue双向数据绑定原理-下这一篇文章主要讲解Vue双向数据绑定的原理,主要是通过Object.defineProperty()来实现的,这里我们手写Vue双向数据绑定的原理。

首先我提出一个需求,我的需求是,快速监听对象中所有属性的变化

首先得要有一个对象,对象的定义代码如下:

代码语言:javascript
复制
<script>
    let obj = {
        name: 'BNTang',
        age: 33
    };
</script>

然后我们需要监听这个对象中所有属性的变化,最最最简单的做法如下,这里我们可以使用Object.defineProperty()来实现,代码如下:

代码语言:javascript
复制
Object.defineProperty(obj, 'name', {
    get() {
        return 'BNTang';
    },
    set(newValue) {
    }
});

Object.defineProperty(obj, 'age', {
    get() {
        return 18;
    },
    set(newValue) {
    }
});

这样我们就可以监听到对象中所有属性的变化了,但是这样写的话,代码量太大了,如果有100个属性,那么就要写100次,这样的话,代码量太大了,所以我们需要写一个函数来实现这个功能(例如自定义类)。

博主这里采用的是自定义类的方式来实现,首先定义一个类,代码如下:

代码语言:javascript
复制
class Observer {
    
}

只要将需要监听的那个对象传递给Observer这个类,这个类就可以快速的给传入的对象的所有属性都添加get/set方法, 该类的主要功能就是给传入的对象的所有属性都添加get/set方法。

首先我定义了一个构造函数,绑定了一个形参,就是需要监听的对象,代码如下:

代码语言:javascript
复制
constructor(data) {
}

在然后我定义了一个 observer 方法,将需要监听的对象传递给 observer 方法,遍历取出传入对象的所有属性,给遍历到的属性都增加get/set方法,代码如下:

代码语言:javascript
复制
observer(obj) {
    if (obj && typeof obj === 'object') {
        for (let key in obj) {
        }
    }
}

在 for 循环中,我使用了 defineReactive 方法(自定义一个方法单独来处理),该方法的作用是给传入的对象的所有属性都添加get/set方法,代码如下:

代码语言:javascript
复制
defineReactive(obj, attr, value) {
    Object.defineProperty(obj, attr, {
        get() {
            return value;
        },
        set: (newValue) => {
            if (value !== newValue) {
                value = newValue;
                console.log('监听到数据的变化, 需要去更新UI');
            }
        }
    })
}

好了,现在我们已经定义了一个类,该类的主要功能就是给传入的对象的所有属性都添加get/set方法,那么我们就可以使用这个类了(Test 阶段),代码如下:

代码语言:javascript
复制
new Observer(obj);
obj.name = 'Example';

查看打印结果,可以看到,我们已经监听到了数据的变化,但是这里有一个问题,就是我们只能监听到对象中已经存在的属性的变化,不能监听对象中属性的对象的属性的变化,例如下面的对象代码:

代码语言:javascript
复制
let obj = {
    name: {a: 'abc'},
    age: 33
};

就是对象中的属性值又是一个对象,而这个属性的对象的属性值发生改变,我们自定义的 Observer 是无法进行监听到的。所以我们需要对这个问题进行处理(如果属性的取值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法)。

代码语言:javascript
复制
this.observer(value);

测试一下:

代码语言:javascript
复制
obj.name.a = 'Example';

可以看到,我们已经可以监听到对象中属性的对象的属性值的变化了。

但是这里还有一个问题,就是如果对象中的属性值是一个基本数据类型,我们在给这个对象绑定完毕get/set方法之后,再给这个对象赋值的时候, 赋值成了引用类型, 新赋值的属性的对象的值是不会被监听到的。例如下面的代码:

代码语言:javascript
复制
<script>
    let obj = {
        name: 'BNTang',
        age: 33
    };

    class Observer {
        constructor(data) {
            this.observer(data);
        }

        observer(obj) {
            if (obj && typeof obj === 'object') {
                for (let key in obj) {
                    this.defineReactive(obj, key, obj[key]);
                }
            }
        }

        defineReactive(obj, attr, value) {
            this.observer(value);
            Object.defineProperty(obj, attr, {
                get() {
                    return value;
                },
                set: (newValue) => {
                    if (value !== newValue) {
                        value = newValue;
                        console.log('监听到数据的变化, 需要去更新UI');
                    }
                }
            })
        }
    }

    new Observer(obj);
    obj.name = {a: 'abc'};
    obj.name.a = 'BNTang';
</script>

运行结果:

可以看到,只能监听到对象属性值初始化的时候的变化,不能监听到对象属性值重新赋值的为对象的属性值的变化。

所以我们需要对这个问题进行处理,我们需要在给对象属性值重新赋值的时候,给这个对象属性值重新绑定get/set方法(如果给属性赋值的新值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法),代码如下:

运行结果:

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Vue响应式的原理(数据改变界面就会改变)是什么?
  • Vue是如何实现时时监听数据变化的
  • defineProperty方法的特点
  • defineProperty用法
  • defineProperty方法
  • defineProperty get/set方法特点
  • defineProperty get 方法
  • defineProperty set 方法
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档


http://www.vxiaotou.com