本文基于Vue 3.2.30版本源码进行分析
为了增加可读性,会对源码进行删减、调整顺序、改变部分分支条件的操作,文中所有源码均可视作为伪代码
由于ts版本代码携带参数过多,不利于展示,大部分伪代码会取编译后的js代码,而不是原来的ts代码

本文内容

  1. Set数据响应式测试代码调试与Vue3对应源码总结
  2. Map数据响应式测试代码调试与Vue3对应源码总结
本篇文章不对ref类型shallow类型readonly类型进行总结分析
本篇文章主要集中总结Vue3对于Map和Set数据一些常见方法代理时的源码处理,不深入探究原理
本文尽可能对源码中涉及到的读写操作的源码进行列举和总结,难免会有遗漏

前置说明

在上一篇Vue3源码-响应式系统-依赖收集和派发更新流程浅析文章中,我们梳理了整个响应式系统依赖收集和派发更新的流程,还简单地介绍了Proxy以及Reflect,明白了响应式的基本原理就是拦截target一些方法,比如get,比如set,然后进行依赖收集和派发更新
但是我们并没有对Vue3中响应式数据类型的分类,响应式数据常见属性的拦截等源码进行分析,本篇文章将基于Vue3源码各种非原始值的数据响应式进行总结,主要研究的源代码核心部分如下所示:

function reactive(target) {
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    const targetType = getTargetType(target);
    if (targetType === 0 /* INVALID */) {
        return target;
    }
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    return proxy;
}

const mutableHandlers = {
    get,
    set,
    deleteProperty,
    has,
    ownKeys
};
const mutableCollectionHandlers = {
    get: /*#__PURE__*/ createInstrumentationGetter(false, false)
};

new Proxy(target)中可以发现,响应式监听数据分为两种targetType=2以及targetType=1,从上面和下面代码块可以得知,当target=2时,即数据类型为Map/Set/WeapMap/WeapSet时,new Proxy(target, collectionHandlers),本文将基于collectionHandlers进行分析

function targetTypeMap(rawType) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return 1 /* COMMON */;
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return 2 /* COLLECTION */;
        default:
            return 0 /* INVALID */;
    }
}

核心源码

由于集合类型的方法较为复杂,因此跟Object/Array不同的是,我们直接从源码分析它们响应式的逻辑,而不从读/写方面分析

主要分为两个部分,第一个部分是getsizehas等等常规方法

const mutableInstrumentations = {
    get(key) {
        return get$1(this, key);
    },
    get size() {
        return size(this);
    },
    has: has$1,
    add,
    set: set$1,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, false)
};

第二部分是keysvaluesentries等迭代方法

const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator];
iteratorMethods.forEach(method => {
    mutableInstrumentations[method] = createIterableMethod(method, false, false);
});
function createIterableMethod(method, isReadonly, isShallow) {
    return function (...args) {
        const rawTarget = toRaw(target);
        //......
        track(rawTarget, "iterate" /* ITERATE */, isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY);
        return {
            next() {
                const { value, done } = innerIterator.next();
                return done ? { value, done }
                    : { value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), done };
            },
            [Symbol.iterator]() {
                return this;
            }
        };
    };
}

迭代器和可迭代对象

为下面的forEach、entries、keys、values的分析做理论准备

迭代器/迭代器协议 iterator protocol

迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
只有实现了一个拥有以下语义的 next() 方法,一个对象才能成为迭代器

属性
next一个无参数的或者可以接受一个参数的函数,返回一个应当拥有以下两个属性的对象:
done(boolean)
next()方法必须返回一个对象,该对象应当有两个属性: donevalue,如果返回了一个非对象值(比如 falseundefined),则会抛出一个 TypeError 异常("iterator.next() returned a non-object value")。

可迭代对象 iterable protocol

要成为可迭代对象,一个对象必须实现@@iterator方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为@@iterator的属性,可通过常量 Symbol.iterator访问该属性:

属性
[Symbol.iterator]一个无参数的函数,其返回值为一个符合迭代器协议
的对象。

示例说明

typeof aGeneratorObject.next;
// 返回"function", 因为有一个 next 方法,所以这是一个迭代器

typeof aGeneratorObject[Symbol.iterator];
// 返回"function", 因为有一个 @@iterator 方法,所以这是一个可迭代对象

aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// 返回 true,因为 @@iterator 方法返回自身(即迭代器),所以这是一个格式良好的可迭代对象

接收可迭代对象的内置API

很多 API 接受可迭代对象作为参数,例如:

new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2); // "b"

let myObj = {};

new WeakMap([
    [{}, 'a'],
    [myObj, 'b'],
    [{}, 'c']
]).get(myObj);             // "b"

