响应式原理实现(2)vue2和vue3

embedded/2024/10/21 23:13:31/

响应式2

vue2响应式实现

提供shallow,决定是否需要深度响应

javascript">/*******************新增 shallow*******************/
export function defineReactive(obj, key, val, shallow) {
/****************************************************/const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher/*******************新增****************************/// 将新的val也收集响应 observe(val)!shallow && observe(val);/******************************************************/Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();}return value;},set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal);} else {val = newVal;}childOb = !shallow && observe(newVal);dep.notify();},});
}
javascript">/*
util.js
export function isObject(obj) {return obj !== null && typeof obj === "object";
}
*/
export function observe(value) {if (!isObject(value)) {return;}let ob = new Observer(value);return ob;
}

代理模式

javascript">import { def } from "./util";const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);const methodsToPatch = ["push","pop","shift","unshift","splice","sort","reverse",
];/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method];def(arrayMethods, method, function mutator(...args) {const result = original.apply(this, args);/*****************这里相当于调用了对象 set 需要通知 watcher ************************/// 待补充/**************************************************************************** */return result;});
});
javascript">export class Observer {constructor(value) {/******新增 *************************/this.dep = new Dep();/************************************/this.walk(value);}/*** 遍历对象所有的属性,调用 defineReactive* 拦截对象属性的 get 和 set 方法*/walk(obj) {const keys = Object.keys(obj);for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]);}}
}
export function defineReactive(obj, key, val, shallow) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcherlet childOb = !shallow && observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();/******新增 *************************/if (childOb) {// 当前 value 是数组,去收集依赖if (Array.isArray(value)) {childOb.dep.depend();}}/************************************/}return value;},set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal);} else {val = newVal;}dep.notify();},});
}

上面已经重写了array方法,不可以直接覆盖全局的array方法,如果当前value是数组,在observer中拦截array方法。

javascript">import { arrayMethods } from './array' // 上边重写的所有数组方法
/* export const hasProto = "__proto__" in {}; */
export class Observer {constructor(value) {this.dep = new Dep();/******新增 *************************/if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods);} else {copyAugment(value, arrayMethods, arrayKeys);}/************************************/} else {this.walk(value);}}/*** 遍历对象所有的属性,调用 defineReactive* 拦截对象属性的 get 和 set 方法*/walk(obj) {const keys = Object.keys(obj);for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]);}}
}
/*** Augment a target Object or Array by intercepting* the prototype chain using __proto__*/
function protoAugment(target, src) {/* eslint-disable no-proto */target.__proto__ = src;/* eslint-enable no-proto */
}/*** Augment a target Object or Array by defining* hidden properties.*/
/* istanbul ignore next */
function copyAugment(target, src, keys) {for (let i = 0, l = keys.length; i < l; i++) {const key = keys[i];def(target, key, src[key]);}
}

上面代码中,实现了数组的操作方法,但是并没有给数组中的元素添加响应式,所以我们需要给数组中的元素添加响应式。

javascript">export class Observer {constructor(value) {this.dep = new Dep();def(value, "__ob__", this);if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods);} else {copyAugment(value, arrayMethods, arrayKeys);}/******新增 *************************/this.observeArray(value);/************************************/} else {this.walk(value);}}/*** 遍历对象所有的属性,调用 defineReactive* 拦截对象属性的 get 和 set 方法*/walk(obj) {const keys = Object.keys(obj);for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]);}}observeArray(items) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i]);}}
}

如果是多维数组,则需要对多维数组中的元素进行依赖

