响应式系统的作用与实现(读书笔记)

133 阅读3分钟

设计一个完善的响应式系统( 读书笔记 -《vuejs设计与实现》)

  • 前置知识点 Proxy

ECMAScript 6 新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。具体地 说,可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对 目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

  • 前置知识点 WeakMap

1. ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型 2. WeakMap对key是弱引用,不影响垃圾回收期的工作 3. WeakMap经常用于存储那些只有当key所引用的对象存在时才有价值的信息

  • 前置知识点 Map

1. 内存占用: Object 和 Map 的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量 都会随键的数量线性增加。批量添加或删除键/值对则取决于各浏览器对该类型内存分配的工程实现。 不同浏览器的情况不同,但给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键/值对。

2. 插入性能: 向 Object 和 Map 中插入新键/值对的消耗大致相当,不过插入 Map 在所有浏览器中一般会稍微快 一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操 作,那么显然 Map 的性能更佳。

3. 查找速度: 与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对, 则 Object 有时候速度更快。在把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏 览器引擎可以进行优化,在内存中使用更高效的布局。这对 Map 来说是不可能的。对这两个类型而言, 择 Object 更好一些。

4. 删除性能: 使用 delete 删除 Object 属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此, 出现了一些伪删除对象属性的操作,包括把属性值设置为 undefined 或 null。但很多时候,这都是一 种讨厌的或不适宜的折中。而对大多数浏览器引擎来说,Map 的 delete()操作都比插入和查找更快。 如果代码涉及大量删除操作,那么毫无疑问应该选择 Map。


// 原始数据类型
type dataType = {
  text: string;
  ok: boolean;
};

type effectFnType = {
  (): void;
  deps: Array<Function>;
};

// 用全局变量存储被注册的副作用函数
let activeEffect: effectFnType;

// 存储副作用函数的桶
const bucket = new WeakMap();

// 原始数据
const data = { text: "hello world", ok: true } as dataType;

//将副作用函数复制给activeEffect
function effect(fn: Function): void {
  const effectFn: effectFnType = () => {
    cleanup(effectFn);
    activeEffect = effectFn;
    fn();
  };
  //用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = [];
  //执行副作用函数
  effectFn();
}

const obj = new Proxy(data, {
  get<T extends dataType, K extends keyof T>(
    target: T,
    key: K
  ): T[K] | undefined {
    track(target, key);
    return target[key];
  },
  set<T extends dataType, K extends keyof T>(target: T, key: K, newVal: T[K]) {
    target[key] = newVal;
    return trigger(target, key);
  },
});


effect(() => {
  console.log("run");
  document.body.innerText = obj.ok ? obj.text : "not";
});
setTimeout(() => {
  obj.text = "你好呀";
}, 3000);


// 拦截函数内调用 -> 函数追踪变化
function track<T extends dataType, K extends keyof T>(target: T, key: K) {
  if (!activeEffect) return;
  let depsMap = bucket.get(target);
  if (!depsMap) bucket.set(target, (depsMap = new Map()));
  let deps = depsMap.get(key);
  if (!deps) depsMap.set(key, (deps = new Set()));
  deps.add(activeEffect);
  // deps 是与当前副作用函数存在联系的依赖集合
  activeEffect.deps.push(deps);
}

//拦截函数内 -> 函数触发变化
function trigger<T extends dataType, K extends keyof T>(
  target: T,
  key: K
): boolean {
  const depsMap: WeakMap<Object, Array<Function>> = bucket.get(target);
  if (!depsMap) return false;
  const effects = depsMap.get(key);
  const effectToRun = new Set(effects);
  effectToRun.forEach((effectFn) => effectFn());
  return true;
}

function cleanup(effectFn: effectFnType) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 依赖收集
    const deps = effectFn.deps[i];
    // 在集合中移除依赖
    deps.delete(effectFn);
  }
  // 重置 数组
  effectFn.deps.length = 0;
}
export {};