概览
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
.