javascript">export function defineReactive(obj, key, val, shallow) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcherlet childOb = !shallow && observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();if (childOb) {if (Array.isArray(value)) {childOb.dep.depend(); // [["hello", "wind"],["hello", "liang"]]  这个整体进行了依赖的收集/******新增 *************************/dependArray(value); // 循环数组中的元素,如果是数组的话进行依赖收集。/************************************/}}}return value;},...}
function dependArray(value) {for (let e, i = 0, l = value.length; i < l; i++) {e = value[i];if (Array.isArray(e)) {e && e.__ob__ && e.__ob__.dep.depend();dependArray(e); // 递归进行}}
}      

同时也需要对插入的数据进行依赖收集,如果是数组,进行数组的依赖收集。
如果是数组,需要将数组中的元素进行响应式处理,对于新添加的元素也进行响应式处理。收集依赖的时候,需要对数组中的数组进行依赖收集。
数组的set和delete方法
数组set
list[0]不会触发watcher收集。数组只能通过重写的push,splice方法去触发

javascript">/*** Set a property on an object. Adds the new property and* triggers change notification if the property doesn't* already exist.*/
export function set(target, key, val) {if (Array.isArray(target)) {target.length = Math.max(target.length, key);target.splice(key, 1, val);return val;}// targe 是对象的情况// ...
}

数组del

javascript">/*** Delete a property and trigger change if necessary.*/
export function del(target, key) {if (Array.isArray(target) && isValidArrayIndex(key)) {target.splice(key, 1);return;}// targe 是对象的情况// ...
}

对象set和delete方法

  • 对象set方法
javascript">// 例子
import { observe, set, del } from "./reactive";
import Watcher from "./watcher";
const data = {obj: {a: 1,b: 2,},
};
observe(data);
const updateComponent = () => {const c = data.obj.c ? data.obj.c : 0;console.log(data.obj.a + data.obj.b + c);
};new Watcher(updateComponent);data.obj.c = 3;
javascript">/*** Set a property on an object. Adds the new property and* triggers change notification if the property doesn't* already exist.*/
export function set(target, key, val) {if (Array.isArray(target)) {target.length = Math.max(target.length, key);target.splice(key, 1, val);return val;}// targe 是对象的情况// key 在 target 中已经存在if (key in target && !(key in Object.prototype)) { target[key] = val;return val;}const ob = target.__ob__;// target 不是响应式数据if (!ob) {target[key] = val;return val;}// 将当前 key 变为响应式的defineReactive(target, key, val);return val;
}

上面的代码虽然设置了set,但是不会新收集watcher, 需要手动调用。
这里将代码中的对象的dep进行收集对象元素中dep收集的watcher

javascript">export function defineReactive(obj, key, val, shallow) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcherlet childOb = !shallow && observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();if (childOb) {/******新位置 *************************/childOb.dep.depend();/**********************************/if (Array.isArray(value)) {// childOb.dep.depend(); //原来的位置dependArray(value);}}}return value;},set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal);} else {val = newVal;}childOb = !shallow && observe(newVal);dep.notify();},});
}function dependArray(value) {for (let e, i = 0, l = value.length; i < l; i++) {e = value[i];/******新位置 *************************/e && e.__ob__ && e.__ob__.dep.depend();/**********************************/if (Array.isArray(e)) {//  e && e.__ob__ && e.__ob__.dep.depend(); // 原位置dependArray(e);}}
}

并不知道 c 被哪些 Watcher 依赖,我们只知道和 c 同属于一个对象的 a 和 b 被哪些 Watcher 依赖,但大概率 c 也会被其中的 Watcher 依赖。所以我们可以在set中手动执行一下obj的Dep,依赖c的Watcher大概率会被执行,相应的c也会成功收集到依赖。
c中的watcher不会在收集,这种情况下,只能通过父级收集依赖,触发依赖,来更新后续步骤。

  • 对象delete方法
    如果要是删除a属性,删除后执行他相应的dep就行。
    同上诉的思想一样,这里也需要更新父级。这样就可以完成delete。
javascript">/*** Delete a property and trigger change if necessary.*/
export function del(target, key) {if (Array.isArray(target)) {target.splice(key, 1);return;}// targe 是对象的情况const ob = target.__ob__;if (!hasOwn(target, key)) {return;}delete target[key];if (!ob) {return;}ob.dep.notify();
}

vue3响应式实现

reactive

在vue2中需要重写数组的方法,来达到对数组响应式。
但是在vue3中,却是不需要,因为vue3中使用的是proxy来做数据拦截,可以原生支持数组的响应式。
这里来解释一下object.definePropertyProxy
object.defineproperty实际上是通过定义或者修改对象属性的描述符来实现数据劫持,缺点是只能拦截get和set操作,无法拦截delete,in,方法调用等属性。动态添加新属性,保证后续使用的属性要在初始化生命data的时候定义,通过this. s e t 设置新属性。通过 d e l e t e 删除属性,响应式丢失,通过 t h i s . set设置新属性。通过delete删除属性,响应式丢失,通过this. set设置新属性。通过delete删除属性,响应式丢失,通过this.delete()删除属性。通过数组索引替换/新增元素,响应式丢失,使用this.$set来设置新元素。使用数组push,pop,shift,unshif,splice,sort,reverse等原生方法来改变原数组的时候,会响应式丢失,需要使用重写/增强后的push,pop,shift,unshift,splice,sort,reverse方法。一次只能对一个属性实现数据劫持,需要遍历对所有的属性进行劫持。数据结构复杂的时候,属性值为引用类型数据,需要通过递归进行处理。
其实object.defineProperty也能拦截Array。但是,还是有如下原因。1. 数组和普通对象在使用场景下会有区别,在项目中使用数组的目的是为了遍历,比较少会使用array[index]=xxx的形式。2. 数组长度是多变的,不可能和普通对象一样在data选项中提前声明好的所有元素。通过array[index]=xxx方式赋值的时候,一旦index超过了现有最大的索引值,那么当前添加的新元素也不会具有响应式。3. 数组存储的元素比较多,不可能为每个数组元素都这是getter/setter。4.无法拦截数组原生方法,比如push,pop,shift,unshift等的调用,最终任然需要重写和增强方法。
proxy主要是用来创建一个对象的代理,从而实现基本操作的拦截和自定义(比如属性查找, 赋值, 枚举, 函数调用等),本质上是通过拦截对象内部方法的执行实现代理,而对象本身根据规范定义的不同又会分为常规对象和异质对象。

  1. get()属性读取操作捕获的捕获器。
  2. set()属性设置操作的捕获器。
  3. deleteProperty()是delete操作符的捕捉器。
  4. ownkeys()是object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕获器。
  5. has()是in操作符的捕获器。
javascript">export function reactive(target: object) {is(isReadonly(target)) {return target}return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)
}

target为传进来的对象

javascript">function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlerrs: ProxyHandler<any>,proxyMap: WeakMap<Target>
) {
function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any>
) {// 非对象类型直接返回if (!isObject(target)) {if (__DEV__) {console.warn(`value cannot be made reactive: ${String(target)}`)}return target}// 目标数据的 __v_raw 属性若为 true,且是【非响应式数据】或 不是通过调用 readonly() 方法,则直接返回if (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {return target}// 目标对象已存在相应的 proxy 代理对象,则直接返回const existingProxy = proxyMap.get(target)if (existingProxy) {return existingProxy}// 只有在白名单中的值类型才可以被代理监测,否则直接返回const targetType = getTargetType(target)if (targetType === TargetType.INVALID) {return target     }// 创建代理对象const proxy = new Proxy(target,// 若目标对象是集合类型(Set、Map)则使用集合类型对应的捕获器,否则使用基础捕获器targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers )
}

createReactiveObject()函数需要做一些前置判断处理。

  • 目标数据是原始值类型,直接返回原数据。
  • __v_raw属性为true,而且非响应式数据或不是通过调用readonly()方法。直接返回原数据。
  • 若目标数据中已经存在响应的proxy代理对象,则直接返回对应的代理对象。
  • 若目标数据中不存在对应的白名单数据类型中,则直接返回原数据。
    支持响应式的数据类型为
    • 可拓展的对象,即他的上面上可以添加新的属性
    • __v_skip属性不存在或者是值为false的对象
    • 数据类型为object, Array, Map, Set, WeakMap, WeakSet对象。
    • 其他数据都被认为无效的响应式对象。
javascript">// 白名单
function targetTypeMap(rawType: string) {switch (rawType) {case 'Object':case 'Array':return TargetType.COMMONcase 'Map':case 'Set':case 'WeakMap':case 'WeakSet':return TargetType.COLLECTIONdefault:return TargetType.INVALID}
}
TargetType.COMMON对应的Handlers捕获器
javascript">export const mutableHandlers: ProxyHandler<object> = {get,set,deleteProperty,has,ownKeys
}

