《Vue.js设计与实现》心得与总结 04:响应系统-非原始值的响应式方案(Set、Map篇)

代理 Set 和 Map 的基本实现与实现代理 Set 和 Map 的响应联系

集合类型指 Set、Map、WeakSet 以及 WeakMap 。集合类型不同于普通对象,它有特定的数据操作方法。当使用 Proxy 代理集合类型的数据时要格外注意,例如,集合类型的 size 属性是一个访问器属性,当通过代理对象访问 size 属性时,由于代理对象本身并没有部署 [[SetData]] 这样的内部槽,所以会发生错误。另外,通过代理对象执行集合类型的操作方法时,要注意这些方法执行时的 this 指向,我们需要在 get 拦截函数内通过 .bind 函数为这些方法绑定正确的 this 值。同时,为了实现集合类型响应式数据,我们需要通过"重写"集合方法的方式来实现自定义的能力,当 Set 集合的 add 方法执行时,需要调用 trigger 函数触发响应。

代理 Set 和 Map 的基本实现的

js 复制代码
// 存储副作用函数的桶
const bucket = new WeakMap()

const ITERATE_KEY = Symbol()

const arrayInstrumentations = {}

;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function(...args) {
    // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
    let res = originMethod.apply(this, args)

    if (res === false || res === -1) {
      // res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
      res = originMethod.apply(this.raw, args)
    }
    // 返回最终结果
    return res
  }
})

// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
  // 取得原始 push 方法
  const originMethod = Array.prototype[method]
  // 重写
  arrayInstrumentations[method] = function(...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false
    // push 方法的默认行为
    let res = originMethod.apply(this, args)
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true
    return res
  }
})

// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    deleteProperty(target, key) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 检查被操作的属性是否是对象自己的属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      // 使用 Reflect.deleteProperty 完成属性的删除
      const res = Reflect.deleteProperty(target, key)
      if (res && hadKey) {
        // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
        trigger(target, key, 'DELETE')
      }
      return res
    },
    ownKeys(target) {
      // 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
      track(target,  Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // 拦截读取操作
    get(target, key, receiver) {
      if (key === 'size') {
        return Reflect.get(target, key, target)
      }
      return target[key].bind(target)
    },
    // 拦截设置操作
    set(target, key, newVal, receiver) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 先获取旧值
      const oldVal = target[key]

      // 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
      const type = Array.isArray(target)
        // 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
        // 如果是,则视作 SET 操作,否则是 ADD 操作
        ? Number(key) < target.length ? 'SET' : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      // 设置属性值
      const res = Reflect.set(target, key, newVal, receiver)

      // target === receiver.raw 说明 receiver 就是 target 的代理对象
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 增加第四个参数,即触发响应的新值
          trigger(target, key, type, newVal)
        }
      }
      return res
    },
    has(target, key) {
      console.error('拦截 in 操作符')
      track(target, key)
      return Reflect.has(target, key)
    }
  })
}

// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()

function reactive(obj) {
  // 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
  const existionProxy = reactiveMap.get(obj)
  if (existionProxy) return existionProxy

  // 否则,创建新的代理对象
  const proxy = createReactive(obj)
  // 存储到 Map 中,从而避免重复创建
  reactiveMap.set(obj, proxy)

  return proxy
}

function shallowReactive(obj) {
  return createReactive(obj, true)
}

function readonly(obj) {
  return createReactive(obj, false, true /* 只读 */)
}

function shallowReadonly(obj) {
  return createReactive(obj, true /* shallow */, true)
}

// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
  // 当禁止追踪时,直接返回
  if (!activeEffect || !shouldTrack) 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 中
  deps.add(activeEffect)
  // deps 就是一个与当前副作用函数存在联系的依赖集合
  // 将其添加到 activeEffect.deps 数组中
  activeEffect.deps.push(deps)
}

//  为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  // 取得与 key 相关联的副作用函数
  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  // 将与 key 相关联的副作用函数添加到 effectsToRun
  effects && effects.forEach(effectFn => {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effectFn !== activeEffect) {  // 新增
      effectsToRun.add(effectFn)
    }
  })

  // 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
  if (type === 'ADD' && Array.isArray(target)) {
    // 取出与 length 相关联的副作用函数
    const lengthEffects = depsMap.get('length')
    // 将这些副作用函数添加到 effectsToRun 中,待执行
    lengthEffects && lengthEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  // 如果操作目标是数组,并且修改了数组的 length 属性
  if (Array.isArray(target) && key === 'length') {
    // 对于索引大于或等于新的 length 值的元素,
    // 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
    depsMap.forEach((effects, key) => {
      if (key >= newVal) {
        effects.forEach(effectFn => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }

  //  当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
  if (type === 'ADD' || type === 'DELETE') {
    // 取得与 ITERATE_KEY 相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)
    // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
    iterateEffects && iterateEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

function cleanup(effectFn) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i]
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn)
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0
}

// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []

// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    // 调用 cleanup 函数完成清除工作
    cleanup(effectFn)
    // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
    activeEffect = effectFn
    // 在调用副作用函数之前将当前副作用函数压入栈中
    effectStack.push(effectFn)
    fn()
    // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  // 将 options 挂载到 effectFn 上
  effectFn.options = options
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = []
  // 执行副作用函数
  effectFn()
}


const p = reactive(new Set([1, 2, 3]))
console.log(p.size) // 3
p.delete(1)

实现代理 Set 和 Map 的响应联系

js 复制代码
// 存储副作用函数的桶
const bucket = new WeakMap()

const ITERATE_KEY = Symbol()

const arrayInstrumentations = {}

;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function(...args) {
    // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
    let res = originMethod.apply(this, args)

    if (res === false || res === -1) {
      // res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
      res = originMethod.apply(this.raw, args)
    }
    // 返回最终结果
    return res
  }
})

// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
  // 取得原始 push 方法
  const originMethod = Array.prototype[method]
  // 重写
  arrayInstrumentations[method] = function(...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false
    // push 方法的默认行为
    let res = originMethod.apply(this, args)
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true
    return res
  }
})

// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
  add(key) {
    // this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
    const target = this.raw
    // 先判断值是否已经存在
    const hadKey = target.has(key)
    const res = target.add(key)
    // 只有在值不存在的情况下,才需要触发响应
    if (!hadKey) {
      trigger(target, key, 'ADD')
    }
    // 返回操作结果
    return res
  },
  delete(key) {
    const target = this.raw
    const hadKey = target.has(key)
    const res = target.delete(key)
    // 当要删除的元素确实存在时,才触发响应
    if (hadKey) {
      trigger(target, key, 'DELETE')
    }
    return res
  }
}

// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    deleteProperty(target, key) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 检查被操作的属性是否是对象自己的属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      // 使用 Reflect.deleteProperty 完成属性的删除
      const res = Reflect.deleteProperty(target, key)
      if (res && hadKey) {
        // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
        trigger(target, key, 'DELETE')
      }
      return res
    },
    ownKeys(target) {
      // 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
      track(target,  Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // 拦截读取操作
    get(target, key, receiver) {
      // 如果读取的是 raw 属性,则返回原始数据对象 target
      if (key === 'raw') return target
      if (key === 'size') {
        // 调用 track 函数建立响应联系
        track(target, ITERATE_KEY)
        return Reflect.get(target, key, target)
      }
      // 返回定义在 mutableInstrumentations 对象下的方法
      return mutableInstrumentations[key]
    },
    // 拦截设置操作
    set(target, key, newVal, receiver) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 先获取旧值
      const oldVal = target[key]

      // 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
      const type = Array.isArray(target)
        // 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
        // 如果是,则视作 SET 操作,否则是 ADD 操作
        ? Number(key) < target.length ? 'SET' : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      // 设置属性值
      const res = Reflect.set(target, key, newVal, receiver)

      // target === receiver.raw 说明 receiver 就是 target 的代理对象
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 增加第四个参数,即触发响应的新值
          trigger(target, key, type, newVal)
        }
      }
      return res
    },
    has(target, key) {
      console.error('拦截 in 操作符')
      track(target, key)
      return Reflect.has(target, key)
    }
  })
}

// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()

function reactive(obj) {
  // 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
  const existionProxy = reactiveMap.get(obj)
  if (existionProxy) return existionProxy

  // 否则,创建新的代理对象
  const proxy = createReactive(obj)
  // 存储到 Map 中,从而避免重复创建
  reactiveMap.set(obj, proxy)

  return proxy
}

function shallowReactive(obj) {
  return createReactive(obj, true)
}

function readonly(obj) {
  return createReactive(obj, false, true /* 只读 */)
}

function shallowReadonly(obj) {
  return createReactive(obj, true /* shallow */, true)
}

// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
  // 当禁止追踪时,直接返回
  if (!activeEffect || !shouldTrack) 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 中
  deps.add(activeEffect)
  // deps 就是一个与当前副作用函数存在联系的依赖集合
  // 将其添加到 activeEffect.deps 数组中
  activeEffect.deps.push(deps)
}

//  为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  // 取得与 key 相关联的副作用函数
  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  // 将与 key 相关联的副作用函数添加到 effectsToRun
  effects && effects.forEach(effectFn => {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effectFn !== activeEffect) {  // 新增
      effectsToRun.add(effectFn)
    }
  })

  // 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
  if (type === 'ADD' && Array.isArray(target)) {
    // 取出与 length 相关联的副作用函数
    const lengthEffects = depsMap.get('length')
    // 将这些副作用函数添加到 effectsToRun 中,待执行
    lengthEffects && lengthEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  // 如果操作目标是数组,并且修改了数组的 length 属性
  if (Array.isArray(target) && key === 'length') {
    // 对于索引大于或等于新的 length 值的元素,
    // 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
    depsMap.forEach((effects, key) => {
      if (key >= newVal) {
        effects.forEach(effectFn => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }

  //  当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
  if (type === 'ADD' || type === 'DELETE') {
    // 取得与 ITERATE_KEY 相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)
    // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
    iterateEffects && iterateEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

function cleanup(effectFn) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i]
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn)
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0
}

// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []

// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    // 调用 cleanup 函数完成清除工作
    cleanup(effectFn)
    // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
    activeEffect = effectFn
    // 在调用副作用函数之前将当前副作用函数压入栈中
    effectStack.push(effectFn)
    fn()
    // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  // 将 options 挂载到 effectFn 上
  effectFn.options = options
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = []
  // 执行副作用函数
  effectFn()
}


const p = reactive(new Set([1, 2, 3]))

effect(() => {
  // 在副作用函数内访问 size 属性
  console.log(p.size)
})

// 添加值为 4 的元素,应该触发响应
p.add(4)

代理 Set 和 Map 避免污染原始数据实现

我们把响应式数据设置到原始数据上的行为称为数据污染。它导致用户可以通过原始数据执行响应式相关操作。为了避免这类问题发生,我们通过响应式数据对象的 raw 属性来访问对应的原始数据对象,后续操作使用原始数据对象就可以了。

代理 Set 和 Map 避免污染原始数据的完整实现如下

js 复制代码
// 存储副作用函数的桶
const bucket = new WeakMap()

const ITERATE_KEY = Symbol()

const arrayInstrumentations = {}

;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function(...args) {
    // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
    let res = originMethod.apply(this, args)

    if (res === false || res === -1) {
      // res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
      res = originMethod.apply(this.raw, args)
    }
    // 返回最终结果
    return res
  }
})

// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
  // 取得原始 push 方法
  const originMethod = Array.prototype[method]
  // 重写
  arrayInstrumentations[method] = function(...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false
    // push 方法的默认行为
    let res = originMethod.apply(this, args)
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true
    return res
  }
})

// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
  get(key) {
    // 获取原始对象
    const target = this.raw
    // 判断读取的 key 是否存在
    const had = target.has(key)
    // 追踪依赖,建立响应联系
    track(target, key)
    // 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
    // 则要返回使用 reactive 包装后的响应式数据
    if (had) {
      const res = target.get(key)
      return typeof res === 'object' ? reactive(res) : res
    }
  },
  set(key, value) {
    const target = this.raw
    const had = target.has(key)
    // 获取旧值
    const oldValue = target.get(key)
    // 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
    const rawValue = value.raw || value
    target.set(key, rawValue)
    // 如果不存在,则说明是 ADD 类型的操作,意味着新增
    if (!had) {
      trigger(target, key, 'ADD')
    } else if (oldValue !== value || (oldValue === oldValue && value === value)) {
      // 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
      trigger(target, key, 'SET')
    }
  },
  add(key) {
    // this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
    const target = this.raw
    // 先判断值是否已经存在
    const hadKey = target.has(key)
    const res = target.add(key)
    // 只有在值不存在的情况下,才需要触发响应
    if (!hadKey) {
      trigger(target, key, 'ADD')
    }
    // 返回操作结果
    return res
  },
  delete(key) {
    const target = this.raw
    const hadKey = target.has(key)
    const res = target.delete(key)
    // 当要删除的元素确实存在时,才触发响应
    if (hadKey) {
      trigger(target, key, 'DELETE')
    }
    return res
  }
}

// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    deleteProperty(target, key) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 检查被操作的属性是否是对象自己的属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      // 使用 Reflect.deleteProperty 完成属性的删除
      const res = Reflect.deleteProperty(target, key)
      if (res && hadKey) {
        // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
        trigger(target, key, 'DELETE')
      }
      return res
    },
    ownKeys(target) {
      // 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
      track(target,  Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // 拦截读取操作
    get(target, key, receiver) {
      // 如果读取的是 raw 属性,则返回原始数据对象 target
      if (key === 'raw') return target
      if (key === 'size') {
        // 调用 track 函数建立响应联系
        track(target, ITERATE_KEY)
        return Reflect.get(target, key, target)
      }
      // 返回定义在 mutableInstrumentations 对象下的方法
      return mutableInstrumentations[key]
    },
    // 拦截设置操作
    set(target, key, newVal, receiver) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 先获取旧值
      const oldVal = target[key]

      // 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
      const type = Array.isArray(target)
        // 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
        // 如果是,则视作 SET 操作,否则是 ADD 操作
        ? Number(key) < target.length ? 'SET' : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      // 设置属性值
      const res = Reflect.set(target, key, newVal, receiver)

      // target === receiver.raw 说明 receiver 就是 target 的代理对象
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 增加第四个参数,即触发响应的新值
          trigger(target, key, type, newVal)
        }
      }
      return res
    },
    has(target, key) {
      console.error('拦截 in 操作符')
      track(target, key)
      return Reflect.has(target, key)
    }
  })
}

// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()

function reactive(obj) {
  // 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
  const existionProxy = reactiveMap.get(obj)
  if (existionProxy) return existionProxy

  // 否则,创建新的代理对象
  const proxy = createReactive(obj)
  // 存储到 Map 中,从而避免重复创建
  reactiveMap.set(obj, proxy)

  return proxy
}

function shallowReactive(obj) {
  return createReactive(obj, true)
}

function readonly(obj) {
  return createReactive(obj, false, true /* 只读 */)
}

function shallowReadonly(obj) {
  return createReactive(obj, true /* shallow */, true)
}

// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
  // 当禁止追踪时,直接返回
  if (!activeEffect || !shouldTrack) 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 中
  deps.add(activeEffect)
  // deps 就是一个与当前副作用函数存在联系的依赖集合
  // 将其添加到 activeEffect.deps 数组中
  activeEffect.deps.push(deps)
}

//  为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  // 取得与 key 相关联的副作用函数
  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  // 将与 key 相关联的副作用函数添加到 effectsToRun
  effects && effects.forEach(effectFn => {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effectFn !== activeEffect) {  // 新增
      effectsToRun.add(effectFn)
    }
  })

  // 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
  if (type === 'ADD' && Array.isArray(target)) {
    // 取出与 length 相关联的副作用函数
    const lengthEffects = depsMap.get('length')
    // 将这些副作用函数添加到 effectsToRun 中,待执行
    lengthEffects && lengthEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  // 如果操作目标是数组,并且修改了数组的 length 属性
  if (Array.isArray(target) && key === 'length') {
    // 对于索引大于或等于新的 length 值的元素,
    // 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
    depsMap.forEach((effects, key) => {
      if (key >= newVal) {
        effects.forEach(effectFn => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }

  //  当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
  if (type === 'ADD' || type === 'DELETE') {
    // 取得与 ITERATE_KEY 相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)
    // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
    iterateEffects && iterateEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

function cleanup(effectFn) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i]
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn)
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0
}

// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []

// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    // 调用 cleanup 函数完成清除工作
    cleanup(effectFn)
    // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
    activeEffect = effectFn
    // 在调用副作用函数之前将当前副作用函数压入栈中
    effectStack.push(effectFn)
    fn()
    // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  // 将 options 挂载到 effectFn 上
  effectFn.options = options
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = []
  // 执行副作用函数
  effectFn()
}


// 原始 Map 对象 m
const m = new Map()
// p1 是 m 的代理对象
const p1 = reactive(m)

// p2 是另外一个代理对象
const p2 = reactive(new Map())
// 为 p1 设置一个键值对,值是代理对象 p2
p1.set('p2', p2)

effect(() => {
  // 注意,这里我们通过原始数据 m 访问 p2
  console.log(m.get('p2').size)
})

// 注意,这里我们通过原始数据 m 为 p2 设置一个键值对 foo --> 1
m.get('p2').set('foo', 1)

代理 Set 和 Map forEach 遍历的实现

集合的 forEach 方法与对象的 for...in 遍历类似,最大的不同体现在,当使用 for...in 遍历对象时,我们只关心对象的键是否变化,而不关心值;但使用 forEach 遍历集合时,我们既关心键的变化,也关心值的变化。

代理 Set 和 Map forEach 遍历的实现的完整实现如下

js 复制代码
// 存储副作用函数的桶
const bucket = new WeakMap()

const ITERATE_KEY = Symbol()

const arrayInstrumentations = {}

;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function(...args) {
    // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
    let res = originMethod.apply(this, args)

    if (res === false || res === -1) {
      // res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
      res = originMethod.apply(this.raw, args)
    }
    // 返回最终结果
    return res
  }
})

// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
  // 取得原始 push 方法
  const originMethod = Array.prototype[method]
  // 重写
  arrayInstrumentations[method] = function(...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false
    // push 方法的默认行为
    let res = originMethod.apply(this, args)
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true
    return res
  }
})

// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
  get(key) {
    // 获取原始对象
    const target = this.raw
    // 判断读取的 key 是否存在
    const had = target.has(key)
    // 追踪依赖,建立响应联系
    track(target, key)
    // 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
    // 则要返回使用 reactive 包装后的响应式数据
    if (had) {
      const res = target.get(key)
      return typeof res === 'object' ? reactive(res) : res
    }
  },
  set(key, value) {
    const target = this.raw
    const had = target.has(key)
    // 获取旧值
    const oldValue = target.get(key)
    // 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
    const rawValue = value.raw || value
    target.set(key, rawValue)
    // 如果不存在,则说明是 ADD 类型的操作,意味着新增
    if (!had) {
      trigger(target, key, 'ADD')
    } else if (oldValue !== value || (oldValue === oldValue && value === value)) {
      // 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
      trigger(target, key, 'SET')
    }
  },
  add(key) {
    // this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
    const target = this.raw
    // 先判断值是否已经存在
    const hadKey = target.has(key)
    const res = target.add(key)
    // 只有在值不存在的情况下,才需要触发响应
    if (!hadKey) {
      trigger(target, key, 'ADD')
    }
    // 返回操作结果
    return res
  },
  delete(key) {
    const target = this.raw
    const hadKey = target.has(key)
    const res = target.delete(key)
    // 当要删除的元素确实存在时,才触发响应
    if (hadKey) {
      trigger(target, key, 'DELETE')
    }
    return res
  },
  // 接收第二个参数
  forEach(callback, thisArg) {
    // wrap 函数用来把可代理的值转换为响应式数据
    const wrap = (val) => typeof val === 'object' ? reactive(val) : val
    // 取得原始数据对象
    const target = this.raw
    // 与 ITERATE_KEY 建立响应联系
    track(target, ITERATE_KEY)
    //  通过 target 调用原始 forEach 方法进行遍历
    target.forEach((v, k) => {
      // 通过 .call 调用 callback,并传递 thisArg
      callback.call(thisArg, wrap(v), wrap(k), this)
    })
  }
}

// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    deleteProperty(target, key) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 检查被操作的属性是否是对象自己的属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      // 使用 Reflect.deleteProperty 完成属性的删除
      const res = Reflect.deleteProperty(target, key)
      if (res && hadKey) {
        // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
        trigger(target, key, 'DELETE')
      }
      return res
    },
    ownKeys(target) {
      // 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
      track(target,  Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // 拦截读取操作
    get(target, key, receiver) {
      // 如果读取的是 raw 属性,则返回原始数据对象 target
      if (key === 'raw') return target
      if (key === 'size') {
        // 调用 track 函数建立响应联系
        track(target, ITERATE_KEY)
        return Reflect.get(target, key, target)
      }
      // 返回定义在 mutableInstrumentations 对象下的方法
      return mutableInstrumentations[key]
    },
    // 拦截设置操作
    set(target, key, newVal, receiver) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 先获取旧值
      const oldVal = target[key]

      // 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
      const type = Array.isArray(target)
        // 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
        // 如果是,则视作 SET 操作,否则是 ADD 操作
        ? Number(key) < target.length ? 'SET' : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      // 设置属性值
      const res = Reflect.set(target, key, newVal, receiver)

      // target === receiver.raw 说明 receiver 就是 target 的代理对象
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 增加第四个参数,即触发响应的新值
          trigger(target, key, type, newVal)
        }
      }
      return res
    },
    has(target, key) {
      console.error('拦截 in 操作符')
      track(target, key)
      return Reflect.has(target, key)
    }
  })
}

// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()

function reactive(obj) {
  // 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
  const existionProxy = reactiveMap.get(obj)
  if (existionProxy) return existionProxy

  // 否则,创建新的代理对象
  const proxy = createReactive(obj)
  // 存储到 Map 中,从而避免重复创建
  reactiveMap.set(obj, proxy)

  return proxy
}

function shallowReactive(obj) {
  return createReactive(obj, true)
}

function readonly(obj) {
  return createReactive(obj, false, true /* 只读 */)
}

function shallowReadonly(obj) {
  return createReactive(obj, true /* shallow */, true)
}

// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
  // 当禁止追踪时,直接返回
  if (!activeEffect || !shouldTrack) 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 中
  deps.add(activeEffect)
  // deps 就是一个与当前副作用函数存在联系的依赖集合
  // 将其添加到 activeEffect.deps 数组中
  activeEffect.deps.push(deps)
}

