跳转到内容

读与写的深度思考

刀刀

4/10/2025

0 字

0 分钟

上文主要是针对如何监听对象的变化,建立联系,初步实现了读和写的操作。但是还需要深度思考一下,是否有需要优化的点。

首先看一个代码示例:

js
const obj = {
  a: 1,
  get b() {
    return this.a + 2
  }
}

const data = reactive(obj)

function fn () {
  console.log(data.b)
}

fn()

上方代码中,属性 b 用到了属性 a 的值,预期应该会收集 ba 的依赖,运行代码后查看控制台打印,发现只打印了 b 的依赖收集。

这是因为 b 是一个 getter,在获取 b 的值时,底层会执行 getter 函数,而 this 指向的是 obj ,而不是 proxy 代理对象。所以 a 的依赖收集没有生效。

解决方法是使用 Reflect 反射机制,将 getter 函数中的 this 替换为 proxy 代理对象。

js
import { track, trigger } from './effect.js'

const weakmap = new WeakMap() // 存储依赖关系

export function reactive(v) {
  if (!isObject(v)) {
    // 如果传入的不是对象,则包装成对象
  }
  if (weakmap.has(v)) {
    return weakmap.get(v) // 如果已经监听过了,则直接返回
  }
  const proxy = new Proxy(v, {
    get(target, key, receiver) {
      track(target, key) // 读取数据时,执行相关操作
      return target[key] 
      return Reflect.get(target, key, receiver) // 使用 Reflect 反射机制,将 this 指向 proxy 代理对象
    },
    set(target, key, value) {
      trigger(target, key, value) // 修改数据时,执行相关操作
      return Reflect.set(target, key, value)
    }
  })

  weakmap.set(v, proxy) // 存储依赖关系
  return proxy
}

解决了一个问题,再来思考一下是否还有优化点,看一个代码示例:

js
const obj = {
  a: 1,
  b: {
    c: 2
  }
}

const data = reactive(obj)

function fn () {
  console.log(data.b)
}

fn()

上方代码中,b 是一个对象,cb 的属性,预期应该会收集 cb 的依赖,运行代码后查看控制台打印,发现只打印了 b 的依赖收集。这是因为 b 是一个对象,这个对象没有被代理,所以 c 的依赖收集没有生效。

因此需要做到深度监听,如果该属性是一个对象,则递归代理。

js
import { track, trigger } from './effect.js'

const weakmap = new WeakMap() // 存储依赖关系

export function reactive(v) {
  if (!isObject(v)) {
    // 如果传入的不是对象,则包装成对象
  }
  if (weakmap.has(v)) {
    return weakmap.get(v) // 如果已经监听过了,则直接返回
  }
  const proxy = new Proxy(v, {
    get(target, key, receiver) {
      track(target, key) // 读取数据时,执行相关操作
      return Reflect.get(target, key, receiver) // 使用 Reflect 反射机制,将 this 指向 proxy 代理对象
      const result = Reflect.get(target, key, receiver) // 使用 Reflect 反射机制,将 this 指向 proxy 代理对象
      if (isObject(result)) {  
        return reactive(result) // 深度监听
      }  
      return result  
    },
    set(target, key, value) {
      trigger(target, key, value) // 修改数据时,执行相关操作
      return Reflect.set(target, key, value)
    }
  })

  weakmap.set(v, proxy) // 存储依赖关系
  return proxy
}

还有一种操作,虽然看起来不是读取属性点值,但是也是在判断依赖收集关系,示例代码如下:

js
const obj = {
  a: 1,
  b: {
    c: 2
  }
}

const data = reactive(obj)

function fn () {
  'a' in data
}

fn()

上方代码中,in 操作符会触发 has 拦截器,因此需要在 has 拦截器中收集依赖。

js
import { track, trigger } from './effect.js'

const weakmap = new WeakMap() // 存储依赖关系

export function reactive(v) {
  if (!isObject(v)) {
    // 如果传入的不是对象,则包装成对象
  }
  if (weakmap.has(v)) {
    return weakmap.get(v) // 如果已经监听过了,则直接返回
  }
  const proxy = new Proxy(v, {
    get(target, key, receiver) {
      track(target, key) // 读取数据时,执行相关操作
      const result = Reflect.get(target, key, receiver) // 使用 Reflect 反射机制,将 this 指向 proxy 代理对象
      if (isObject(result)) {
        return reactive(result) // 深度监听
      }
      return result
    },
    set(target, key, value) {
      trigger(target, key, value) // 修改数据时,执行相关操作
      return Reflect.set(target, key, value)
    },
    has(target, key) { 
      return Reflect.has(target, key) // 判断对象是否有对应属性
    } 
  })

  weakmap.set(v, proxy) // 存储依赖关系
  return proxy
}

目前能思考的点都思考了,暂时可以告一段落,后续如果有需要补充的点,再补充。

类型枚举

可以看到,有一些操作,比如 getsethas 等,这些操作在 Proxy 中都可以拦截,但是并不是所有的操作都需要拦截,因此需要枚举一下,哪一些操作需要拦截,哪一些操作又需要做啥处理。

js
export const TrackOpTypes = {
  GET: 'get', // 读取数据时,执行相关操作
  HAS: 'has', // 判断对象是否有对应属性
  ITERATE: 'iterate', // 遍历操作
}

export const TriggerOpTypes = {
  SET: 'set', // 修改数据时,执行相关操作
  ADD: 'add', // 添加数据时,执行相关操作
  DELETE: 'delete', // 删除数据时,执行相关操作
  CLEAR: 'clear', // 清空数据时,执行相关操作
}