这些对应的就是读取,设置,删除,判断是否存在对应的属性,获取对象自身的属性值。

get捕获器
javascript">class BaseReactiveHandler implements ProxyHandler<Target> {constructor(protected readonly _isReadonly = false,protected readonly _isShallow = false,) {}get(target: Target, key: string | symbol, receiver: object) {const isReadonly = this._isReadonly,isShallow = this._isShallow// 当直接通过指定 key 访问 vue 内置自定义的对象属性时,返回其对应的值if (key === ReactiveFlags.IS_REACTIVE) {return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {return isReadonly} else if (key === ReactiveFlags.IS_SHALLOW) {return isShallow} else if (key === ReactiveFlags.RAW) {if (receiver ===(isReadonly? isShallow? shallowReadonlyMap: readonlyMap: isShallow? 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 proxyObject.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {return target}// early return undefinedreturn}// 判断是否为数组类型const targetIsArray = isArray(target)// 数组对象if (!isReadonly) {if (targetIsArray && hasOwn(arrayInstrumentations, key)) {// 重写/增强数组的方法// - 查找方法: includes, indexof, lastIndexOf// - 修改原数组的方法: push, pop, unshift, shift, splicereturn Reflect.get(arrayInstrumentations, key, receiver)}if (key === 'hasOwnProperty') {return hasOwnProperty}}// 获取对应属性值const res = Reflect.get(target, key, receiver)if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res}if (!isReadonly) {track(target, TrackOpTypes.GET, key)}if (isShallow) {return res}if (isRef(res)) {// ref unwrapping - skip unwrap for Array + integer key.return targetIsArray && isIntegerKey(key) ? res : res.value}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)}return res}
}
  • 若当前数据对象是数组,则重写/增强数组对应的方法。
    • 数组的查找方法: includes, indexOf, lastIndexOf
    • 修改原数组的方法: push, pop, unshift, shift, splice
  • 若当前数据对象是普通对象,且非只读的则通过track(target, TrackOpTypes.GET, key)进行依赖收集
    • 若当前数据对象是浅层响应的,则直接返回其对应属性值。
    • 当前对象是ref类型的,则会自动脱ref
  • 若当前数据对象的属性值是对象类型
    • 若当前属性值属于是只读的,则通过readonly(res)向外返回其结果
    • 否则会将当前的属性值以reactie(res)向外返回proxy代理对象
    • 否则直接想外返回对应的属性值
