学习尚硅谷禹神b站vue3时学习vue监视原理中遇到的疑惑,

在学习尚硅谷禹神的最新的vue3时时有这样一个疑问:
就是一开始使用如下代码,在页面中读取data.name时会产生死循环:

img


然后后来使用observe构造函数创建一个obs对象,然后再把vm._data和data和obs连等,为什么这样在读取data.name时不会产生死循环,我的想法如下图:

img

我没怎么学过前端,还请各位解答谢谢。

  • 这篇博客: 【Vue 组件化开发 二 】注册组件的语法糖、组件模板的分离写法、组件的数据中的 2.组件的data为什么必须要是函数 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • ​组件的思想是复用,定义组件当然是把通用的公共的东西抽出来复用。

    以下代码中定义了两个组件cpn1cpn2,都是定义了两个计数器,con1的data虽然使用了函数,但是为了模拟data:{count:0},使用了常量obj来返回count。

    <div id="app">
        <h2>data不使用函数</h2>
        <cpn1></cpn1>
        <cpn1></cpn1>
        <hr>
        <h2>data使用函数</h2>
        <cpn2></cpn2>
        <cpn2></cpn2>
        <hr>
      </div>
      <script src="../js/vue.js"></script>
      <template id="cpn1">
        <div>
          <button @click="count--">-</button>
          当前计数:{{count}}
          <button @click="count++">+</button>
        </div>
      </template>
      <template id="cpn2">
        <div>
          <button @click="count--">-</button>
          当前计数:{{count}}
          <button @click="count++">+</button>
        </div>
      </template>
      <script>
        const obj = {
          count:0
        };
        const app = new Vue({
          el: "#app",
          components: { //局部组件创建
            cpn1: {
              template: '#cpn1',
              data() {
                return obj;
              }
            },
            cpn2: {
              template: '#cpn2',
              data() {
                return {
                  count: 0
                }
              }
            }
          }
        })
      </script>

     图中可以看到,不使用函数data的好像共用一个count属性,而使用函数的data的count是各自用各自的,像局部变量一样有块级作用域,这个块级就是vue组件的作用域。

    我们在复用组件的时候肯定希望,各自组件用各自的变量,如果确实需要都用一样的,可以全局组件注册,也可以是用vuex来进行状态管理。

  • 以下回答由chatgpt基于相关博客总结生成:

    针对问题: 当页面中读取data.name时产生死循环的问题该如何解决?

    解决方案: 1. 确认是因为该问题导致的死循环,可以通过console.log()等打印信息,定位代码中出现问题的位置。 2. 将组件中的data定义成函数形式,确保每个组件实例都有一个独立的数据源。 3. 使用观察者模式解决数据监听问题。可以通过创建一个Observer类,并在该类中使用Object.defineProperty()为每个属性添加getter和setter监听,从而实现数据劫持。在创建组件实例时,使用observe构造函数创建Observer对象,并将vm._data和data和obs进行连等。具体代码实现如下:

    class Observer {
      constructor(value) {
        this.value = value;
        if (!Array.isArray(value)) {
          this.walk(value);
        }
      }
    
      walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i]);
        }
      }
    }
    
    function defineReactive(obj, key, val) {
      const dep = new Dep(); //定义一个dep依赖实例
      const property = Object.getOwnPropertyDescriptor(obj, key);
      if (property && property.configurable === false) {
        return;
      }
      val = obj[key];
    
      // 继续递归核心算法,判断val是不是对象,如果是,就递归下去
      let childOb = observe(val);
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          const value = property.get ? property.get.call(obj) : val;
          if (Dep.target) { // 当Dep.target存在时,添加订阅进来
            dep.depend();
            if (childOb) { // 如果子组件不为空,则将新添加的Dep对象添加进去
              childOb.dep.depend();
            }
          }
          return value
        },
        set: function reactiveSetter(newVal) {
          const value = property.get ? property.get.call(obj) : val;
          if (newVal === value || (newVal !== newVal && value !== value)) { //如果要设置的值等于现在的值,或者设置的值是NaN,并且现在的值也是NaN,则直接结束递归
            return
          }
          if (property && property.set) {
            property.set.call(obj, newVal);
          } else {
            val = newVal;
          }
          childOb = observe(newVal);
          dep.notify(); // 如果值发生了改变,则通知订阅者更新组件视图
        }
      });
    }
    
    function observe(value) {
      if (!value || typeof value !== 'object') {
        return
      }
      if (value.__ob__) {
        return value.__ob__;
      }
      return new Observer(value);
    }
    
    function initState(vm) {
      const data = vm.$options.data;
      vm._data = data.call(vm);
    
      // 执行observe函数
      observe(vm._data);
    }
    
    let vm = new Vue({
      el: "#box",
      data() {
        return {
          num: 0
        }
      },
      components: {
        temp: {
          template: "#temp",
          data() {
            return {
              num: 2
            }
          }
        }
      }
    });
    

    以上是使用observe构造函数创建obs对象声明方式来通过观察者模式解决死循环问题的代码实现。其中,Observer类是对整个数据对象的分类,递归地为对象中每个属性定义get/set方法进行数据监听。而在getter方法中,使用Dep类的depend方法来将订阅者添加到dep实例的subs队列中,将该属性与订阅者联系起来。在setter方法中,使用Dep类的notify方法,通知其subs队列中所有的订阅者进行更新操作。

    最后,对于问题“为什么要使用observe构造函数创建obs对象,并将vm._data和data和obs连等?”,这是因为observe构造函数创建的obs对象可以进行递归监听,实现对整个对象的监听,而连等可以将_data和data和obs的引用指向同一块内存地址,这样修改_data和data中的值时,都指向一个地址,可以互相影响,实现对data数据的监听。