概览
vue3中实现集合对象(Map/WeakMap/Set/WeakSet)的处理器方法,也是针对四种集合对象的代理方法,如:响应式集合对象、浅层响应式集合对象、只读集合对象和浅层只读集合对象。但是集合对象处理器方法没有使用class的集成实现,具体参见packages\reactivity\src\collectionHandlers.ts
源码分析
首先要理解集合对象的代理方法就是一个包含get方法的对象,如下所示:
js
const mutableCollectionHandlers = {
get: /* @__PURE__ */ createInstrumentationGetter(false, false)
};
const shallowCollectionHandlers = {
get: /* @__PURE__ */ createInstrumentationGetter(false, true)
};
const readonlyCollectionHandlers = {
get: /* @__PURE__ */ createInstrumentationGetter(true, false)
};
const shallowReadonlyCollectionHandlers = {
get: /* @__PURE__ */ createInstrumentationGetter(true, true)
};
createInstrumentationGetter
createInstrumentationGetter方法就是用于创建getter方法,接受两个参数:isReadonly(是否只读)和shallow(是否是浅层响应)。
createInstrumentationGetter的源码实现如下:
js
function createInstrumentationGetter(isReadonly2, shallow) {
const instrumentations = createInstrumentations(isReadonly2, shallow);
return (target, key, receiver) => {
if (key === "__v_isReactive") {
return !isReadonly2;
} else if (key === "__v_isReadonly") {
return isReadonly2;
} else if (key === "__v_raw") {
return target;
}
return Reflect.get(
hasOwn(instrumentations, key) && key in target ? instrumentations : target,
key,
receiver
);
};
}
createInstrumentationGetter方法也是一个高阶函数,它返回一个getter方法。首先会调用createInstrumentations获取一个对象instrumentations,该对象内部就是包含vue3针对集合对象Map/WeakMap/Set/WeakSet重写的一些实例(静态)方法;然后返回一个getter,getter内部首先会先判断key值是否是__v_isReactive、__v_isReadonly或者是__v_raw,返回值由参数isReadonly2或target决定;若key不是这三者之一,则调用Reflect.get,若key是instrumentations中重写实现的方法名并且也是集合对象的原生方法,则Reflect.get的第一个参数是instrumentations,否则为target。
createInstrumentations
createInstrumentations方法的源码实现如下:
js
function createInstrumentations(readonly, shallow) {
const instrumentations = {
get(key) {
const target = this["__v_raw"];
const rawTarget = toRaw(target);
const rawKey = toRaw(key);
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, "get", key);
}
track(rawTarget, "get", rawKey);
}
const { has } = getProto(rawTarget);
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive;
if (has.call(rawTarget, key)) {
return wrap(target.get(key));
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey));
} else if (target !== rawTarget) {
target.get(key);
}
},
get size() {
const target = this["__v_raw"];
!readonly && track(toRaw(target), "iterate", ITERATE_KEY);
return Reflect.get(target, "size", target);
},
has(key) {
const target = this["__v_raw"];
const rawTarget = toRaw(target);
const rawKey = toRaw(key);
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, "has", key);
}
track(rawTarget, "has", rawKey);
}
return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey);
},
forEach(callback, thisArg) {
const observed = this;
const target = observed["__v_raw"];
const rawTarget = toRaw(target);
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive;
!readonly && track(rawTarget, "iterate", ITERATE_KEY);
return target.forEach((value, key) => {
return callback.call(thisArg, wrap(value), wrap(key), observed);
});
}
};
extend(
instrumentations,
readonly ? {
add: createReadonlyMethod("add"),
set: createReadonlyMethod("set"),
delete: createReadonlyMethod("delete"),
clear: createReadonlyMethod("clear")
} : {
add(value) {
if (!shallow && !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, "add", value, value);
}
return this;
},
set(key, value) {
if (!shallow && !isShallow(value) && !isReadonly(value)) {
value = toRaw(value);
}
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 (!!(process.env.NODE_ENV !== "production")) {
checkIdentityKeys(target, has, key);
}
const oldValue = get.call(target, key);
target.set(key, value);
if (!hadKey) {
trigger(target, "add", key, value);
} else if (hasChanged(value, oldValue)) {
trigger(target, "set", key, value, oldValue);
}
return this;
},
delete(key) {
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 (!!(process.env.NODE_ENV !== "production")) {
checkIdentityKeys(target, has, key);
}
const oldValue = get ? get.call(target, key) : void 0;
const result = target.delete(key);
if (hadKey) {
trigger(target, "delete", key, void 0, oldValue);
}
return result;
},
clear() {
const target = toRaw(this);
const hadItems = target.size !== 0;
const oldTarget = !!(process.env.NODE_ENV !== "production") ? isMap(target) ? new Map(target) : new Set(target) : void 0;
const result = target.clear();
if (hadItems) {
trigger(
target,
"clear",
void 0,
void 0,
oldTarget
);
}
return result;
}
}
);
const iteratorMethods = [
"keys",
"values",
"entries",
Symbol.iterator
];
iteratorMethods.forEach((method) => {
instrumentations[method] = createIterableMethod(method, readonly, shallow);
});
return instrumentations;
}
相比之前的vue3源码,createInstrumentations简化了集合对象的重写,参数readonly和shallow表示是否是只读对象和是否是浅响应式对象,它们决定了返回的对象instrumentations中会包含哪些方法以及方法的具体实现。
先来回顾下集合对象分别有哪些方法:
| 方法 | Map |
Set |
WeakMap |
WeakSet |
|---|---|---|---|---|
.size |
✅ | ✅ | ❌ | ❌ |
.set(key, value) |
✅ | - | ✅ | - |
.get(key) |
✅ | - | ✅ | - |
.has(key) |
✅ | ✅ | ✅ | ✅ |
.delete(key) |
✅ | ✅ | ✅ | ✅ |
.clear() |
✅ | ✅ | ❌ | ❌ |
.add(value) |
- | ✅ | - | ✅ |
.keys() |
✅ | ✅ | ❌ | ❌ |
.values() |
✅ | ✅ | ❌ | ❌ |
.entries() |
✅ | ✅ | ❌ | ❌ |
.forEach() |
✅ | ✅ | ❌ | ❌ |
Symbol.iterator |
✅ (同.entries) |
✅ (同.values) |
❌ | ❌ |
对比createInstrumentations方法,其内部就是定义了如上12种方法,如下:
-
get(key)
get(key)方法是Map和WeakMap实例的方法,用于获取指定键对应的值。
get(key)接受一个key参数,表示键,首先通过this["__v_raw"]获取实例target,这里的this指向的就是Reflect.get的第三个参数receiver,即getter中第三个参数receiver表示代理对象。获取代理对象的实例target后,调用toRaw获取原始数据rawTarget以及原始键rawKey。然后判断readonly,若不是只读对象,则判断key是否是原始键rawKey,若不是,则为key调用track建立依赖收集;然后为rawKey调用track建立依赖。接着,用
Reflect.getPrototypeOf获取原始实例rawTarget的has方法;根据shallow和readonly来决定wrap装饰方法;若是浅层响应,则将toShallow赋值给wrap;否则判断readonly,若是深层响应只读,则wrap为toReadonly;若是深层响应可写对象,则wrap为toReactive。这样确保了最后获取的值和原始实例的响应式和只读特性保持一致。最后通过一些
if...else获取值,若是target.has(key)存在,则返回wrap(target.get(key));否则若是rawTarget.has(rawKey)存在,则返回wrap(target.get(rawKey));否则最后判断target和rawTarget是否是同一对象,若是,则直接返回target.get(key) -
**
get size()
size属性是Map和Set实例的属性,用于获取集合的元素数量。
get size()本质上是一个访问器属性方法,内部就是先获取代理对象的实例target,然后判断是否只读,若不是只读对象,则调用track建立依赖,当该代理对象被迭代时,就会触发响应的依赖(监听)。最后通过Reflect.get获取target的size属性值,并返回。 -
has(key)
Map、Set、WeakMap和WeakSet均有has方法,用于判断集合是否包含指定的键或值。
has(key)方法会先获取代理对象的实例target,然后通过toRaw获取原始对象rawTarget和原始键rawKey。判断readonly,若不是只读对象,则判断key是否是原始键rawKey即是否发生了改变,若不等,则为key调用track建立依赖收集;然后为rawKey调用track建立依赖。最后,判断
key值是否是原始键rawKey,若是,则调用target.has(key)返回结果;若不是,则优先调用target.has(key),若为false,再调用target.has(rawKey)返回结果。 -
forEach(callback, thisArg)
forEach是Map和Set的实例方法,用于遍历集合中的元素。接受一个函数callback参数和thisArg对象。
forEach同样会先获取代理对象的实例target以及原始对象rawTarget,然后根据shallow和readonly获取装饰函数wrap。再根据是否只读,若不是只读对象,则调用track建立依赖收集。最后调用
target.forEach遍历集合对象,执行callback方法,并且调用wrap包装键key和值key。
add、set、delete和clear会改变代理对象的实例target,因此对于只读对象,不应该调用这些方法,vue3中在开发环境,当对只读集合对象调用者四个方法时,会打印警告信息,内部不会做其他操作。
-
add(value)
add(value)是Set和WeakSet的方法,用于新增元素。
add(value)方法接收参数value,若是深层响应式对象,并且value也是深层响应式且不是只读的,则会调用toRaw(value)获取参数的原始值并赋值给value。然后获取代理对象的实例target以及它的原型proto,调用原型的has方法判断target上是否存在相同的value,若不存在即hadKey为false,则调用target.add新增元素,再调用trigger触发target上与add相关的监听;最后返回this。这样确保了Set上的值都是唯一的,而且最后返回this,可以方便进行链式操作。 -
set(key, value)
set(key,value)是Map和WeakMap的实例用于新增键值对的方法。同
add方法,set方法会对value进去去响应式处理,获取原始值value。然后获取代理对象的实例target,以及从它的原型上获取has和get方法。然后判断target上是否存在key属性,若不存在,则将key去响应式,继续调用has方法判断target上是否存在原始key属性,记为hadKey;调用原型上的get方法获取target上的key值记为旧值oldValue;调用target.set(key,value)新增键值对。然后判断hadKey值的布尔属性,若hadKey为false,则说明target上不存在key键和原始key键,这是一个新增操作,那么就会调用trigger触发add相关的监听;若hadKey存在,则说明是一个重新赋值的更新操作,判断旧值和新值是否相等,若不等,则调用trigger触发set相关的监听。最后返回this。 -
delete(key)
Map、Set、WeakMap和WeakSet均有delete方法。对于Map和WeakMap,该方法是删除指定键值对(即键为key);对于Set和WeakSet,该方法是删除指定元素(key即为元素)。
delete(key)方法会先获取代理对象的实例target以及其原型上的has和get方法。然后调用has方法来判断target上是否存在key属性(或键),若不存在,则获取key的原始值,判断target上是否存在原始key,记为hadKey;由前面我们知道get方法只有Map和WeakMap的实例才有,因此若是Set或WeakSet的实例,则不能通过get获得旧值oldValue;然后调用target.delete(key)删除元素结果记为result,然后判断hadKey的布尔属性,若存在,则调用trigger触发delete相关的依赖;最后返回result. -
clear()
clear()是Map和Set的实例方法,用于清空集合中的所有元素。
clear()方法会先获取代理对象的实例target,然后读取对象的size判断是否为0;然后调用isMap判断当前target是Map实例还是Set实例,通过new构建新的实例记为oldTarget;然后调用target.clear()清空元素或者键值对,结果记为result。若当前target不是空对象,则调用trigger触发clear相关的监听,旧值是oldTarget。 -
keys()、values()、entries()、Symbol.iterator上述四种方法都是集合对象的迭代器方法,用于遍历集合中的元素,只有
Map和Set实例才有。vue3中是基于createIterableMethod重写了它们。
createIterableMethod
createIterableMethod顾名思义,就是用于创建可迭代的方法,接受三个参数:method(方法名)、isReadonly2(是否只读)、isShallow2(是否是浅层响应)。返回一个函数,该函数返回一个对象,实现了[Symbol.iterator]方法。其实现如下:
js
function createIterableMethod(method, isReadonly2, isShallow2) {
return function(...args) {
const target = this["__v_raw"];
const rawTarget = toRaw(target);
const targetIsMap = isMap(rawTarget);
const isPair = method === "entries" || method === Symbol.iterator && targetIsMap;
const isKeyOnly = method === "keys" && targetIsMap;
const innerIterator = target[method](...args);
const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive;
!isReadonly2 && track(
rawTarget,
"iterate",
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
);
return {
// iterator protocol
next() {
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;
}
};
};
}
createIterableMethod返回函数中的this依旧是指向代理对象,获取代理对象的对象target以及原始对象rawTarget,调用isMap判断当前原始对象是否是Map实例;若method方法名是entries或者是Symbol.iterator且当前原始对象是Map的实例,则isPair为true,否则为false。若方法名是keys且当前原始对象是Map实例,则isKeyOnly为true
调用target[method](...args)获取对象默认的迭代器,记为innerIterator。根据是否是浅层响应以及是否只读确定装饰方法wrap;若不是只读对象,则调用track建立iterate相关的依赖收集。
最后的部分就是定义返回的对象,返回对象中包含两个方法:next()和[Symbol.iterator]。next中就是对默认的迭代器进行调用,获取value和done,若done为true,说明迭代完了,则直接返回{value,done};若done为false,则判断是否是isPair,若isPair为true,则说返回[wrap(value[0]), wrap(value[1])];否则直接返回wrap(value)。而[Symbol.iterator]内部就是返回this.