数组类型捕获

数组的查找方法中包含includes, indexof, lastIndexOf,这些方法通常情况下是能够按照预期工作的,但是还需要对某些情况进行特殊处理。

  • 查找的目标数据是响应式数据本身。
javascript">const obj = {}
const proxy = reactive([obj])
console.log(proxy.includes(proxy[0])) // false
- 产生原因: 这里涉及到了两次读取操作,第一次是proxy[0],这个时候会触发get捕获器并为obj生成对应代理对象并返回。第二次是proxy.includes()的调用,会遍历数组的每个元素,就是触发get捕获其,生成一个新的代理对象并返回,这两次生成的代理独享不是同一个,因此返回false
- 解决方法: 会在get中设置一个名为proxyMap的weakMap集合用来存储每个响应式对象,在触发get时优先返回proxyMap存在的响应式对象,不管触发多少次都能返回相同的响应式对象。
  • 当在响应式对象中查找原始数据的时候,得到的不是预期结果
const obj = {}
const proxy = reactive([obj])
console.log(proxy.includes(obj)) // fasle
- proxy.includes()会触发get不获取并为obj生成对应代理对象并返回,而includes方法的参数传递是原始数据,相当于此时是响应式和原始数据对象进行比较,结果一定是false
- 解决方案: 核心就是将它们的数据类型统一,就是统一使用原始值数据对象或者响应式数据对比,由于includes()的方法本身不支持对传入参数或者内部响应式数据的处理,因此需要自定义以上的数组查找方法。 在重写/增强的includes, indexOf, lastIndexOf等方法中,会将当前方法内部访问到的响应式数据转换为原始数据,然后调用数组对应的原始方法进行查找,若查找结果为true则直接返回结g果。如果以上操作没有查找到,则通过当前方法传入的参数转换为原始数据,则调用数组的原始方法,此时直接将对应的结果向外进行返回。
  • 处理数组影响length的方法
    隐式修改数组长度的原型方法包括push,pop,shift,unshift, splice等,在调用这些方法的同时会间接的读取数组的length属性,又因为这些方法具有修改数组长度的能力,即相当于length的设置操作,如果不进行特殊处理,会导致与lenght属性相关的副作用函数被重复执行。就是栈溢出。
    • 在调用真正的数组原型方法之前,会通过设置pauseTracking()方法来禁止track依赖收集
    • 在调用数组原生方法之后,在通过resetTracking()方法恢复track进行依赖收集
    • 上面的方法是通过控制shouldTrack变量为true或false,使得在track函数执行是否需要执行原来的依赖收集逻辑
