Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • reactivity响应式

    • ref
    • reactive
    • 基础对象的代理
    • 数组代理的方法
    • 集合对象的代理
      • 概览
      • 源码分析
        • createInstrumentationGetter
        • createInstrumentations
        • createIterableMethod
    • Reflect和Proxy详解
    • 依赖的收集与触发
    • effectScope解析
    • effect解析
    • reactive响应式依赖的收集与触发监听
    • 批量更新实现
    • ReactiveEffect类介绍
    • computed
  • 5.18源码学习》
  • reactivity响应式
东流
2025-08-14
目录

集合对象的代理

# 概览

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)
};
1
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
    );
  };
}
1
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;
}
1
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;
        }
      };
    };
  }
1
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.

编辑 (opens new window)
上次更新: 2025/08/20, 10:23:42
数组代理的方法
Reflect和Proxy详解

← 数组代理的方法 Reflect和Proxy详解→

最近更新
01
computed
08-26
02
ReactiveEffect类介绍
08-26
03
批量更新实现
08-25
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式