//  为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  // 取得与 key 相关联的副作用函数
  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  // 将与 key 相关联的副作用函数添加到 effectsToRun
  effects && effects.forEach(effectFn => {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effectFn !== activeEffect) {  // 新增
      effectsToRun.add(effectFn)
    }
  })

  // 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
  if (type === 'ADD' && Array.isArray(target)) {
    // 取出与 length 相关联的副作用函数
    const lengthEffects = depsMap.get('length')
    // 将这些副作用函数添加到 effectsToRun 中,待执行
    lengthEffects && lengthEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  // 如果操作目标是数组,并且修改了数组的 length 属性
  if (Array.isArray(target) && key === 'length') {
    // 对于索引大于或等于新的 length 值的元素,
    // 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
    depsMap.forEach((effects, key) => {
      if (key >= newVal) {
        effects.forEach(effectFn => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }

  //  当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
  if (
    type === 'ADD' ||
    type === 'DELETE' ||
    // 如果操作类型是 SET,并且目标对象是 Map 类型的数据,
    // 也应该触发那些与 ITERATE_KEY 相关联的副作用函数重新执行
    (
      type === 'SET' &&
      Object.prototype.toString.call(target) === '[object Map]'
    )
  ) {
    // 取得与 ITERATE_KEY 相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)
    // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
    iterateEffects && iterateEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

function cleanup(effectFn) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i]
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn)
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0
}

// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []

// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    // 调用 cleanup 函数完成清除工作
    cleanup(effectFn)
    // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
    activeEffect = effectFn
    // 在调用副作用函数之前将当前副作用函数压入栈中
    effectStack.push(effectFn)
    fn()
    // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  // 将 options 挂载到 effectFn 上
  effectFn.options = options
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = []
  // 执行副作用函数
  effectFn()
}


const p = reactive(new Map([
  ['key', 1]
]))

effect(() => {
  p.forEach(function (value, key) {
    // forEach 循环不仅关心集合的键,还关心集合的值
    console.log(value) // 1
  })
})

p.set('key', 2) // 即使操作类型是 SET,也应该触发响应

代理 Set 和 Map 实现迭代器方法的响应性

一个对象能否迭代,取决于该对象是否实现了迭代协议。

  • 可迭代协议指的是一个对象实现了 Symbol.iterator 方法

  • 迭代器协议指的是一个对象实现了 next 方法

一个对象可以同时实现可迭代协议和迭代器协议,例如:

js 复制代码
const obj = {
  // 迭代器协议
  next() {
    // ...
  }
  // 可迭代协议
  [Symbol.iterator]() {
    return this
  }
}

一个对象同时实现了可迭代协议和迭代器协议才可以被迭代。

实现集合类型的数据结构的迭代器方法的拦截,关键在于需要借助原生的可迭代协议与迭代器协议,实现自定义的可迭代协议与迭代器协议,调用 track 函数建立响应联系。

js 复制代码
const mutableInstrumentations = {
  [Symbol.iterator]: iterationMethod,
  entries: iterationMethod
}

// 抽离为独立的函数,便于复用
function iterationMethod() {
  // 获取原始数据对象 target
  const target = this.raw
  // 获取原始迭代器方法
  const itr = target[Symbol.iterator]()
  const wrap = (val) => typeof val === 'object' ? reactive(val) : val

  // 调用 track 函数建立响应联系
  track(target, ITERATE_KEY)

  // 返回自定义的迭代器    
  return {
    next() {
      // 调用原始迭代器的 next 方法获取 value 和 done
      const { value, done } = itr.next()
      return {
        // 如果 value 不是 undefined,则对其进行包裹
        value: value ? [wrap(value[0]), wrap(value[1])] : value,
        done
      }
    },
    // 实现可迭代协议
    [Symbol.iterator]() {
      return this
    }
  }
}

代理 Set 和 Map 实现迭代器方法的响应性的完整实现如下

js 复制代码
// 存储副作用函数的桶
const bucket = new WeakMap()

const ITERATE_KEY = Symbol()

const arrayInstrumentations = {}

;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function(...args) {
    // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
    let res = originMethod.apply(this, args)

    if (res === false || res === -1) {
      // res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
      res = originMethod.apply(this.raw, args)
    }
    // 返回最终结果
    return res
  }
})

// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
  // 取得原始 push 方法
  const originMethod = Array.prototype[method]
  // 重写
  arrayInstrumentations[method] = function(...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false
    // push 方法的默认行为
    let res = originMethod.apply(this, args)
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true
    return res
  }
})

// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
  get(key) {
    // 获取原始对象
    const target = this.raw
    // 判断读取的 key 是否存在
    const had = target.has(key)
    // 追踪依赖,建立响应联系
    track(target, key)
    // 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
    // 则要返回使用 reactive 包装后的响应式数据
    if (had) {
      const res = target.get(key)
      return typeof res === 'object' ? reactive(res) : res
    }
  },
  set(key, value) {
    const target = this.raw
    const had = target.has(key)
    // 获取旧值
    const oldValue = target.get(key)
    // 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
    const rawValue = value.raw || value
    target.set(key, rawValue)
    // 如果不存在,则说明是 ADD 类型的操作,意味着新增
    if (!had) {
      trigger(target, key, 'ADD')
    } else if (oldValue !== value || (oldValue === oldValue && value === value)) {
      // 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
      trigger(target, key, 'SET')
    }
  },
  add(key) {
    // this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
    const target = this.raw
    // 先判断值是否已经存在
    const hadKey = target.has(key)
    const res = target.add(key)
    // 只有在值不存在的情况下,才需要触发响应
    if (!hadKey) {
      trigger(target, key, 'ADD')
    }
    // 返回操作结果
    return res
  },
  delete(key) {
    const target = this.raw
    const hadKey = target.has(key)
    const res = target.delete(key)
    // 当要删除的元素确实存在时,才触发响应
    if (hadKey) {
      trigger(target, key, 'DELETE')
    }
    return res
  },
  // 接收第二个参数
  forEach(callback, thisArg) {
    // wrap 函数用来把可代理的值转换为响应式数据
    const wrap = (val) => typeof val === 'object' ? reactive(val) : val
    // 取得原始数据对象
    const target = this.raw
    // 与 ITERATE_KEY 建立响应联系
    track(target, ITERATE_KEY)
    //  通过 target 调用原始 forEach 方法进行遍历
    target.forEach((v, k) => {
      // 通过 .call 调用 callback,并传递 thisArg
      callback.call(thisArg, wrap(v), wrap(k), this)
    })
  },
  // 共用 iterationMethod 方法
  [Symbol.iterator]: iterationMethod,
  entries: iterationMethod
}