javascript">const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()function createArrayInstrumentations() {const instrumentations: Record<string, Function> = {}// instrument identity-sensitive Array methods to account for possible reactive// values;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {instrumentations[key] = function (this: unknown[], ...args: unknown[]) {const arr = toRaw(this) as anyfor (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'] as const).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
}
set捕获器

处理数组索引index和length
数组的index和length是会相互影响的。

  • 当Number(key)<target.length => 证明是修改操作。对应的是TriggerOpTypes.SET类型,即当前操作不会改变length的值,不需要触发和length相关副作用的执行
  • 当Number(key)>=target.length => 证明是新增操作,TriggerOpTypes.ADD类型,当前操作会改变length的值,需要触发和length相关副作用函数的执行。
javascript">  set(target: object,key: string | symbol,value: unknown,receiver: object,): boolean {// 保存旧的数据let oldValue = (target as any)[key]// 若原数据值属于只读且ref类型,并且新数据值不属于ref类型,则意味着修改失败if (!this._isShallow) {const isOldValueReadonly = isReadonly(oldValue)if (!isShallow(value) && !isReadonly(value)) {oldValue = toRaw(oldValue)value = toRaw(value)}if (!isArray(target) && isRef(oldValue) && !isRef(value)) {if (isOldValueReadonly) {return false} else {oldValue.value = valuereturn true}}} else {// in shallow mode, objects are set as-is regardless of reactive or not}// 是否存在对应的keyconst hadKey =isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)// 设置对应值const result = Reflect.set(target, key, value, receiver)// 若目标对象是原始原型链上的内容(非自定义添加),则不触发依赖更新if (target === toRaw(receiver)) {if (!hadKey) {// 若目标对象不在对应的key上,则为新增操作。trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {// 目标对象存在对应的值,则为修改操作。trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}
deleteProperty has ownKeys捕获器

这三个捕获器内容非常简洁,其中has和ownKeys本质上也属于读取操作,因此需要通过track()进行依赖搜集,而deleteProperty相当于修改操作。因此需要trigger()触发更新

javascript">  deleteProperty(target: object, key: string | symbol): boolean {const hadKey = hasOwn(target, key)const oldValue = (target as any)[key]const result = Reflect.deleteProperty(target, key)if (result && hadKey) {trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result}
javascript">  has(target: object, key: string | symbol): boolean {const result = Reflect.has(target, key)if (!isSymbol(key) || !builtInSymbols.has(key)) {track(target, TrackOpTypes.HAS, key)}return result}
javascript">  ownKeys(target: object): (string | symbol)[] {track(target,TrackOpTypes.ITERATE,isArray(target) ? 'length' : ITERATE_KEY,)return Reflect.ownKeys(target)}
TargetType.COLLECTION对应的Handlers捕获器

集合类型包括Map,WeakMap,Set,WeakSet等,而对集合类型的代理模式和对象模式有所不同,因为集合类型和对象类型的操作是不同的
Map类型的原型属性和方法为 clear delete(key) has(key) get(key) set(key) keys() values() size entries() forEach(cb)
set类型的原型属性和方法为 size add(value) clear() delete(key) has(value) keys() values() entries() forEach(cb)
代理对象无法访问集合类型对应的属性和方法: 代理集合类无法将代理对象没法获取到集合类型的属性和方法。

