get(target: Target, key: string | symbol, receiver: object) { const isReadonly = this._isReadonly, shallow = this._shallow if (key === ReactiveFlags.IS_REACTIVE) { // 代理 __v_isReactive return !isReadonly } elseif (key === ReactiveFlags.IS_READONLY) { // 代理 __v_isReadonly return isReadonly } elseif (key === ReactiveFlags.IS_SHALLOW) { // 代理 __v_isShallow return shallow } elseif (key === ReactiveFlags.RAW) { // 函数中判断响应式对象是否存在 __v_raw 属性,如果存在就返回这个响应式对象本身。 if ( receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target) || // receiver is not the reactive proxy, but has the same prototype // this means the reciever is a user proxy of the reactive proxy Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver) ) { return target } // early return undefined return }
// 是否是数组 const targetIsArray = isArray(target)
if (!isReadonly) { // arrayInstrumentations 包含对数组一些方法修改的函数 if (targetIsArray && hasOwn(arrayInstrumentations, key)) { returnReflect.get(arrayInstrumentations, key, receiver) } if (key === 'hasOwnProperty') { return hasOwnProperty } }
const res = Reflect.get(target, key, receiver) // 内置 Symbol key 不需要依赖收集 if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res }
// 依赖收集 if (!isReadonly) { track(target, TrackOpTypes.GET, key) }
if (shallow) { return res }
if (isRef(res)) { // ref unwrapping - skip unwrap for Array + integer key. return targetIsArray && isIntegerKey(key) ? res : res.value }
// 如果 res 是个对象或者数组类型,则递归执行 reactive 函数把 res 变成响应式 if (isObject(res)) { // Convert returned value into a proxy as well. we do the isObject check // here to avoid invalid value warning. Also need to lazy access readonly // and reactive here to avoid circular dependency. return isReadonly ? readonly(res) : reactive(res) }
functioncreateArrayInstrumentations() { const instrumentations: Record<string, Function> = {} // instrument identity-sensitive Array methods to account for possible reactive // values ;(['includes', 'indexOf', 'lastIndexOf'] asconst).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { // toRaw 可以把响应式对象转成原始数据,this就是Reflect的receiver const arr = toRaw(this) as any for (let i = 0, l = this.length; i < l; i++) { // 依赖收集 track(arr, TrackOpTypes.GET, i + '') } // we run the method using the original args first (which may be reactive) const res = arr[key](...args) if (res === -1 || res === false) { // if that didn't work, run it again using raw values. return arr[key](...args.map(toRaw)) } else { return res } } }) // instrument length-altering mutation methods to avoid length being tracked // which leads to infinite loops in some cases (#2137) ;(['push', 'pop', 'shift', 'unshift', 'splice'] asconst).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { pauseTracking() pauseScheduling() const res = (toRaw(this) as any)[key].apply(this, args) resetScheduling() resetTracking() return res } }) return instrumentations }
也就是说,当 target 是一个数组的时候,我们去访问 target.includes、target.indexOf、 target.lastIndexOf 就会执行 arrayInstrumentations 代理的函数,除了调用数组本身的方法求值外,还对数组每个元素做了依赖收集。因为一旦数组的元素被修改,数组的这几个 API 的返回结果都可能发生变化,所以我们需要跟踪数组每个元素的变化。
// 创建要执行的deps数组 let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target // 清空数组或者map的时候触发所有key对应的的effect函数 deps = [...depsMap.values()] } elseif (key === 'length' && isArray(target)) { const newLength = Number(newValue) depsMap.forEach((dep, key) => { if (key === 'length' || (!isSymbol(key) && key >= newLength)) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE // set add delete操作 将key对应的effect函数添加到deps数组中 if (key !== void0) { deps.push(depsMap.get(key)) }
// also run for iteration key on ADD | DELETE | Map.SET // 根据不同的操作类型push对应的dep switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } elseif (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } }
pauseScheduling() for (const dep of deps) { if (dep) { triggerEffects( dep, DirtyLevels.Dirty, __DEV__ ? { target, type, key, newValue, oldValue, oldTarget, } : void0, ) } } resetScheduling() }