// 抽离为独立的函数,便于复用
function iterationMethod() {
  // 获取原始数据对象 target
  const target = this.raw
  // 获取原始迭代器方法
  const itr = target[Symbol.iterator]()
  const wrap = (val) => typeof val === 'object' ? reactive(val) : val

  // 调用 track 函数建立响应联系
  track(target, ITERATE_KEY)

  // 返回自定义的迭代器    
  return {
    next() {
      // 调用原始迭代器的 next 方法获取 value 和 done
      const { value, done } = itr.next()
      return {
        // 如果 value 不是 undefined,则对其进行包裹
        value: value ? [wrap(value[0]), wrap(value[1])] : value,
        done
      }
    },
    // 实现可迭代协议
    [Symbol.iterator]() {
      return this
    }
  }
}

// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    deleteProperty(target, key) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 检查被操作的属性是否是对象自己的属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      // 使用 Reflect.deleteProperty 完成属性的删除
      const res = Reflect.deleteProperty(target, key)
      if (res && hadKey) {
        // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
        trigger(target, key, 'DELETE')
      }
      return res
    },
    ownKeys(target) {
      // 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
      track(target,  Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // 拦截读取操作
    get(target, key, receiver) {
      // 如果读取的是 raw 属性,则返回原始数据对象 target
      if (key === 'raw') return target
      if (key === 'size') {
        // 调用 track 函数建立响应联系
        track(target, ITERATE_KEY)
        return Reflect.get(target, key, target)
      }
      // 返回定义在 mutableInstrumentations 对象下的方法
      return mutableInstrumentations[key]
    },
    // 拦截设置操作
    set(target, key, newVal, receiver) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 先获取旧值
      const oldVal = target[key]

      // 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
      const type = Array.isArray(target)
        // 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
        // 如果是,则视作 SET 操作,否则是 ADD 操作
        ? Number(key) < target.length ? 'SET' : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      // 设置属性值
      const res = Reflect.set(target, key, newVal, receiver)

      // target === receiver.raw 说明 receiver 就是 target 的代理对象
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 增加第四个参数,即触发响应的新值
          trigger(target, key, type, newVal)
        }
      }
      return res
    },
    has(target, key) {
      console.error('拦截 in 操作符')
      track(target, key)
      return Reflect.has(target, key)
    }
  })
}

// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()

function reactive(obj) {
  // 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
  const existionProxy = reactiveMap.get(obj)
  if (existionProxy) return existionProxy

  // 否则,创建新的代理对象
  const proxy = createReactive(obj)
  // 存储到 Map 中,从而避免重复创建
  reactiveMap.set(obj, proxy)

  return proxy
}

function shallowReactive(obj) {
  return createReactive(obj, true)
}

function readonly(obj) {
  return createReactive(obj, false, true /* 只读 */)
}

function shallowReadonly(obj) {
  return createReactive(obj, true /* shallow */, true)
}

// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
  // 当禁止追踪时,直接返回
  if (!activeEffect || !shouldTrack) 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 中
  deps.add(activeEffect)
  // deps 就是一个与当前副作用函数存在联系的依赖集合
  // 将其添加到 activeEffect.deps 数组中
  activeEffect.deps.push(deps)
}

//  为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  // 取得与 key 相关联的副作用函数
  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  // 将与 key 相关联的副作用函数添加到 effectsToRun
  effects && effects.forEach(effectFn => {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effectFn !== activeEffect) {  // 新增
      effectsToRun.add(effectFn)
    }
  })

  // 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
  if (type === 'ADD' && Array.isArray(target)) {
    // 取出与 length 相关联的副作用函数
    const lengthEffects = depsMap.get('length')
    // 将这些副作用函数添加到 effectsToRun 中,待执行
    lengthEffects && lengthEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  // 如果操作目标是数组,并且修改了数组的 length 属性
  if (Array.isArray(target) && key === 'length') {
    // 对于索引大于或等于新的 length 值的元素,
    // 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
    depsMap.forEach((effects, key) => {
      if (key >= newVal) {
        effects.forEach(effectFn => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }

  //  当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
  if (
    type === 'ADD' ||
    type === 'DELETE' ||
    // 如果操作类型是 SET,并且目标对象是 Map 类型的数据,
    // 也应该触发那些与 ITERATE_KEY 相关联的副作用函数重新执行
    (
      type === 'SET' &&
      Object.prototype.toString.call(target) === '[object Map]'
    )
  ) {
    // 取得与 ITERATE_KEY 相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)
    // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
    iterateEffects && iterateEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

function cleanup(effectFn) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i]
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn)
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0
}

// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []

// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    // 调用 cleanup 函数完成清除工作
    cleanup(effectFn)
    // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
    activeEffect = effectFn
    // 在调用副作用函数之前将当前副作用函数压入栈中
    effectStack.push(effectFn)
    fn()
    // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  // 将 options 挂载到 effectFn 上
  effectFn.options = options
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = []
  // 执行副作用函数
  effectFn()
}


const p = reactive(new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
]))

effect(() => {
  for (const [key, value] of p) {
    console.log(key, value)
  }
})

for (const [key, value] of p.entries()) {
  console.log(key, value)
}

代理 Set 和 Map 实现 values 与 keys 方法的响应性