javascript">const set = new Set()
const proxy = new Proxy(set, {get(target, key, receiver) {return Reflect.get(target, key, receiver)}
})
set.size // 0
proxy.size // undefined this指向的是代理对象,通过mutableInstrumentations对象,并且在对应的属性和方法中将this指向为源对象
javascript">function createInstrumentations() {const mutableInstrumentations: Instrumentations = {get(this: MapTypes, key: unknown) {return get(this, key)},get size() {return size(this as unknown as IterableCollections)},has,add,set,delete: deleteEntry,clear,forEach: createForEach(false, false),}const shallowInstrumentations: Instrumentations = {get(this: MapTypes, key: unknown) {return get(this, key, false, true)},get size() {return size(this as unknown as IterableCollections)},has,add(this: SetTypes, value: unknown) {return add.call(this, value, true)},set(this: MapTypes, key: unknown, value: unknown) {return set.call(this, key, value, true)},delete: deleteEntry,clear,forEach: createForEach(false, true),}const readonlyInstrumentations: Instrumentations = {get(this: MapTypes, key: unknown) {return get(this, key, true)},get size() {return size(this as unknown as IterableCollections, true)},has(this: MapTypes, key: unknown) {return has.call(this, key, true)},add: createReadonlyMethod(TriggerOpTypes.ADD),set: createReadonlyMethod(TriggerOpTypes.SET),delete: createReadonlyMethod(TriggerOpTypes.DELETE),clear: createReadonlyMethod(TriggerOpTypes.CLEAR),forEach: createForEach(true, false),}const shallowReadonlyInstrumentations: Instrumentations = {get(this: MapTypes, key: unknown) {return get(this, key, true, true)},get size() {return size(this as unknown as IterableCollections, true)},has(this: MapTypes, key: unknown) {return has.call(this, key, true)},add: createReadonlyMethod(TriggerOpTypes.ADD),set: createReadonlyMethod(TriggerOpTypes.SET),delete: createReadonlyMethod(TriggerOpTypes.DELETE),clear: createReadonlyMethod(TriggerOpTypes.CLEAR),forEach: createForEach(true, true),}const iteratorMethods = ['keys','values','entries',Symbol.iterator,] as constiteratorMethods.forEach(method => {mutableInstrumentations[method] = createIterableMethod(method, false, false)readonlyInstrumentations[method] = createIterableMethod(method, true, false)shallowInstrumentations[method] = createIterableMethod(method, false, true)shallowReadonlyInstrumentations[method] = createIterableMethod(method,true,true,)})return [mutableInstrumentations,readonlyInstrumentations,shallowInstrumentations,shallowReadonlyInstrumentations,]
}const [mutableInstrumentations,readonlyInstrumentations,shallowInstrumentations,shallowReadonlyInstrumentations,
] = /* #__PURE__*/ createInstrumentations()// 分隔符function deleteEntry(this: CollectionTypes, key: unknown) {const target = toRaw(this)const { has, get } = getProto(target)let hadKey = has.call(target, key)if (!hadKey) {key = toRaw(key)hadKey = has.call(target, key)} else if (__DEV__) {checkIdentityKeys(target, has, key)}const oldValue = get ? get.call(target, key) : undefined// forward the operation before queueing reactionsconst result = target.delete(key)if (hadKey) {trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result
}function clear(this: IterableCollections) {const target = toRaw(this)const hadItems = target.size !== 0const oldTarget = __DEV__? isMap(target)? new Map(target): new Set(target): undefined// forward the operation before queueing reactionsconst result = target.clear()if (hadItems) {trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)}return result
}function createForEach(isReadonly: boolean, isShallow: boolean) {return function forEach(this: IterableCollections,callback: Function,thisArg?: unknown,) {const observed = this as anyconst target = observed[ReactiveFlags.RAW]const rawTarget = toRaw(target)const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive!isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)return target.forEach((value: unknown, key: unknown) => {// important: make sure the callback is// 1. invoked with the reactive map as `this` and 3rd arg// 2. the value received should be a corresponding reactive/readonly.return callback.call(thisArg, wrap(value), wrap(key), observed)})}
}
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {const target = (this as any)[ReactiveFlags.RAW]const rawTarget = toRaw(target)const rawKey = toRaw(key)if (!isReadonly) {if (hasChanged(key, rawKey)) {track(rawTarget, TrackOpTypes.HAS, key)}track(rawTarget, TrackOpTypes.HAS, rawKey)}return key === rawKey? target.has(key): target.has(key) || target.has(rawKey)
}function size(target: IterableCollections, isReadonly = false) {target = (target as any)[ReactiveFlags.RAW]!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)return Reflect.get(target, 'size', target)
}function add(this: SetTypes, value: unknown, _isShallow = false) {if (!_isShallow && !isShallow(value) && !isReadonly(value)) {value = toRaw(value)}const target = toRaw(this)const proto = getProto(target)const hadKey = proto.has.call(target, value)if (!hadKey) {target.add(value)trigger(target, TriggerOpTypes.ADD, value, value)}return this
}
依赖收集,依赖触发

track=> 依赖收集
trigger => 依赖触发
track的时机: get(), get size(), has(), forEach()
trigger的时机: add(), set(), delete(), clear()
优化内容

  • 在add()中通过has()判断当前添加的元素是否已经存在于set集合中时,若是已经存在,则不需要进行trigger()操作,因为set集合本身的一个特性就是去重
  • 在delete()中通过has()判断当前删除的元素或属性是否存在,若不存在,则不需要进行trigger()操作,因此当前的操作是无效的
    避免污染原始数据
  • 通过重写集合类型的方法并手动指定其中this指向为原始对象的方式,能够解决代理对象无法访问集合类型对应的属性和方法的问题。但是会导致原始数据污染的问题
  • 只希望代理对象才具备依赖收集和依赖更新的能力,然后通过原始数据进行的操作不具有响应式的能力
javascript">// 原数数据 originalData1
const originalData1 = new Map({});
// 代理对象 proxyData1
const proxyData1 = reactive(originalData1);
// 另一个代理对象 proxyData2
const proxyData2 = reactive(new Map({}));
// 将 proxyData2 做为 proxyData1 一个键值
// 【注意】此时的 set() 经过重写,其内部 this 已经指向 原始对象(originalData1),等价于 原始对象 originalData1 上存储了一个 响应式对象 proxyData2
proxyData1.set("proxyData2", proxyData2);
// 若不做额外处理,如下基于 原始数据的操作 就会触发 track 和 trigger
originalData1.get("proxyData2").set("name", "zs");

在源码中可以通过value=toRaw(value)获取当前设置值对应的原始数据,可以避免响应式数据对原始数据的污染。
处理forEach回调参数
Map.prototype.forEach(callbackFn, [, thisArg])其中的callback回调函数会接受三个参数, 当前的值value, 当前的键key,正在被遍历的Map对象
遍历操作等价于读取操作,在处理普通对象的get()捕获器中有一个处理,如果当前访问的属性值是对象类型,那么就会向外返回其对应的代理对象。

javascript">function createForEach(isReadonly: boolean, isShallow: boolean) {return function forEach(this: IterableCollections,callback: Function,thisArg?: unknown,) {const observed = this as anyconst target = observed[ReactiveFlags.RAW]const rawTarget = toRaw(target)const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive!isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)return target.forEach((value: unknown, key: unknown) => {// important: make sure the callback is// 1. invoked with the reactive map as `this` and 3rd arg// 2. the value received should be a corresponding reactive/readonly.// 将map类型的键和值进行响应式处理,以及进行track操作return callback.call(thisArg, wrap(value), wrap(key), observed)})}
}