new Set([1, 2, 3]).has(3); // true
new Set('123').has('2');   // true

new WeakSet(function* () {
    yield {}
    yield myObj
    yield {}
}()).has(myObj);           // true

可迭代对象的应用

一些语句和表达式需要可迭代对象,比如 for...of 循环、展开语法yield*,和解构赋值

for(let value of ["a", "b", "c"]){
    console.log(value); // "a", "b", "c"
}
[..."abc"]; // ["a", "b", "c"]

function* gen() {
  yield* ["a", "b", "c"];
}
gen().next(); // { value: "a", done: false }

[a, b, c] = new Set(["a", "b", "c"]);

for....in和for....of的区别

**for...in**语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性(原型链上的key)
**for...of**语句可迭代对象(包括 ArrayMapSetStringTypedArrayarguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句,遍历可迭代对象定义要迭代的数据

forEach循环无法中途跳出,break和return都无法中断循环,而for...in和for...of可以
for...in是无序的,for...of是有序的

自定义实现一个可迭代对象和迭代器协议

核心结构

var myIterator = {
  next: function() {
    // ...
  },
  [Symbol.iterator]: function() {
    return this;
  }
};

简单示例

function MyArray(array) {
    let nextIndex = 0;
    return {
        next: function () {
            // 应该done:true将nextIndex置为0
            return nextIndex < array.length ? {
                value: array[nextIndex++],
                done: false
            } : {
                done: true
            };
        },
        [Symbol.iterator]: function () {
            return this;
        }
    };
}

var initArray = new MyArray(["我是第一个", "2", "我是第三个"]);
for (let value of initArray) {
    console.log(value);
}

forEach:proxy.forEach(function(value, key, target), thisArg)

测试代码

effect(() => {
    proxyMap.forEach((...args) => {
        console.warn("proxyMap.forEach的item结果是");
        console.info(...args);
    });
});

源码分析

  • 触发Proxy.get()响应,触发track(rawTarget, "iterate", ITERATE_KEY)的依赖收集
  • callback返回数据可能也是Object类型,包括keyvalue,都得使用toReactive(value)+toReactive(key)进行转化为响应式对象后返回
  • forEach可以传递第二个参数thisArgcallback.call调用时需要显式callback.call(thisArg)
  • forEach的第一个参数function(value, key, target)中第三个参数是触发forEach调用的对象,传递代理对象observed
function createForEach(isReadonly, isShallow) {
    return function forEach(callback, thisArg) {
        const observed = this;
        const target = observed["__v_raw" /* RAW */];
        const rawTarget = toRaw(target);

        track(rawTarget, "iterate" /* ITERATE */, ITERATE_KEY);

        return target.forEach((value, key) => {
            // 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, toReactive(value), toReactive(key), observed);
        });
    };
}

entries/Symbol.iterator/keys/values

测试代码 & 原始方法逻辑分析

  • entriesMapSet都会返回一个可迭代对象,可以使用for(let [key,value] of data.entries())进行遍历

    • Map.entries()返回的是[key, value]数据
    • Set.entries()返回的是[value, value]数据
  • keys:由于Set没有key,因此Set.keys()等同于Set.values()Map.keys()获取所有的keys
  • values:由于Set没有key,因此Set.keys()等同于Set.values()Map.values()获取所有的value
  • Symbol.iterator

    • Set[Symbol.iterator]Set.entries()表现不一致,如下面代码块所示,Set[Symbol.iterator]更接近于Set.valuesSet[Symbol.iterator] === Set.entriesfalse
    • Map[Symbol.iterator]Map.entries()表现一致,Map[Symbol.iterator] === Map.entriestrue
const mySet = new Set(); mySet.add("0");mySet.add("1");
const setIter = mySet[Symbol.iterator]();
console.log(setIter.next().value); // "0"
for(let value of mySet[Symbol.iterator]()) {
  // Symbol.iterator let...of 0
  // Symbol.iterator let...of 1
  console.warn("Symbol.iterator let...of", value);
}


const set1 = new Set(); set1.add("0");set1.add("1");
const iterator1 = set1.entries();
console.log(iterator1.next().value); // ['0', '0']
for(let value of set1.entries()) {
  // Symbol.iterator let...of ['0', '0']
  // Symbol.iterator let...of ['1', '1']
  console.warn("entries let...of", value);
}

源码分析

返回数据

  • 根据上面上面迭代器和可迭代对象的理论分析,我们需要实现一个可迭代对象和实现迭代器协议,即next()[Symbol.iterator]()
  • 根据entries/Symbol.iterator+Map进行数据的返回:[value[0], value[1]],返回数据都进行toReactive()响应式包裹
  • 其余的method=keys/values/Symbol.iterator+Set,都只返回一个数据,即value,返回数据都进行toReactive()响应式包裹

依赖追踪

  • 如果使用keys,那么我们就只需要关注key的变化,不需要关注value的变化,因此使用MAP_KEY_ITERATE_KEY进行依赖追踪,触发track(rawTarget, "iterate", MAP_KEY_ITERATE_KEY)
  • 如果使用values/entries/Symbol.iterator,都需要关注value的变化,因此使用ITERATE_KEY进行依赖追踪,触发track(rawTarget, "iterate", ITERATE_KEY)
function createIterableMethod(method, isReadonly, isShallow) {
    return function (...args) {
        const target = this["__v_raw" /* 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 = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
        !isReadonly &&
            track(rawTarget, "iterate" /* 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;
            }
        };
    };
}

总结

根据Map/Set数据类型的特性,仿写对应的next()[Symbol.iterator]实现迭代器和可迭代对象,在仿写的基础上,实现对应的依赖收集逻辑和返回数据响应式包裹逻辑

size:proxy.size

测试代码

effect(() => {
    console.log("map.size", proxyMap.size);
});

源码分析

触发Proxy.size()响应,然后进行track(target, "iterate", ITERATE_KEY)的依赖收集

Object/Array不同的是,Map/Set不能通过代理对象访问属性,会报Uncaught TypeError: Method get Map.prototype.size called on incompatible receiver undefined的错误
因此Reflect.get()的第三个参数receiver需要显示更改为原始对象target
function size(target, isReadonly = false) {
    target = target["__v_raw" /* RAW */];
    track(toRaw(target), "iterate" /* ITERATE */, ITERATE_KEY);
    return Reflect.get(target, 'size', target);
}

get:proxy.get("key")

测试代码

const object = {
    "item1": 1,
    "item2": 333
}
const map = new Map(Object.entries(object));
const objectKey = { test: 1 };
const reactiveKey = reactive(objectKey);
map.set(reactiveKey, "testReactive");
const proxy = reactive(map);

const objectKeyProxy = { testProxy: 1 };
const reactiveKeyProxy = reactive(objectKeyProxy);
proxy.set(reactiveKeyProxy, "testProxyReactive");

effect(() => {
    console.log("map.get(普通number)", proxy.get(0));
    console.log("map.get(proxy对象)", proxy.get(reactiveKey));
    console.log("map.get(原始对象)", proxy.get(objectKey));
    console.log("map.get(proxy对象之后才set的)", proxy.get(reactiveKeyProxy));
});

代码分析

  • 触发Proxy.get()响应,如果key是响应式,需要进行keytrack,即track(target, "get", key)+track(target, "get", rawKey)的依赖收集
  • 触发Proxy.get()响应,如果key不是响应式,则进行track(target, "get", rawKey)的依赖收集
  • 除了上面的依赖收集之外,需要使用原始对象进行get()方法的调用,即target.get(key)
  • 如果返回对象target.get(key)Object类型,需要转化为响应式的,调用reactive(value)
  • 由于key可能是响应式的Object Key,因此需要判断has.call(rawTarget, key)是否为true,如果不为true,需要去进行has.call(rawTarget, rawKey)的判断,然后进行具体的get请求返回要求的值
function get(target, key, isReadonly = false, isShallow = false) {
    target = target["__v_raw" /* RAW */];
    const rawTarget = toRaw(target);
    const rawKey = toRaw(key);
    if (key !== rawKey) {
        !isReadonly && track(rawTarget, "get" /* GET */, key);
    }
    !isReadonly && track(rawTarget, "get" /* GET */, rawKey);
    const { has } = getProto(rawTarget);
      // const toReactive = (value) => isObject(value) ? reactive(value) : value;
    const wrap = isShallow ? toShallow : isReadonly ? 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) {
        // #3602 readonly(reactive(Map))
        // ensure that the nested reactive `Map` can do tracking for itself
        target.get(key);
    }
}

has:proxy.has("key")

测试代码

const object = {
    "item1": 1,
    "item2": 333
}
const map = new Map(Object.entries(object));
const proxy = reactive(map);
effect(() => {
    console.log("map.has", proxy.has(4));
});

源码分析

  • 触发Proxy.has()响应,如果key是响应式,需要进行keytrack,即track(target, "get", key)+track(target, "get", rawKey)的依赖收集
  • 触发Proxy.has()响应,如果key不是响应式,则进行track(target, "get", rawKey)的依赖收集
function has(key, isReadonly = false) {
    const target = this["__v_raw" /* RAW */];
    const rawTarget = toRaw(target);
    const rawKey = toRaw(key);
    if (key !== rawKey) {
        !isReadonly && track(rawTarget, "has" /* HAS */, key);
    }
    !isReadonly && track(rawTarget, "has" /* HAS */, rawKey);
    return key === rawKey
        ? target.has(key)
        : target.has(key) || target.has(rawKey);
}

Set.add

测试代码

effect(() => {
    console.log("Set.add", proxySet.add(5));
});

源码分析

  • 触发Proxy.add()响应,如果value不存在,使用原生Set.add()方法,然后trigger(target, "add", value, value)的依赖收集

    • 触发Proxy.size()所依赖收集的ITERATE_KEY,触发所有涉及到sizeeffects重新执行(元素新增,size也新增了)
    • 由于不是Map数据,因此不会触发MAP_KEY_ITERATE_KEYMAP_KEY_ITERATE_KEY关联的是Mapkey数据,是MapforEach以及for...in所收集的依赖
  • 如果value存在,则什么都不操作
function add(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" /* ADD */, value, value);
    }
    return this;
}
function trigger(target, type, key, newValue, oldValue, oldTarget) {
    if (key !== void 0) {
        deps.push(depsMap.get(key));
    }
    switch (type) {
        case "add" /* ADD */:
            if (!isArray(target)) {
                deps.push(depsMap.get(ITERATE_KEY));
                if (isMap(target)) {
                    deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
                }
            }
            break;
    }
}

