手写响应式

47 阅读3分钟

1. Vue3(Proxy)

  1. 使用 Proxy 接管我们要监听的对象

这样我们就可以在我们定义的代理对象上面执行对应的操作。

const obj = {
  name: 'xxx',
  code: 'xxx'
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
  }
})
  1. 创建依赖类
class Depend {
  constructor() {
    this.reactiveFns = []
  }
  
  // 将需要执行的函数添加到执行队列中
  addDepend(reactiveFn) {
    this.reactiveFns.push(reactiveFn)
  }
  
  // 发送通知
  notify() {
    this.reactiveFns.map(fn => fn())
  }
}
  1. 封装一个响应式函数
const depend = new Depend()
watch(fn => {
  depend.addDepend(fn)
})
  1. 在设置完新的值后执行对应的函数
...
set(target, key, newValue, receiver) {
  depend.notify()
  ...
}
  1. 为不同的对象设置不同的依赖

因为全局不可能只有一个对象,所以我们根据不同的对象建立不同的 Map;在 Map 对象为不同的属性建立不同的 Depend 类。

const targetMap = new WeakMap()
const getDepend(target, key) {
  let map = targetMap.get(target)
  // 如果一开始没有对应的 Map, 我们需要手动创建一个 Map
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }
  
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  
  return depend
}
  1. 更新 set 函数
set(target, key, newValue, receiver) {
  const depend = getDepnd(target, key)
  depend.notify()
  ...
}
  1. 更新响应式函数

在这一步,我们在监听对应的函数时,即在 get 时并不知道要执行什么函数,但是在 watchFn 时知道执行了什么函数,所以我们创建一个全局变量,拿到此时要执行的函数

let activeReactiveFn = null
watchFn(fn => {
  activeReactiveFn = fn
  fn()
  // 防止内存泄漏,在执行完毕后记得清空引用
  activeReactiveFn = null
})

get(target, key) {
  const depend = getDepend(target, key)
  if (activeReactiveFn) {
    depend.addDepend(activeReactiveFn)
  }
}
  1. 优化代码
let activeReactiveFn = null

// 为防止同一个属性执行多次函数,我们使用 Set 保存 reactiveFns,而不是数组。
class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }
  
  // 将需要执行的函数添加到执行队列中
  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }
  
  // 发送通知
  notify() {
    Array.from(this.reactiveFns).map(fn => fn())
  }
}
  1. 完整代码
let activeReactiveFn = null

/**
 * depend 优化
 * 1> 使用 depend 类方法
 * 2> 使用 set 而不是 数组
 */

class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    Array.from(this.reactiveFns).map(fn => fn())
  }
}

// 封装一个获取 depend 的函数
/**
 * 需要用到两层 Map
 * 第一层 Map 是保存 不同的对象
 * 第二层 Map 是保存 上述对象中不同属性值的依赖
 */
const targetMap = new WeakMap()
const getDepend = (target, key) => {
  // 根据 target 获取 map 的过程
  let map = targetMap.get(target)
  // 如果第一层 Map 首次没找到,新建一个 Map
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  // 如果第二层 Map 首次没找到依赖,新建 Depend
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

// 封装一个响应式的函数
const watchFn = fn => {
  activeReactiveFn = fn
  fn()
  // 当此全局变量不用时,要置为空,防止内存泄漏
  activeReactiveFn = null
}

const obj = {}

// 定义一个 Proxy 对象
const reactive = (obj) => {
  return new Proxy(obj, {
    get(target, key, reveiver) {
      // 根据 target 、key 获取对应的依赖
      const depend = getDepend(target, key)
      // 给 depend 中添加响应函数
      // depend.addDepend(activeReactiveFn)
      depend.depend()

      return Reflect.get(target, key, reveiver)
    },
    set(target, key, newValue, reveiver) {
      Reflect.set(target, key, newValue, reveiver)
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}
const objProxy = new Proxy(obj, {
  get(target, key, reveiver) {
    // 根据 target 、key 获取对应的依赖
    const depend = getDepend(target, key)
    // 给 depend 中添加响应函数
    // depend.addDepend(activeReactiveFn)
    depend.depend()

    return Reflect.get(target, key, reveiver)
  },
  set(target, key, newValue, reveiver) {
    Reflect.set(target, key, newValue, reveiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})

// 为防止多次调用,使用 set
watchFn(() => {
  console.log(obj.name, '--------------')
  console.log(obj.name, '++++++++++++++')
})

const info = reactive({
  name: 'xxx',
  code: 'xxx'
})

2. Vue2(Object.defineProperty)

const reactive = obj => {
  Object.keys(obj).map(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get() {
        // 根据 target 、key 获取对应的依赖
        const depend = getDepend(obj, key)
        depend.depend()

        return value
      },
      set(newValue) {
        value = newValue
        const depend = getDepend(target, key)
        depend.notify()
      }
    })
  })

  return obj
}