实现集合数据类型的 values 与 keys 方法的响应性的思路也是:拦截原生的 values 与 keys 方法,调用 track 方法建立响应联系,然后返回实现了自定义的可迭代协议与迭代器协议的新对象。

js 复制代码
const mutableInstrumentations = {
  values: valuesIterationMethod,
  keys: keysIterationMethod
}

function valuesIterationMethod() {
  // 获取原始数据对象 target
  const target = this.raw
  // 通过 target.values 获取原始迭代器方法
  const itr = target.values()

  const wrap = (val) => typeof val === 'object' ? reactive(val) : val

  track(target, ITERATE_KEY)

  // 将其返回
  return {
    next() {
      const { value, done } = itr.next()
      return {
        // value 是值,而非键值对,所以只需要包裹 value 即可
        value: wrap(value),
        done
      }
    },
    [Symbol.iterator]() {
      return this
    }
  }
}

const MAP_KEY_ITERATE_KEY = Symbol()

function keysIterationMethod() {
  // 获取原始数据对象 target
  const target = this.raw
  // 获取原始迭代器方法
  const itr = target.keys()

  const wrap = (val) => typeof val === 'object' ? reactive(val) : val

  // 调用 track 函数追踪依赖,在副作用函数与 MAP_KEY_ITERATE_KEY 之间建立响应联系
  track(target, MAP_KEY_ITERATE_KEY)

  // 将其返回
  return {
    next() {
      const { value, done } = itr.next()

      return {
        value: wrap(value),
        done
      }
    },
    [Symbol.iterator]() {
      return this
    }
  }
}

代理 Set 和 Map 实现 values 与 keys 方法的响应性的完整实现如下

js 复制代码
// 存储副作用函数的桶
const bucket = new WeakMap()

const ITERATE_KEY = Symbol()

const arrayInstrumentations = {}

;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function(...args) {
    // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
    let res = originMethod.apply(this, args)

    if (res === false || res === -1) {
      // res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
      res = originMethod.apply(this.raw, args)
    }
    // 返回最终结果
    return res
  }
})

// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
  // 取得原始 push 方法
  const originMethod = Array.prototype[method]
  // 重写
  arrayInstrumentations[method] = function(...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false
    // push 方法的默认行为
    let res = originMethod.apply(this, args)
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true
    return res
  }
})

// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
  get(key) {
    // 获取原始对象
    const target = this.raw
    // 判断读取的 key 是否存在
    const had = target.has(key)
    // 追踪依赖,建立响应联系
    track(target, key)
    // 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
    // 则要返回使用 reactive 包装后的响应式数据
    if (had) {
      const res = target.get(key)
      return typeof res === 'object' ? reactive(res) : res
    }
  },
  set(key, value) {
    const target = this.raw
    const had = target.has(key)
    // 获取旧值
    const oldValue = target.get(key)
    // 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
    const rawValue = value.raw || value
    target.set(key, rawValue)
    // 如果不存在,则说明是 ADD 类型的操作,意味着新增
    if (!had) {
      trigger(target, key, 'ADD')
    } else if (oldValue !== value || (oldValue === oldValue && value === value)) {
      // 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
      trigger(target, key, 'SET')
    }
  },
  add(key) {
    // this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
    const target = this.raw
    // 先判断值是否已经存在
    const hadKey = target.has(key)
    const res = target.add(key)
    // 只有在值不存在的情况下,才需要触发响应
    if (!hadKey) {
      trigger(target, key, 'ADD')
    }
    // 返回操作结果
    return res
  },
  delete(key) {
    const target = this.raw
    const hadKey = target.has(key)
    const res = target.delete(key)
    // 当要删除的元素确实存在时,才触发响应
    if (hadKey) {
      trigger(target, key, 'DELETE')
    }
    return res
  },
  // 接收第二个参数
  forEach(callback, thisArg) {
    // wrap 函数用来把可代理的值转换为响应式数据
    const wrap = (val) => typeof val === 'object' ? reactive(val) : val
    // 取得原始数据对象
    const target = this.raw
    // 与 ITERATE_KEY 建立响应联系
    track(target, ITERATE_KEY)
    //  通过 target 调用原始 forEach 方法进行遍历
    target.forEach((v, k) => {
      // 通过 .call 调用 callback,并传递 thisArg
      callback.call(thisArg, wrap(v), wrap(k), this)
    })
  },
  // 共用 iterationMethod 方法
  [Symbol.iterator]: iterationMethod,
  entries: iterationMethod,
  values: valuesIterationMethod,
  keys: keysIterationMethod
}

function valuesIterationMethod() {
  // 获取原始数据对象 target
  const target = this.raw
  // 通过 target.values 获取原始迭代器方法
  const itr = target.values()

  const wrap = (val) => typeof val === 'object' ? reactive(val) : val

  track(target, ITERATE_KEY)

  // 将其返回
  return {
    next() {
      const { value, done } = itr.next()
      return {
        // value 是值,而非键值对,所以只需要包裹 value 即可
        value: wrap(value),
        done
      }
    },
    [Symbol.iterator]() {
      return this
    }
  }
}

const MAP_KEY_ITERATE_KEY = Symbol()

function keysIterationMethod() {
  // 获取原始数据对象 target
  const target = this.raw
  // 获取原始迭代器方法
  const itr = target.keys()

  const wrap = (val) => typeof val === 'object' ? reactive(val) : val

  // 调用 track 函数追踪依赖,在副作用函数与 MAP_KEY_ITERATE_KEY 之间建立响应联系
  track(target, MAP_KEY_ITERATE_KEY)

  // 将其返回
  return {
    next() {
      const { value, done } = itr.next()

      return {
        value: wrap(value),
        done
      }
    },
    [Symbol.iterator]() {
      return this
    }
  }
}