Map.set

测试代码

effect(()=> {
  console.log("Map.set", proxy.set(0, 44444));
});

源码分析

  • Map.set会判断是否存在key,如果存在,则触发trigger(target, "add" /* ADD */, key, value)
  • Map.set会判断是否存在key,如果不存在,则触发trigger(target, "set" /* SET */, key, value, oldValue)
function set(key, 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 {
        checkIdentityKeys(target, has, key);
    }
    const oldValue = get.call(target, key);
    target.set(key, value);
    if (!hadKey) {
        trigger(target, "add" /* ADD */, key, value);
    }
    else if (hasChanged(value, oldValue)) {
        trigger(target, "set" /* SET */, key, value, oldValue);
    }
    return this;
}

新增key以及value

trigger(target, "add" /* ADD */, key, value)

  • depsMap.get(key)的所有effecs重新执行,此时key=Map数据中新的key
  • 触发跟size相关的ITERATE_KEYeffecs重新执行(增加key会影响Map.size的属性,而循环遍历,比如forEachfor..of都需要获取Map.size,因为遍历得要知道到底需要遍历item为几个)
  • 触发跟keys相关的MAP_KEY_ITERATE_KEYeffecs重新执行(由上面entries/Symbol.iterator/keys/values的分析可以知道,这个MAP_KEY_ITERATE_KEY是为了关联key集合而产生的,因为对于一些遍历来说,比如for(let key of map.keys()),我们只需要关注keys,不需要关注values的变化)
