vue的watch和computed的详细原理结合代码解析

88 阅读3分钟

Vue 的 computed 的实现原理及代码解析

Vue 的 computed 属性的实现原理主要涉及依赖追踪、缓存机制和响应式更新。下面是一个简化的代码解析,帮助理解其实现原理。

依赖追踪和缓存机制

  1. 依赖追踪

    • Vue 使用 Object.defineProperty 或 Proxy 来拦截对响应式数据的访问。
    • 当访问计算属性时,Vue 会追踪其依赖的数据。
  2. 缓存机制

    • 计算属性的结果会被缓存,只有当依赖的数据发生变化时,才会重新计算。
  3. 响应式更新

    • 当依赖的数据发生变化时,Vue 会通知相关的 Watcher 对象,重新计算计算属性的值。

简化代码解析

以下是一个简化的 Vue 计算属性实现示例:

class Dep {
  constructor() {
    this.subs = [];
  }

  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target);
    }
  }

  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

Dep.target = null;

class Watcher {
  constructor(getter, callback) {
    this.getter = getter;
    this.callback = callback;
    this.value = this.get();
  }

  get() {
    Dep.target = this;
    const value = this.getter();
    Dep.target = null;
    return value;
  }

  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.callback.call(this, this.value, oldValue);
  }
}

class ComputedProperty {
  constructor(getter) {
    this.getter = getter;
    this.dep = new Dep();
    this.value = null;
    this.dirty = true;
    this.watcher = new Watcher(this.get.bind(this), this.update.bind(this));
  }

  get() {
    if (this.dirty) {
      this.value = this.getter();
      this.dirty = false;
    }
    if (Dep.target) {
      this.dep.depend();
    }
    return this.value;
  }

  update() {
    this.dirty = true;
    this.dep.notify();
  }
}

// 示例使用
const data = {
  firstName: 'John',
  lastName: 'Doe'
};

const depFirstName = new Dep();
const depLastName = new Dep();

Object.defineProperty(data, 'firstName', {
  get() {
    depFirstName.depend();
    return 'John';
  },
  set(newValue) {
    if (newValue !== 'John') {
      depFirstName.notify();
    }
  }
});

Object.defineProperty(data, 'lastName', {
  get() {
    depLastName.depend();
    return 'Doe';
  },
  set(newValue) {
    if (newValue !== 'Doe') {
      depLastName.notify();
    }
  }
});

const computedFullName = new ComputedProperty(() => {
  depFirstName.depend();
  depLastName.depend();
  return `${data.firstName} ${data.lastName}`;
});

const watcherFullName = new Watcher(() => {
  return computedFullName.get();
}, (newValue, oldValue) => {
  console.log(`fullName changed from ${oldValue} to ${newValue}`);
});

console.log(watcherFullName.value); // John Doe

data.firstName = 'Jane'; // fullName changed from John Doe to Jane Doe

关键点解析

  1. Dep 类

    • depend 方法用于将当前的 Watcher 添加到依赖列表中。
    • notify 方法用于通知所有订阅的 Watcher 更新。
  2. Watcher 类

    • get 方法用于获取值,并在获取过程中收集依赖。
    • update 方法用于在依赖变化时重新计算值。
  3. ComputedProperty 类

    • get 方法用于获取计算属性的值,并在需要时重新计算。
    • update 方法用于标记计算属性为脏状态,以便在下次访问时重新计算。

通过这些类和方法,Vue 实现了计算属性的依赖追踪、缓存和响应式更新。

Vue 的 watch的实现原理及原理实现代码解析

Vue 的 watch 选项用于观察和响应 Vue 实例上的数据变化。其实现原理主要依赖于 Vue 的响应式系统。下面详细解析其实现原理及代码实现。

实现原理

  1. 响应式系统:Vue 通过 Object.defineProperty 或 Proxy 对数据进行拦截,监听数据的变化。
  2. 依赖收集:当组件渲染时,会访问响应式数据,Vue 会收集这些数据的依赖(即哪些地方使用了这些数据)。
  3. 触发更新:当数据发生变化时,Vue 会通知所有依赖该数据的地方进行更新。
  4. Watcherwatch 选项内部会创建一个 Watcher 实例,当监听的数据变化时,Watcher 会执行回调函数。

代码解析

Vue 的 watch 实现主要涉及 Watcher 类和响应式系统的集成。以下是一个简化的代码解析。

1. 响应式系统

Vue 使用 Object.defineProperty 来拦截数据的访问和修改:

function defineReactive(obj, key, val) {
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      dep.depend();
      return val;
    },
    set: function reactiveSetter(newVal) {
      if (newVal === val) return;
      val = newVal;
      dep.notify();
    }
  });
}

2. Dep 类

Dep 类用于管理依赖关系:

class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(sub) {
    this.subs.push(sub);
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }

  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

Dep.target = null;

3. Watcher 类

Watcher 类用于观察数据变化并执行回调函数:

class Watcher {
  constructor(vm, expOrFn, cb, options) {
    this.vm = vm;
    this.getter = parsePath(expOrFn);
    this.cb = cb;
    this.value = this.get();
  }

  get() {
    Dep.target = this;
    const value = this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
  }

  addDep(dep) {
    dep.addSub(this);
  }

  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
}

function parsePath(path) {
  const segments = path.split('.');
  return function(obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return;
      obj = obj[segments[i]];
    }
    return obj;
  };
}

4. 初始化 Watcher

在 Vue 实例初始化时,会根据 watch 选项创建 Watcher 实例:

function initWatch(vm, watch) {
  for (const key in watch) {
    const handler = watch[key];
    if (Array.isArray(handler)) {
      handler.forEach(handler => createWatcher(vm, key, handler));
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

function createWatcher(vm, expOrFn, handler, options) {
  if (typeof handler === 'string') {
    handler = vm[handler];
  }
  return vm.$watch(expOrFn, handler, options);
}

Vue.prototype.$watch = function(expOrFn, cb, options) {
  const vm = this;
  options = options || {};
  const watcher = new Watcher(vm, expOrFn, cb, options);
  if (options.immediate) {
    cb.call(vm, watcher.value);
  }
  return function unwatchFn() {
    watcher.teardown();
  };
};

总结

  1. 响应式系统:通过 Object.defineProperty 拦截数据访问和修改。
  2. Dep 类:管理依赖关系,收集和通知依赖。
  3. Watcher 类:观察数据变化并执行回调函数。
  4. 初始化 Watcher:在 Vue 实例初始化时,根据 watch 选项创建 Watcher 实例。

通过这些步骤,Vue 实现了对数据变化的监听和响应。