// 抽离为独立的函数,便于复用
function iterationMethod() {
  // 获取原始数据对象 target
  const target = this.raw
  // 获取原始迭代器方法
  const itr = target[Symbol.iterator]()
  const wrap = (val) => typeof val === 'object' ? reactive(val) : val

  // 调用 track 函数建立响应联系
  track(target, ITERATE_KEY)

  // 返回自定义的迭代器    
  return {
    next() {
      // 调用原始迭代器的 next 方法获取 value 和 done
      const { value, done } = itr.next()
      return {
        // 如果 value 不是 undefined,则对其进行包裹
        value: value ? [wrap(value[0]), wrap(value[1])] : value,
        done
      }
    },
    // 实现可迭代协议
    [Symbol.iterator]() {
      return this
    }
  }
}

// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    deleteProperty(target, key) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 检查被操作的属性是否是对象自己的属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      // 使用 Reflect.deleteProperty 完成属性的删除
      const res = Reflect.deleteProperty(target, key)
      if (res && hadKey) {
        // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
        trigger(target, key, 'DELETE')
      }
      return res
    },
    ownKeys(target) {
      // 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
      track(target,  Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // 拦截读取操作
    get(target, key, receiver) {
      // 如果读取的是 raw 属性,则返回原始数据对象 target
      if (key === 'raw') return target
      if (key === 'size') {
        // 调用 track 函数建立响应联系
        track(target, ITERATE_KEY)
        return Reflect.get(target, key, target)
      }
      // 返回定义在 mutableInstrumentations 对象下的方法
      return mutableInstrumentations[key]
    },
    // 拦截设置操作
    set(target, key, newVal, receiver) {
      // 如果是只读的,则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`)
        return true
      }
      // 先获取旧值
      const oldVal = target[key]

      // 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
      const type = Array.isArray(target)
        // 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
        // 如果是,则视作 SET 操作,否则是 ADD 操作
        ? Number(key) < target.length ? 'SET' : 'ADD'
        : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      // 设置属性值
      const res = Reflect.set(target, key, newVal, receiver)

      // target === receiver.raw 说明 receiver 就是 target 的代理对象
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 增加第四个参数,即触发响应的新值
          trigger(target, key, type, newVal)
        }
      }
      return res
    },
    has(target, key) {
      console.error('拦截 in 操作符')
      track(target, key)
      return Reflect.has(target, key)
    }
  })
}

// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()

function reactive(obj) {
  // 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
  const existionProxy = reactiveMap.get(obj)
  if (existionProxy) return existionProxy

  // 否则,创建新的代理对象
  const proxy = createReactive(obj)
  // 存储到 Map 中,从而避免重复创建
  reactiveMap.set(obj, proxy)

  return proxy
}

function shallowReactive(obj) {
  return createReactive(obj, true)
}

function readonly(obj) {
  return createReactive(obj, false, true /* 只读 */)
}

function shallowReadonly(obj) {
  return createReactive(obj, true /* shallow */, true)
}

// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
  // 当禁止追踪时,直接返回
  if (!activeEffect || !shouldTrack) 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 中
  deps.add(activeEffect)
  // deps 就是一个与当前副作用函数存在联系的依赖集合
  // 将其添加到 activeEffect.deps 数组中
  activeEffect.deps.push(deps)
}

//  为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  // 取得与 key 相关联的副作用函数
  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  // 将与 key 相关联的副作用函数添加到 effectsToRun
  effects && effects.forEach(effectFn => {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effectFn !== activeEffect) {  // 新增
      effectsToRun.add(effectFn)
    }
  })

  // 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
  if (type === 'ADD' && Array.isArray(target)) {
    // 取出与 length 相关联的副作用函数
    const lengthEffects = depsMap.get('length')
    // 将这些副作用函数添加到 effectsToRun 中,待执行
    lengthEffects && lengthEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  // 如果操作目标是数组,并且修改了数组的 length 属性
  if (Array.isArray(target) && key === 'length') {
    // 对于索引大于或等于新的 length 值的元素,
    // 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
    depsMap.forEach((effects, key) => {
      if (key >= newVal) {
        effects.forEach(effectFn => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }

  //  当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
  if (
    type === 'ADD' ||
    type === 'DELETE' ||
    // 如果操作类型是 SET,并且目标对象是 Map 类型的数据,
    // 也应该触发那些与 ITERATE_KEY 相关联的副作用函数重新执行
    (
      type === 'SET' &&
      Object.prototype.toString.call(target) === '[object Map]'
    )
  ) {
    // 取得与 ITERATE_KEY 相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)
    // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
    iterateEffects && iterateEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  if (
    // 操作类型为 ADD 或 DELETE
    (type === 'ADD' || type === 'DELETE') &&
    // 并且是 Map 类型的数据
    Object.prototype.toString.call(target) === '[object Map]'
  ) {
    // 则取出那些与 MAP_KEY_ITERATE_KEY 相关联的副作用函数并执行
    const iterateEffects = depsMap.get(MAP_KEY_ITERATE_KEY)
    iterateEffects && iterateEffects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  }

  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

function cleanup(effectFn) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i]
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn)
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0
}

// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []

// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    // 调用 cleanup 函数完成清除工作
    cleanup(effectFn)
    // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
    activeEffect = effectFn
    // 在调用副作用函数之前将当前副作用函数压入栈中
    effectStack.push(effectFn)
    fn()
    // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }
  // 将 options 挂载到 effectFn 上
  effectFn.options = options
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = []
  // 执行副作用函数
  effectFn()
}


const p = reactive(new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
]))

effect(() => {
  for (const value of p.keys()) {
    console.log(value)
  }
})

p.set('key2', 'value3') // 不会触发响应
p.set('key3', 'value3') // 能够触发响应
相关推荐
m0_748254881 分钟前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl
ZJ_.12 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营17 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood43 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端44 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特1 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6172 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248942 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5