Vue 双向绑定原理

120 min read

Vue 双向绑定原理

  • Vue响应式的原理(数据改变界面就会改变)是什么?
    时时监听数据变化, 一旦数据发生变化就更新界面

  • Vue是如何实现时时监听数据变化的?
    通过原生JS的defineProperty方法

  • defineProperty方法的特点
    可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象

  • defineProperty用法
    obj: 需要操作的对象
    prop: 需要操作的属性
    descriptor: 属性描述符
    Object.defineProperty(obj, prop, descriptor)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>01-Vue基本模板</title>
    <!--1.下载导入Vue.js-->
    <script src="js/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="name">
    <p>{{ name }}</p>
</div>
<script>
    // 2.创建一个Vue的实例对象
    let vue = new Vue({
        // 3.告诉Vue的实例对象, 将来需要控制界面上的哪个区域
        el: '#app',
        // 4.告诉Vue的实例对象, 被控制区域的数据是什么
        data: {
            name: "test"
        }
    });
    /*
    1.Vue响应式的原理(数据改变界面就会改变)是什么?
      时时监听数据变化, 一旦数据发生变化就更新界面
    2.Vue是如何实现时时监听数据变化的?
      通过原生JS的defineProperty方法
    3.defineProperty方法的特点
      可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象
    4.defineProperty用法
    obj: 需要操作的对象
    prop: 需要操作的属性
    descriptor: 属性描述符
    Object.defineProperty(obj, prop, descriptor)
    * */
    let obj = {name: 'test'};
    // 需求: 给obj对象动态新增一个name属性, 并且name属性的取值必须是lnj
    Object.defineProperty(obj, 'name', {
        // 可以通过value来告诉defineProperty方法新增的属性的取值是什么
        value: 'lnj',
        // 默认情况下通过defineProperty新增的属性的取值是不能修改的
        // 如果想修改, 那么就必须显示的告诉defineProperty方法
        // writable: true
        writable: true,
        // 默认情况下通过defineProperty新增的属性是不能删除的
        // 如果想上传, 那么就必须显示的告诉defineProperty方法
        configurable: true,
        // 默认情况下通过defineProperty新增的属性是不能迭代的
        // 如果想迭代, 那么就必须显示的告诉defineProperty方法
        enumerable: true
    });
    console.log(obj);
    // 注意点: 默认情况下通过defineProperty新增的属性的取值是不能修改的
    // obj.name = 'it666';
    // console.log(obj);
    // 注意点: 默认情况下通过defineProperty新增的属性是不能删除的
    // delete obj.name
    // console.log(obj);
    // 注意点: 默认情况下通过defineProperty新增的属性是不能遍历(迭代的)
    // for(let key in obj){
    //     console.log(key, obj[key]);
    // }
</script>
</body>

defineProperty get/set方法

只要通过defineProperty给某个属性添加了get/set方法

那么以后只要获取这个属性的值就会自动调用get, 设置这个属性的值就会自动调用set

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>01-Vue基本模板</title>
    <!--1.下载导入Vue.js-->
    <script src="js/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="name">
    <p>{{ name }}</p>
</div>
<script>
    // 2.创建一个Vue的实例对象
    let vue = new Vue({
        // 3.告诉Vue的实例对象, 将来需要控制界面上的哪个区域
        el: '#app',
        // 4.告诉Vue的实例对象, 被控制区域的数据是什么
        data: {
            name: "test"
        }
    });
    /*
    1.defineProperty方法
    defineProperty除了可以动态修改/新增对象的属性以外
    还可以在修改/新增的时候给该属性添加get/set方法
    2.defineProperty get/set方法特点
    只要通过defineProperty给某个属性添加了get/set方法
    那么以后只要获取这个属性的值就会自动调用get, 设置这个属性的值就会自动调用set
    3.注意点:
    如果设置了get/set方法, 那么就不能通过value直接赋值, 也不能编写writable:true
    * */
    let obj = {};
    let oldValue = 'test';
    Object.defineProperty(obj, 'name', {
        // value: oldValue,
        // writable: true,
        configurable: true,
        enumerable: true,
        get(){
            console.log("get方法被执行了");
            return oldValue;
        },
        set(newValue){
            if(oldValue !== newValue){
                console.log("set方法被执行了");
                oldValue = newValue;
            }
        }
    });
    console.log(obj.name);
    obj.name = 'lnj';
</script>
</body>
</html>

手动实现Vue的响应式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>01-Vue基本模板</title>
    <!--1.下载导入Vue.js-->
    <script src="js/vue.js"></script>
</head>
<body>
<div id="app">
    <input type="text" v-model="name">
    <p>{{ name }}</p>
</div>
<script>
    // 2.创建一个Vue的实例对象
    let vue = new Vue({
        // 3.告诉Vue的实例对象, 将来需要控制界面上的哪个区域
        el: '#app',
        // 4.告诉Vue的实例对象, 被控制区域的数据是什么
        data: {
            name: "李南江"
        }
    });
    /*
    需求: 快速监听对象中所有属性的变化
    * */
    let obj = {
        name: 'lnj',
        // name: {a: 'abc'},
        age: 33
    };
    class Observer{
        // 只要将需要监听的那个对象传递给Observer这个类
        // 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
        constructor(data){
            this.observer(data);
        }
        observer(obj){
            if(obj && typeof obj === 'object'){
                // 遍历取出传入对象的所有属性, 给遍历到的属性都增加get/set方法
                for(let key in obj){
                    this.defineRecative(obj, key, obj[key])
                }
            }
        }
        // obj: 需要操作的对象
        // attr: 需要新增get/set方法的属性
        // value: 需要新增get/set方法属性的取值
        defineRecative(obj, attr, value){
            // 如果属性的取值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法
            this.observer(value);
            Object.defineProperty(obj, attr, {
                get(){
                    return value;
                },
                set:(newValue)=>{
                    if(value !== newValue){
                        // 如果给属性赋值的新值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法
                        this.observer(newValue);
                        value = newValue;
                        console.log('监听到数据的变化, 需要去更新UI');
                    }
                }
            })
        }
    }
    new Observer(obj);
    // obj.name = 'it666';
    // obj.age = 666;
    // obj.name.a = 'it666';
    obj.name = {a: 'abc'};
    obj.name.a = 'it666';
</script>
</body>
</html>