function trigger(target, type, key, newValue, oldValue, oldTarget) {
    if (key !== void 0) {
        deps.push(depsMap.get(key));
    }
    switch (type) {
        case "add" /* ADD */:
            if (!isArray(target)) {
                deps.push(depsMap.get(ITERATE_KEY));
                if (isMap(target)) {
                    deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
                }
            }
    }
}

更新已经存在的keyvalue

trigger(target, "set" /* SET */, key, value, oldValue)

  • depsMap.get(key)的所有effecs重新执行
  • size相关的ITERATE_KEYeffecs重新执行
function trigger(target, type, key, newValue, oldValue, oldTarget) {
    if (key !== void 0) {
        deps.push(depsMap.get(key));
    }
    switch (type) {
        case "set" /* SET */:
            if (isMap(target)) {
                deps.push(depsMap.get(ITERATE_KEY));
            }
            break;
    }
}

按照常识来说,更新已经存在的keyvalue不应该触发跟size相关的ITERATE_KEYeffecs重新执行,但是源码确实这么做了,如下面示例代码,每次更新已经存在的keyvalue后,虽然proxyMap.size不会变化,但是effect会重新执行

已经提了github vue3-issue,由于本文基于Vue 3.2.30版本源码进行分析,就算后续版本修复该问题,Vue 3.2.30版本仍然也是有这个问题,因此这里总结为:在Vue 3.2.30版本下,任何Map.set操作都会触发跟size相关的ITERATE_KEYeffecs重新执行,包括Map.size/forEachfor..of等遍历方法
const map = new Map();
map.set("item1", 1);
map.set("item2", 2);

const proxyMap = reactive(map);

