Vue2响应式数据原理之Object.defineProperty

718 阅读3分钟

前言

今天来聊聊vue2中的响应式数据的原理,究竟是如何去实现双向绑定的?

什么是响应式?

我们都知道Vue是一个MVVM的模式,在 MVVM 模式中,有三个核心组件:

  1. Model:
    • 表示应用程序的数据模型和业务逻辑。
    • 它是应用程序的核心,与用户界面无关。
  2. View:
    • 表示用户界面。
    • 它是Model中数据的可视化展现。
  3. ViewModel:
    • 扮演Model和View之间的桥梁角色。
    • ViewModel负责把Model的数据同步到View上,并将View上的数据变化同步回Model。
    • ViewModel完全封装了View和Model之间的交互细节,使得开发者无需关注这些细节。

响应式就是

  1. 当用户在视图组件中修改数据时,数据模型中的相应数据会自动更新。
  2. 当数据模型中的数据发生变化时,视图组件也会自动更新以反映这些变化。

Object.defineProperty

Object.defineProperty()可以实现属性的数据劫持

<span id="container">1</span>
<button id="btn">加一</button>
<script>
const btn = document.querySelector("#btn");
const span = document.querySelector("#container");
btn.addEventListener("click", () => {
    span.innerHTML = parseInt(span.innerHTML) + 1;
});
</script>

以上代码实现了当我点击按钮就可以为数字添加一的效果

动画3.gif

但是我们观察这段代码可以发现,这段代码没有将视图和数据进行分离

每一次点击按钮都是去主动的操控了dom元素

接下来我们修改代码

<span id="container">1</span>
    <button id="btn">加一</button>

    <script>
      const btn = document.getElementById("btn");
      const span = document.getElementById("container");

      var obj = {
        value: 1,
      };
      var value = 1;

      btn.addEventListener("click", () => {
        obj.value++;
        console.log(obj.value);
      });

      // 监控对象 es5
      Object.defineProperty(obj, "value", {
        get: function () {
          return value;
        },
        set: function (newValue) {
          value = newValue;
          console.log("值改变了");
          // 更新视图
          span.innerHTML = value;
        },
      });
    </script>

使用 Object.defineProperty() 方法实现简单的数据响应式

  1. 首先定义一个 obj 对象,其 value 属性初始值为 1。

  2. 然后通过 document.getElementById() 获取页面上的 <span> 元素和 <button> 元素。

  3. 为 <button> 元素添加一个 click 事件监听器。当按钮被点击时,会触发 obj.value 的自增操作,并打印出新的值。

  4. 接下来,使用 Object.defineProperty() 方法对 obj.value 属性进行数据劫持。

    • 定义 get 函数,返回当前 value 的值。
    • 定义 set 函数,在值发生变化时更新 <span> 元素的内容。

这样,当用户点击按钮时,obj.value 的值会发生变化,通过 set 函数中的逻辑,页面上的 <span> 元素也会随之更新,实现了简单的数据双向绑定。

我们继续修改代码

<span id="cintainer">1</span>
    <span id="txt">hi</span>
    <button id="btn">加一</button>
    <button id="btn2">点击</button>

    <script>
      var obj = {
        value: 1,
        txt: "hi",
      };

      (function () {
        function watch(obj, key, func) {
          console.log("监听任何要监听的数据");
          var value = obj[key];
          Object.defineProperty(obj, key, {
            get: function () {
              return value;
            },
            set: function (newValue) {
              value = newValue;
              func(newValue);
            },
          });
        }
        this.watch = watch;
      })();

      // 数据可以被监听
      watch(obj, "value", function (newValue) {
        document.getElementById("cintainer").innerHTML = newValue;
      });
      watch(obj, "text", function (newValue) {
        document.getElementById("txt").innerHTML = newValue;
      });

      document.getElementById("btn").addEventListener("click", function () {
        obj.value++;
      });
      document.getElementById("btn2").addEventListener("click", function () {
        obj.text = "哈哈哈";
      });
    </script>

这段代码展示了如何通过自定义一个 watch 函数来实现对对象属性的监听和数据双向绑定。

主要流程如下:

  1. 定义一个立即执行函数,在其内部定义了 watch 函数。

  2. watch 函数接受三个参数:

    • obj: 需要被监听的对象
    • key: 需要被监听的对象属性
    • func: 当属性值发生变化时要执行的回调函数
  3. 在 watch 函数内部,首先记录当前属性的值。

  4. 然后使用 Object.defineProperty() 对该属性进行数据劫持:

    • get 函数返回当前属性值
    • set 函数在属性值发生变化时执行传入的回调函数 func
  5. 在主逻辑中,分别对 obj 的 "value" 和 "text" 属性使用 watch 函数进行监听,并在回调函数中更新相应的 DOM 元素。

  6. 为两个按钮添加点击事件监听器,分别触发 obj.value 和 obj.text 的变化。

通过这种自定义 watch 函数的方式,我们可以轻松地对任意对象的任意属性进行监听和数据绑定,并在属性值发生变化时执行相应的更新逻辑。这方式与 Vue.js 中的数据响应式机制非常相似,体现了同样的设计思想。

总结

本文讲解了Vue2响应式数据原理之Object.defineProperty,希望对你能够有所帮助