帖子把两条脉络讲得很清楚,我补三个代码截断之后最关键的细节——它们决定了 Proxy 能不能真正“一次性填掉”那些坑。

一、get 里那个 const r 是惰性代理的入口,不能只写 Reflect.get

get(t, k, receiver) {
  track(t, k)
  const r = Reflect.get(t, k, receiver)
  return isObject(r) ? reactive(r) : r   // 访问到才代理
}

这一行同时解决两件事:嵌套对象不用初始化时全量递归(这是初始化开销真正归零的原因),以及 state.a.b 这种链式访问能继续追踪。但前提是 reactive 必须带 WeakMap 缓存,保证同一原始对象永远返回同一个 proxy——否则 state.a === state.a 为 false,深层依赖还会重复收集。Vue 3 内部用两张 WeakMap 互相映射(target↔proxy),这个缓存不是优化,是正确性要求。

二、Proxy 真正卡住的地方不是 defineProperty 的坑,是内部槽

Date、Map、Set、RegExp 这类对象带 internal slots,Date.prototype.getTime.call(proxy) 会直接抛 TypeError,因为 receiver 不是真实例。所以 Vue 3 单独写了 collectionHandlers 给 Map/Set 用,而不是一套 handler 走天下。另外 Proxy 只能套对象、套不了原始值,这才是 ref 用 { value } 包装的根本原因,不是为了统一 API 风格好看。

三、in、delete、for...in 要三个 trap 才补全

has 管 'x' in state,deleteProperty 管删除,ownKeys 管 Object.keys 和 for...in——但 ownKeys 触发时不能按具体 key 派发,要用一个 ITERATE_KEY 占位收集,否则新增/删除属性时遍历类的副作用感知不到。这个 key 的设计是 Vue 3 里最容易被忽略的一处。

结尾一个坑:Proxy 无法 polyfill,所以 Vue 3 直接不支持 IE11,这是硬性取舍;另外 reactive(obj) === obj 永远是 false,写单测和做 Object.is 比较时容易踩。