effect(()=> {
 console.info("proxyMap.size", proxyMap.size);
});

onMounted(()=> {
 setInterval(() => {
   proxyMap.set("item1", new Date().getTime());
 }, 2000);
});

delete

测试代码

effect(() => {
    console.log("size.delete", proxy.delete("item2"));
});

源码分析

  • 触发Proxy.delete(key)响应,直接调用原始对象target.delete(key)
  • Map.delete会判断是否存在key/toRaw(key),如果存在,则触发trigger(target, "delete" /* DELETE */, key, undefined, oldValue)
  • Map.delete会判断是否存在key,如果不存在,则判断toRaw(key)是否存在,如果不存在,则什么都不操作
function deleteEntry(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 {
        // 检测 key === toRaw(key),进行警告
        checkIdentityKeys(target, has, key);
    }
    const oldValue = get ? get.call(target, key) : undefined;
    // forward the operation before queueing reactions
    const result = target.delete(key);
    if (hadKey) {
        trigger(target, "delete" /* DELETE */, key, undefined, oldValue);
    }
    return result;
}

派发更新

trigger(target, "delete" /* DELETE */, key, undefined, oldValue);

  • depsMap.get(key)的所有effecs重新执行
  • 触发跟size相关的ITERATE_KEYeffecs重新执行(删除key会影响Map.size的属性,而循环遍历,比如forEachfor..of都需要获取Map.size,因为遍历得要知道到底需要遍历item为几个)
  • 触发跟keys相关的MAP_KEY_ITERATE_KEYeffecs重新执行(由上面entries/Symbol.iterator/keys/values的分析可以知道,这个MAP_KEY_ITERATE_KEY是为了关联key集合而产生的,因为对于一些遍历来说,比如for(let key of map.keys()),我们只需要关注keys,不需要关注values的变化)
function trigger(target, type, key, newValue, oldValue, oldTarget) {
    if (key !== void 0) {
        deps.push(depsMap.get(key));
    }
    switch (type) {
        case "delete" /* DELETE */:
            if (!isArray(target)) {
                deps.push(depsMap.get(ITERATE_KEY));
                if (isMap(target)) {
                    deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
                }
            }
            break;
    }
}

clear

测试代码

effect(() => {
    console.log("Map.clear", proxy.clear());
});

源码分析

  • 触发Proxy.clear()响应,直接调用原始对象target.clear()
  • 然后判断没清空之前的size是否为0,如果不为0,则trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget)
function clear() {
    const target = toRaw(this);
    const hadItems = target.size !== 0;
    const oldTarget = isMap(target)
        ? new Map(target)
        : new Set(target)
        ;
    // forward the operation before queueing reactions
    const result = target.clear();
    if (hadItems) {
        trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget);
    }
    return result;
}

派发更新

trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget)
调用所有该对象的depsMap,触发该对象涉及到的所有effects重新执行

function trigger(target, type, key, newValue, oldValue, oldTarget) {
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        // never been tracked
        return;
    }
    let deps = [];
    if (type === "clear" /* CLEAR */) {
        // collection being cleared
        // trigger all effects for target
        deps = [...depsMap.values()];
    }
}

总结

Object/Array不同,Map/Set无法调用Reflect.set(target, key, receiver)/Reflect.get(target, key, receiver)进行数据的操作,只能使用原生数据target进行一系列的操作,代理Map/Set多个方法时,主要有4个步骤:

  • 转化key/valuetoRaw(),对key/toRaw(key)以及value/toRaw(value)进行处理
  • 调用Map/Set原生的方法进行数据的处理
  • 根据方法适配不同的依赖收集和派发更新
  • 返回值应该判断是否需要toReactive(object)包裹

参考文章

  1. for...of - JavaScript | MDN
  2. 迭代协议 - JavaScript | MDN
  3. 迭代器和生成器 - JavaScript | MDN

Vue系列其它文章

  1. Vue2源码-响应式原理浅析
  2. Vue2源码-整体流程浅析
  3. Vue2源码-双端比较diff算法 patchVNode流程浅析
  4. Vue3源码-响应式系统-依赖收集和派发更新流程浅析
  5. Vue3源码-响应式系统-Object、Array数据响应式总结
  6. Vue3源码-响应式系统-Set、Map数据响应式总结
  7. Vue3源码-响应式系统-ref、shallow、readonly相关浅析
  8. Vue3源码-整体流程浅析
  9. Vue3源码-diff算法-patchKeyChildren流程浅析

白边
209 声望37 粉丝

源码爱好者,已经完成vue2和vue3的源码解析+webpack5整体流程源码+vite4开发环境核心流程源码+koa2源码