集合对象的代理
# 概览
vue3中实现集合对象(Map/WeakMap/Set/WeakSet
)的处理器方法,也是针对四种集合对象的代理方法,如:响应式集合对象、浅层响应式集合对象、只读集合对象和浅层只读集合对象。但是集合对象处理器方法没有使用class
的集成实现,具体参见packages\reactivity\src\collectionHandlers.ts
# 源码分析
首先要理解集合对象的代理方法就是一个包含get
方法的对象,如下所示:
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)
};
2
3
4
5
6
7
8
9
10
11
12
# createInstrumentationGetter
createInstrumentationGetter
方法就是用于创建getter
方法,接受两个参数:isReadonly
(是否只读)和shallow
(是否是浅层响应)。
createInstrumentationGetter
的源码实现如下:
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
);
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
方法的源码实现如下:
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
相比之前的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]
方法。其实现如下:
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;
}
};
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
.