Vue2-响应式原理

121 阅读1分钟

响应式原理

Vue2 是采用数据劫持配合发布者-订阅者模式的方式实现响应式的。通过Object.defineProperty 来劫持各个属性的 settergetter,在数据变动时,发布消息给依赖收集器 Dep 去通知观察者 Watcher,并由 Watcher 执行更新视图的回调函数。

Vue3 使用 Proxy 取代 Object.defineProperty 的原因
1)无法监听新增属性和删除属性 :使用 Object.defineProperty 可以监听已有属性的变化,但是它无法监听新增属性和删除属性。2)无法监听数组下标的变化 :使用 Object.defineProperty 监听数组时,只能监听到数组元素的值的变化,而无法监听数组下标的变化。

响应式原理图

vue2-响应式原理.tif

实现代码

// 数据劫持
function defineReactive(data, key, value) {
  let dep = new Dep();

  // defineProperty 数据劫持
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    // 通知收集 - addWatcher()
    get() {
      console.log('getter--', value);
      if (Dep.target) {
        dep.addWatcher(Dep.target);
      }
      return value;
    },
    // 通知变化 - notify()
    set(newValue) {
      if (newValue !== value) {
        value = newValue;
        dep.notify();
      }
    },
  });
}

// Watcher 监听器
class Watcher {
  constructor(vm, key, callback) {
    this.vm = vm;
    this.key = key;
    this.callback = callback;

    // 将watcher赋值给Dep类的静态属性target
    Dep.target = this;
    // 触发一次getter,进行依赖收集
    console.log(this.vm.data[this.key],'---value');
    // 重置Dep类的静态属性target
    Dep.target = null;
  }

  // 通过回调函数更新视图
  update() {
    this.callback.call(this.vm, this.vm.data[this.key]);
  }
}

// 收集订阅器
class Dep {
  constructor() {
    this.watchers = [];
  }

  // 添加订阅者
  addWatcher(watcher) {
    this.watchers.push(watcher);
  }

 // 通知更新
  notify() {
    this.watchers.forEach((watcher) => {
      watcher.update();
    });
  }
}

class Vue {
  constructor(options) {
    this.data = options.data;

    console.log(this.data, '--1');

    // 实现数据劫持
    Object.keys(this.data).forEach((key) => {
      defineReactive(this.data, key, this.data[key]);
    });

    // 创建Watcher对象,监听数据变化
    new Watcher(this, 'message', this.updateMessage);

    // 更新视图
    setTimeout(() => {
      this.data.message = 'Hello, Vue!';
      console.log(this.data, '--2');
    }, 2000);
  }

  // 数据变化时的回调函数
  updateMessage(newValue) {
    console.log('Updated message:', newValue);
  }
}
// 测试
const vm = new Vue({
  data: {
    message: 'Hello, World!',
  },
});

// 输出
/*
{message: 'Hello, World!'} '--1'
getter-- Hello, World!
Hello, World! ---value
getter-- Hello, Vue!
Updated message: Hello, Vue!
{message: 'Hello, Vue!'} '--2'
*/