处理迭代器
Map和Set都实现了可迭代协议,因此它们还可以通过for…of的方式进行遍历。

javascript">  const iteratorMethods = ['keys','values','entries',Symbol.iterator,] as constiteratorMethods.forEach(method => {mutableInstrumentations[method] = createIterableMethod(method, false, false)readonlyInstrumentations[method] = createIterableMethod(method, true, false)shallowInstrumentations[method] = createIterableMethod(method, false, true)shallowReadonlyInstrumentations[method] = createIterableMethod(method,true,true,)})function createIterableMethod(method: string | symbol,isReadonly: boolean,isShallow: boolean,
) {return function (this: IterableCollections,...args: unknown[]): Iterable & Iterator {const target = (this as any)[ReactiveFlags.RAW]const rawTarget = toRaw(target)const targetIsMap = isMap(rawTarget)const isPair =method === 'entries' || (method === Symbol.iterator && targetIsMap)const isKeyOnly = method === 'keys' && targetIsMapconst innerIterator = target[method](...args)const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive!isReadonly &&track(rawTarget,TrackOpTypes.ITERATE,isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY,)// return a wrapped iterator which returns observed versions of the// values emitted from the real iteratorreturn {// iterator protocolnext() {const { value, done } = innerIterator.next()return done? { value, done }: {value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),done,}},// iterable protocol[Symbol.iterator]() {return this},}}
}

后续还有,见下篇


http://www.ppmy.cn/embedded/86753.html

相关文章

oracle登录报“ORA-27101: shared memory realm does not exist”

oracle登录报“ORA-27101: shared memory realm does not exist” 问题&#xff1a; 1、使用ip:1521/服务名方式连库报错" ORA-27101: shared memory realm does not exist Linux-x86_64 Error: 2: No such file or directory" 2、sqlplus XX/密码 可以登录数据库 …

Python 爬虫入门(一):从零开始学爬虫 「详细介绍」

Python 爬虫入门&#xff08;一&#xff09;&#xff1a;从零开始学爬虫 「详细介绍」 前言1.爬虫概念1.1 什么是爬虫&#xff1f;1.2 爬虫的工作原理 2. HTTP 简述2.1 什么是 HTTP&#xff1f;2.2 HTTP 请求2.3 HTTP 响应2.4 常见的 HTTP 方法 3. 网页的组成3.1 HTML3.2 CSS3.…

MySQL 备忘清单

本备忘单旨在快速理解 MySQL 所涉及的主要概念&#xff0c;提供了最常用的SQL语句&#xff0c;供您参考。 来源&#xff1a;https://dev.bi/docs/mysql.html 入门 介绍 MySQL 为关系型数据库(Relational Database Management System)&#xff0c;一个关系型数据库由一个或数…

合作伙伴中心Partner Center中添加了Copilot预览版

目录 一、引言 二、Copilot 功能概述 2.1 Copilot 简介 2.2 Copilot 的核心功能 2.3 Copilot 的访问和使用 三、Copilot 的使用方法 3.1 Copilot 功能区域 3.2 Copilot 使用示例 3.2.1 编写有效提示 3.2.2 使用反馈循环 四、负责任的人工智能 4.1 Copilot 结果的可…

20240724----安装git和配置git的环境变量/如何用命令git项目到本地idea

备注参考博客&#xff1a; 1&#xff09;可以参考博客&#xff0c;用git把项目git到本地 2&#xff09;可以参考博客vcs没有git 3)git版本更新&#xff0c;覆盖安装 &#xff08;一&#xff09;安装git &#xff08;1&#xff09;官网下载的链接 https://git-scm.com/downlo…

【SpringBoot】URL映射之consumes和produces匹配、params和header匹配

4.2.3 consumes和produces匹配 //处理request Content-Type为"application/json"类型的请求 RequestMapping(value"/Content",methodRequestMethod.POST,consumes"application/json") public String Consumes(RequestBody Map param){ return…

数据库 执行sql添加删除字段

添加字段&#xff1a; ALTER TABLE 表明 ADD COLUMN 字段名 类型 DEFAULT NULL COMMENT 注释 AFTER 哪个字段后面; 效果&#xff1a; 删除字段&#xff1a; ALTER TABLE 表明 DROP COLUMN 字段;

常见的文心一言的指令

文心一言&#xff0c;作为百度研发的预训练语言模型“ERNIE 3.0”的一项功能&#xff0c;能够与人对话互动&#xff0c;回答问题&#xff0c;协助创作&#xff0c;高效便捷地帮助人们获取信息、知识和灵感。以下是一些常见的文心一言指令类型及其具体示例&#xff1a; 1. 查询…