异步更新队列

在上面 Vue2.x 响应式原理的代码里还是有点问题的。

先捞一遍代码:

let x;

let f = function (v) {
  return v * 100;
};

let active;

const onXChange = (cb) => {
  active = cb;
  active();
  active = null;
};
class Dep {
  deps = new Set();
  depend() {
    if (active) {
      this.deps.add(active);
    }
  }
  notify() {
    this.deps.forEach((dep) => dep());
  }
}

const ref = (value) => {
  let initValue = value;
  let deps = new Dep();

  return Object.defineProperty({}, "value", {
    get() {
      deps.depend();
      return initValue;
    },
    set(newVal) {
      initValue = newVal;
      deps.notify();
    },
  });
};

x = ref(1);

onXChange(() => {
  console.log(f(x.value));
});

x.value = 2;
x.value = 3;

依赖多个变量的情况下的性能问题

简单删减一波,然后让模板依赖于三个变量:

let active;

const watch = (cb) => {
  active = cb;
  active();
  active = null;
};
class Dep {
  deps = new Set();
  depend() {
    if (active) {
      this.deps.add(active);
    }
  }
  notify() {
    this.deps.forEach((dep) => dep());
  }
}

const ref = (value) => {
  let initValue = value;
  let deps = new Dep();

  return Object.defineProperty({}, "value", {
    get() {
      deps.depend();
      return initValue;
    },
    set(newVal) {
      initValue = newVal;
      deps.notify();
    },
  });
};

let x = ref(1);
let y = ref(2);
let z = ref(3);

watch(() => {
  let str = `${x.value} --- ${y.value} --- ${z.value}`;
  document.write(str);
  console.log(str);
});

x.value = 2;
y.value = 3;
z.value = 3;

打开控制台你会发现,每一次值变化的时候,都会触发一次onXChange

那么问题来了。如果在一个函数中,修改了多个变量,那岂不是页面就要进行多次渲染吗?那么性能肯定下降了。

那么这怎么办呢。

异步更新队列

那么就得用到异步更新队列了。

我们知道 JavaScript 有宏任务和微任务。所有的微任务都是在宏任务执行完成之后再执行的。

那么我们可以定义一个nextTick函数,用来在宏任务执行完成之后执行微任务。 即,在所有的赋值完成之后,再执行onChange方法。

const netxtTick = (cb) => Promise.resolve().then(cb);

我们还需要一个队列用来保证先进来的onChange先执行:

let queue = [];
// 增加任务的方法
let queueJob = (job) => {
  // 如果任务已经被添加过,则不添加了
  if (!queue.includes(job)) {
    queue.push(job);
    // 在微任务中执行所有回调
    nextTick(flushJobs);
  }
};
// 执行任务的方法
let flushJobs = () => {
  let job;
  // 如果队列中第一个始终有任务,则取出来执行
  while ((job = queue.shift()) !== undefined) {
    job();
  }
};

整体代码如下:

let active;

const watch = (cb) => {
  active = cb;
  active();
  active = null;
};

let nextTick = (cb) => Promise.resolve().then(cb);

let queue = [];
let queueJob = (job) => {
  if (!queue.includes(job)) {
    queue.push(job);
    nextTick(flushJobs);
  }
};
let flushJobs = () => {
  let job;
  while ((job = queue.shift()) !== undefined) {
    job();
  }
};
class Dep {
  deps = new Set();
  depend() {
    if (active) {
      this.deps.add(active);
    }
  }
  notify() {
    // 注意notify内部变更了
    this.deps.forEach((dep) => queueJob(dep));
  }
}

const ref = (value) => {
  let initValue = value;
  let deps = new Dep();

  return Object.defineProperty({}, "value", {
    get() {
      deps.depend();
      return initValue;
    },
    set(newVal) {
      initValue = newVal;
      deps.notify();
    },
  });
};

let x = ref(1);
let y = ref(2);
let z = ref(3);

watch(() => {
  let str = `${x.value} --- ${y.value} --- ${z.value}`;
  document.write(str);
  console.log(str);
});

x.value = 2;
y.value = 3;
z.value = 3;