vue双向数据绑定原理

196 阅读1分钟

所谓双向数据绑定就是页面与数据的关联,任何一方改动都会引起另一方的变化。其实现响应性原理是利用了js原生的defineProperties方法实现的。

1.defineProperties方法的特点

defineProperties方法的特点 可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。 并且在修改/新增的时候给该属性添加get/set方法 defineProperties get/set方法的特点 只要通过defineProperty给某个属性添加了get/set方法,那么以后只要获取这个属性的值就会自动调用get,设置这个属性的值就会自动调用set. 注意点 如果设置了get/set方法,那么就不能通过value直接赋值,也不能编写writable: true.

2.defineProperties的使用

新增属性

<template>
  <div class="home">
    <button @click="handelButton">按钮</button>
  </div>
</template>
<script>
export default {
  name: "Home",
  methods: {
    // 需求:给obj对象新增一个name对象,取值为"山竹"
    handelButton() {
      let obj = {};
      // 第一个参数是需要操作的对象
      // 第二个参数是需要操作的属性
      // 第三个参数是一个对象,用来描述需要表达的值
      Object.defineProperty(obj, "name", {
        // 可以通过value来告诉defineProperty方法新增的属性的取值是什么
        value: "山竹",
        //需要告诉defineProperty取值可以修改
        writable: true,
        //需要告诉defineProperty取值可以删除
        configurable: true,
        //需要告诉defineProperty新增属性可以迭代
        enumerable: true,
      });
      // console.log(obj); //{name:"山竹"}

      // // 注意点:默认情况下通过defineProperty新增的属性的取值是不能修改的
      // obj.name = "杀生丸";
      // console.log(obj); //如果没有更改writable会报错,提示是只读属性


      // // 注意点:默认情况下通过defineProperty新增的属性的取值是不能删除的
      // delete obj.name;
      // console.log(obj); //如果没有更改configurable会报错,提示是只读属性


      // 注意点:默认情况下通过defineProperty新增的属性是不能遍历的
      for (let key in obj) {
        console.log(key, obj[key]);//name 山竹
      }
    },
  },
};
</script>

修改属性

<template>
  <div class="home">
    <button @click="handelButton">按钮</button>
  </div>
</template>
<script>
export default {
  name: "Home",
  methods: {
    handelButton() {
      let obj = {name:'杀生丸'};
      Object.defineProperty(obj, "name", {
        value: "山竹",
        writable: true,//设置允许修改
      });
      console.log(obj);//山竹
    },
  },
};
</script>

get/set方法

<template>
  <div class="home">
    <button @click="handelButton">按钮</button>
  </div>
</template>
<script>
export default {
  name: "Home",
  methods: {
    handelButton() {
      let obj = {};
      let oldValue = "杀生丸";
      Object.defineProperty(obj, "name", {
        get() {
          console.log("get被触发啦");
          return oldValue;
        },
        set(newValue) {
          console.log("set被触发啦");
          // 判断新值老值不一致,则重新赋值
          if(oldValue!==newValue){
            oldValue=newValue
          }
        },
      });
      console.log(obj.name); //杀生丸
      obj.name='山竹'
      console.log(obj.name);//山竹
    },
  },
};
</script>

快速监听对象中所有属性的变化

<template>
  <div class="home">
    <button @click="handelButton">按钮</button>
  </div>
</template>
<script>
export default {
  name: "Home",
  methods: {
    handelButton() {
      let obj = {
        name:{a:'山竹'},
        age: 18,
      };
      class Observer {
        // 只要将需要监听的那个对象传递给Observer这个类
        // 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
        // data代表接收的对象
        constructor(data) {
          this.observer(data);
        }
        // 给属性添加get/set;
        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方法直接返回值
            get() {
              return value;
            },
            // set方法接收新值并返回
            set(newValue) {
              console.log('监听到值变化并更新到UI ');
              newValue===value?value:value=newValue
            },
          });
        }
      }
      new Observer(obj)
      obj.name.a='杀生丸'
      console.log(obj.name.a);//age:18,name:"杀生丸"
    },
  },
};
</script>

升级版

普通属性转变成复杂属性修改新属性问题

<template>
  <div class="home">
    <button @click="handelButton">按钮</button>
  </div>
</template>
<script>
export default {
  name: "Home",
  methods: {
    handelButton() {
      let obj = {
        name: "山竹",
        // name:{a:'山竹'},
        age: 18,
      };
      class Observer {
        // 只要将需要监听的那个对象传递给Observer这个类
        // 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
        // data代表接收的对象
        constructor(data) {
          this.observer(data);
        }
        // 给属性添加get/set;
        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方法直接返回值
            get() {
              return value;
            },
            // set方法接收新值并返回
            set: (newValue) => {
              if (value !== newValue) {
                // 如果新值里面也有对象,也需要添加get/set
                this.observer(newValue);
                value = newValue;
                console.log("监听到数据的变化,需要去更新UI");
              }
            },
          });
        }
      }
      new Observer(obj);
      obj.name = { a: "山竹" };
      console.log(obj); //山竹
      obj.name.a = "杀生丸";
      console.log(obj); //杀生丸
      obj.name = "终结";
      console.log(obj.name);//终结
    },
  },
};
</script>

在这里插入图片描述