vue3 源码学习(2)—— reactivity-创建响应式对象的代理

1,549 阅读15分钟

前言

本篇文章的主要目的是搞懂响应式的实现原理。因此,关于其代码的细节部分,不会过多研究,仅以其核心代码为主。

相关文章:

  1. vue3 开发环境搭建
  2. vue3 源码学习(3)—— effect

Git 仓库:

vue3-demo 源码地址

准备

由于 vue3 中的各个模块都要要用到 @vue/shared 中的公共方法。所以,我们先把它里面的文件复制到自己的项目中,以便后面使用。

另外,vue3 实现响应式的方法与 vue2 不同,它使用的是 Proxy,而不是 Object.definedProperty

如果你还不了解,建议先去学习一下。同时,还要了解一下 Reflect,因为它们总是会同时使用。

createReactiveObject

想必大家都已了解 vue3 中的 reactiveshallowReactivereadonlyshallowReadonly 函数。

这些函数,虽然功能有所不同,但都会返回一个对象的代理。但这个代理,实际上是由一个叫 createReactiveObject 的函数创建的。就像下面这样。

// reactivity/src/reactive.ts

export function reactive(target) {
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}

export function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap);
}

export function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);
}

export function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap);
}

reactive 等函数,在创建对象的代理时,会将它们各自的参数传给 createReactiveObject 函数,这样就可以根据参数的不同,来实现其相应的功能。

代码分析

从上述代码中,我们已知道,createReactiveObject 才是创建响应式代理的核心函数。下面,我们来分析一下这个函数。

参数

createReactiveObject 有五个参数:

  1. target,需要被代理的对象。

  2. isReadonly,一个 Boolean,用于判断已被代理过的对象,是不是又被 readonlyshallowReadonly 进行了代理。

  3. baseHandlers 传给 Proxy,作为其第二参数,即 handler

  4. collectionHandlers 传给 Proxy,作为其第二参数,即 handler

  5. proxyMap:用来缓存已被代理过的 target,避免对同一个 target 重复代理。

注意,baseHandlerscollectionHandlers 这两个参数,都是作为 Proxyhandler,但它们不会同时存在,而是根据 对象的类型 进行选择。

例如:

  • 对象的类型是 '[object Object]''[object Array]',选 baseHandlers
  • 对象的类型是 '[object Map]''[object Set]''[object WeakMap]''[object WeakSet]',选 collectionHandlers

返回值

createReactiveObject 最终会返回一个对象的代理,即 Proxy

细节问题

createReactiveObject 创建响应式的代理,需要注意以下问题:

  1. 需要代理的 target,其数据类型必须是对象
  2. target 已经是 Proxy 对象,则无需再代理
let data = reactive(obj);
let data2 = reactive(data); // 传入代理对象 data,则不会再次代理
  1. 同一个 target,无需重复代理
let data = reactive(obj);
let data2 = reactive(obj); // 再次传入 obj,则不会再代理
  1. 对象的类型必须是 '[object Object]''[object Array]''[object Map]''[object Set]''[object WeakMap]''[object WeakSet]' 其中之一,否则直接返回。

代码实现

在上文中,我们了解到, createReactiveObject 有五个参数。但在本文的代码讲解中,为求简便,在创建对象的代理时,将省略对 collectionHandlers 这一参数的处理。

换句话说,就是本文中的代码只考虑为对象类型是 '[object Object]''[object Array]' 的对象创建代理。

创建 Proxy 对象

reactivity / src / reactive.ts 文件中,编写创建响应式代理的相关代码。

// reactivity/src/reactive.ts

import {
    mutableHandlers,
    readonlyHandlers,
    shallowReactiveHandlers,
    shallowReadonlyHandlers
  } from './baseHandlers'

import { isObject, toRawType } from '@vue/shared';

// 关于响应式的枚举对象
export const enum ReactiveFlags {
    IS_REACTIVE = '__v_isReactive',
    RAW = '__v_raw'
}

// `reactive`、`shallowReactive`、`readonly`、`shallowReadonly` 各自的 map,用于缓存它们各自创建的 target 的 Proxy 对象,避免对其重复代理
export const reactiveMap = new WeakMap();
export const shallowReactiveMap = new WeakMap();
export const readonlyMap = new WeakMap();
export const shallowReadonlyMap = new WeakMap();

const enum TargetType {
    INVALID = 0,
    COMMON = 1,
    COLLECTION = 2,
}

// 对象类型
function targetTypeMap(rawType: string) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return TargetType.COMMON;
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return TargetType.COLLECTION;
        default:
            return TargetType.INVALID;
    }
}

// 获取 value 的类型
function getTargetType(value) {
    return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));
}

export function reactive(target) {
    return createReactiveObject(target, false, mutableHandlers, reactiveMap);
}

export function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers, shallowReactiveMap);
}

export function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers, readonlyMap);
}

export function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyMap);
}

// 创建对象代理的核心函数
function createReactiveObject(target, isReadonly, baseHandlers, proxyMap) {
    // 若 target 不是对象,则直接返回
    if (!isObject(target)) {
        console.warn(`参数必须是对象`);
        return target;
    }
    
    // 已经是 Proxy 对象,则直接返回
    // 异常处理: 在响应式对象上调用 readonly()
    if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
        console.warn(`已经是代理对象`);
        return target;
    }

    // 若 target 已经代理过,则无需重复代理。用于避免同一个普通对象被多次代理。
    const exisitingProxy = proxyMap.get(target);
    if (exisitingProxy) {
        console.warn(`已被代理过`);
        return exisitingProxy;
    }

    // 只观察特定的值类型,例如:'Object'、'Array'、'Map'、'Set'、'WeakMap'、'WeakSet'
    const targetType = getTargetType(target);
    if (targetType === TargetType.INVALID) {
        console.warn('类型有误');
        return target;
    }
    
    // 通过 Proxy 对 target 进行代理。代理对象进行取值触发 get,赋值则触发 set
    const proxy = new Proxy(target, baseHandlers);

    // 存储已被代理过的对象
    proxyMap.set(target, proxy);

    // 返回代理对象
    return proxy;
}

handler

新建 reactivity / src / baseHandlers.ts。此文件模块是为 '[object Array]''[object Object]' 这样的对象定义 handler

reactiveshallowReactivereadonlyshallowReadonly 都有各自的handler,主要区别在于它们的 getset 函数。

因此,就定义了 createGettercreateSetter 函数,用以创建 getset 函数。

// reactivity/src/baseHandlers.ts

import {
  reactive,
  readonly,
  toRaw,
  ReactiveFlags,
  readonlyMap,
  reactiveMap,
  shallowReactiveMap,
  shallowReadonlyMap,
  isReadonly,
  isShallow
} from './reactive';

import {
  isObject,
  extend,
  isArray,
  hasOwn,
  isIntegerKey,
  hasChanged
} from '@vue/shared';

// `reactive`、`shallowReactive`、`readonly`、`shallowReadonly` 各自的 get 函数
const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true);


// 判断对象自身属性中是否具有指定的属性
function hasOwnProperty(key) {
  const obj = toRaw(this);
  return obj.hasOwnProperty(key);
}

// 创建 get 函数
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        if (key === ReactiveFlags.IS_REACTIVE) {
          // 调用 isReactive() 触发,或在响应式对象上调用了 readonly()
          return !isReadonly;
        } else if (key === ReactiveFlags.IS_READONLY) {
          // 调用 isReadonly() 触发
          return isReadonly
        } else if (key === ReactiveFlags.IS_SHALLOW) {
          // 调用 isShallow() 触发
          return shallow
        } else if (
          key === ReactiveFlags.RAW &&
          receiver ===
            (isReadonly
              ? shallow
                ? shallowReadonlyMap
                : readonlyMap
              : shallow
              ? shallowReactiveMap
              : reactiveMap
            ).get(target)
        ) {
          // 若是条件为真,则说明 target 已被代理过。receiver 是 target 的代理对象(proxy)。
          return target;
        }
        
        if (!isReadonly) {
          // hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
          if (key === 'hasOwnProperty') {
            return hasOwnProperty
          }
        }
        
        // Reflect.get 方法,允许你从一个对象中取属性值。
        const res = Reflect.get(target, key, receiver);
        
        // shallow 真,则说明是一个浅层响应式对象,它只有根级别的属性是响应式的。
        if (shallow) {
          return res
        }
        
        // 深度代理对象。取值时,判断访问的属性值,是否为对象,若是,则对其进行代理。
        if (isObject(res)) {
            return isReadonly ? readonly(res) : reactive(res)
        }

        return res;
    };
}

// `reactive`、`shallowReactive`、`readonly`、`shallowReadonly` 各自的 set 函数
const set = createSetter();
const shallowSet = createSetter(true);

// 创建 set 函数
function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        let oldValue = target[key];
        
        // 排除浅模式
        if (!shallow) {
          if (!isShallow(value) && !isReadonly(value)) {
            oldValue = toRaw(oldValue);
            value = toRaw(value);
          }
        } else {
          // 在浅模式下,不管是否响应,对象都被设置为原样
        }
        
        // Reflect.set 方法允许你在对象上设置属性。它返回一个 Boolean 值表明是否成功设置属性。
        const result = Reflect.set(target, key, value, receiver);
        return result;
    };
}

// 拦截对对象属性的 delete 操作
function deleteProperty(target, key) {
  const result = Reflect.deleteProperty(target, key);
  return result;
}
// handler.has() 方法是针对 in 操作符的代理方法。检查目标对象上是否存在key属性
function has(target, key) {
  const result = Reflect.has(target, key);
  return result;
}
// 用于拦截 Reflect.ownKeys(),返回一个可枚举对象
function ownKeys(target) {
  return Reflect.ownKeys(target);
}

// 导出 reactive 创建对象的代理时,所需要的 handler
export const mutableHandlers = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
};

// 导出 readonly 创建对象的代理时,所需要的 handler
export const readonlyHandlers = {
  get: readonlyGet,
  set(target, key) {
    // 不能为 只读对象 设置 key。只需返回 true,什么也不做。
    console.warn(
      `[Vue warn]: Set operation on key "${String(key)}" failed: target is readonly.`,
      target
    );
    return true;
  },
  deleteProperty(target, key) {
    // 不能删除 只读对象 的 key
    console.warn(
      `[Vue warn]: Delete operation on key "${String(key)}" failed: target is readonly.`,
      target
    );
    return true;
  }
};

// 导出 shallowReactive 创建对象的代理时,所需要的 handler
export const shallowReactiveHandlers = extend({}, mutableHandlers, {
  get: shallowGet,
  set: shallowSet
});

// 导出 shallowReadonly 创建对象的代理时,所需要的 handler
export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
  get: shallowReadonlyGet
});

导出函数

reactivity / src / index.ts 文件中,导出 reactive 等函数。

// reactivity/src/index.ts

export {
  reactive,
  readonly,
  shallowReactive,
  shallowReadonly
} from './reactive';

上述代码完成后,我们就可以使用 reactive 等函数,来创建对象的代理了。下面,我们来讲解一下它的细节问题。

细节问题解析

上文,我们介绍了 createReactiveObject 创建代理对象时,所需要注意的四个问题。下面,我们来分析一下它们的代码实现。

问题-1

使用 isObject 来判断 target 是不是对象,若不是,则直接返回它。换言之,reactiveshallowReactivereadonlyshallowReadonly 接收的参数,其类型必须是对象。

// 若 target 不是对象,则直接返回
if (!isObject(target)) {
    console.warn(`参数必须是对象`);
    return target;
}

问题-2

通过枚举对象 ReactiveFlagsRAWIS_REACTIVE 属性,判断 target 是否已经是代理对象。这需要在 createReactiveObjectcreateGetter 这个两个函数中,写入相应的判断条件。

  • createReactiveObject 中的判断条件。
// 已经是 Proxy 对象,则直接返回
// 异常处理: 在响应式对象上调用 readonly()
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
    console.warn(`已经是代理对象`);
    return target;
}
  • createGetter 中的判断条件
if (key === ReactiveFlags.IS_REACTIVE) {
  // 调用 isReactive 时触发此条件,或在响应式对象上调用了 readonly()
  return !isReadonly;
} else if (
  key === ReactiveFlags.RAW &&
  receiver ===
    (isReadonly
      ? shallow
        ? shallowReadonlyMap
        : readonlyMap
      : shallow
      ? shallowReactiveMap
      : reactiveMap
    ).get(target)
) {
  // 若是条件为真,则说明 target 已被代理过。receiver 是 target 的代理对象(proxy)。
  return target;
}

下面,我们来讲解一下,这两个判断条件,是如何防止已代理的对象被再次代理的:

  1. target 是已被代理过的对象。然后,我们把它作为参数,传给 reactive 函数。

  2. 当代码执行到 if (target[ReactiveFlags.RAW]) 时,target 会进行取值操作,而取值,则必定触发 targetget 方法,这是因为被代理过的对象都有 get 方法。

  3. 既然触发了 get,那么在 get 中获取到的 key 就会是 __v_raw,所以key === ReactiveFlags.RAW 必然为真,然后返回 target,即得到一个真值。

  4. target[ReactiveFlags.IS_REACTIVE] 的触发原理也是一样,若是 !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) 最终值为 true,那么整个条件为真,就会返回 target,不再继续执行,从而避免再次对已代理过的对象进行代理。

问题-3

定义 reactiveshallowReactivereadonlyshallowReadonly 各自的 map,用于缓存它们各自创建的 targetProxy 对象,避免对其重复代理。

export const reactiveMap = new WeakMap();
export const shallowReactiveMap = new WeakMap();
export const readonlyMap = new WeakMap();
export const shallowReadonlyMap = new WeakMap();

在每一次创建代理对象前,先判断一下当前的 target 是不是已被代理过,又再次被代理的。

如果是,就获取其对应的值并返回 proxyMap.get(target)

// 若 target 已经代理过,则无需重复代理。用于避免同一个普通对象被多次代理。
const exisitingProxy = proxyMap.get(target);
if (exisitingProxy) {
    console.warn(`已被代理过`);
    return exisitingProxy;
}

如果不是,就创建其代理对象,然后把它保存到 proxyMap 中。

// 存储已被代理过的对象
proxyMap.set(target, proxy);

另外,使用原生的 WeakMap,是因为其持有的是每个键对象的 弱引用,这意味着在没有其他引用存在时垃圾回收能正确进行。

问题-4

对象的类型分为三类,并用数字枚举为每个类型匹配一个与之对应的数值

  • 第一类:Object、Array,对应值 1
  • 第二类:Map、Set、WeakMap、WeakSet,对应值 2
  • 第三类:其它,例如:Date,对应值 0

以上说的对象的类型,可通过 Object.prototype.toString.call() 得到。

// reactivity/src/reactive.ts

const enum TargetType {
    INVALID = 0,
    COMMON = 1,
    COLLECTION = 2,
}

function targetTypeMap(rawType) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return TargetType.COMMON;
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return TargetType.COLLECTION;
        default:
            return TargetType.INVALID;
    }
}

定义 getTargetType函数,根据对象的类型,获取其相应的值。

function getTargetType(value) {
    return !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(value));
}

Object.isExtensible() 判断一个对象是否可以扩展。

例如,用 Object.seal 密封的对象,是就不能再扩展的。对于这种情况,可直接返回 TargetType.INVALID

toRawTypeshared 模块中的一个方法。它可以把 "RawType",从像 "[object RawType]"这样的字符串中提取出来。

例如, 用 Object.prototype.toString.call(Array) 得到 "[object Array]" 字符串,我们将它传入 toRawType,就可以获得字符串 "Array".

根据所得对象的类型,进行判断。

// 对象的类型必须是 'Object'、'Array'、'Map'、'Set'、'WeakMap'、'WeakSet' 其中之一 ,否则直接返回
const targetType = getTargetType(target); 
if (targetType === TargetType.INVALID) { 
    return target;
}

测试

执行 pnpm run dev 命令进行打包,然后在 html 中引入 reactivity.global.js,进行测试。下面是测试案例。

const { reactive } = VueReactivity;
const obj = { name: 'demo', age: 18, info: { title: '信息'} };

// 测试:参数必须是对象
//const data = reactive(20);

// 测试:深度代理
// const data = reactive(obj);
// console.log(data.info);

// 测试:重复代理
// const data = reactive(obj);
// const data2 = reactive(obj);

// 测试:已经是代理对象
// const data = reactive(obj);
// const data2 = reactive(data);

// 测试:取值、赋值
const data = reactive(obj);

console.log(data.name); // 取值

data.name = '哈哈'; // 赋值
setTimeout(() => {
    console.log(data.name);
}, 1000);

关于数组的问题

Object.defineProperty 相比,虽然 Proxy 能够直接对数组进行代理,但是在使用数组的某些方法(例如:indexOfincludes 等方法)时,无法避免对其长度的跟踪,甚至在某些情况下还会导致无限循环。所以,仍需要像 vue2 一样,对这些数组方法进行重写。

vue3 源码,重写了 8 种数组方法,可分为两种类型:

  1. 不会改变数组的方法:includes, indexOf, lastIndexOf
  2. 会改变数组的方法:push, pop, shift, unshift, splice

为了更好地理解,下面我们用打包好的 reactivity.global.js 进行演示。

不会改变数组的方法

includes, indexOf, lastIndexOf 这些方法仅是获取数组某个值的下标值或是判断其是否存在,而不会改变数组。因此,它们只会触发数组的代理对象的 get 函数。

测试案例:

const { reactive } = VueReactivity;
const data = reactive(['js', 'node']);

// 使用 indexOf 测试
data.indexOf('js');

测试结果:

截屏2022-09-01 16.15.10.png

当调用 indexOf 访问数组中 js 的下标值时,就会触发 get,但触发了多次。然而,我们仅需要 keyindexOf 这一次。

会改变数组的方法

push, pop, shift, unshift, splice 这些方法会改变数组,它们会触发数组的代理对象的 getset 函数。

测试案例:

const { reactive } = VueReactivity;
const data = reactive(['js', 'node']);

// 使用 unshift 测试
data.unshift('html');

测试结果:

截屏2022-09-02 16.45.25.png

当调用 unshift 这种会改变数组的方法时,即访问了数组,也对其做出了修改。所以,它会触发 getset。虽然,这两个函数都被触发了多次,但就 get 而言,我们只需要 keyunshift 这一次。

重写数组方法

  1. 添加 createArrayInstrumentations 方法,实现对上述 8 种数组方法的重写。
// reactivity / src / baseHandler.ts

// 存放重写后的数组方法的对象
const arrayInstrumentations = createArrayInstrumentations();

// 重写数组方法,在实现上,内部依旧是通过原生的数组方法来调用,只是要做一些改变。
function createArrayInstrumentations() {
    const instrumentations = {};
    ['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
        // this -> 原数组的代理对象,args -> 传入数组方法中的参数
        instrumentations[key] = function (this, ...args) {
            // 通过 toRaw 返回原数组,避免死循环导致栈溢出
            const arr = toRaw(this);
            
            // 通过数组的原生方法调用,倘若参数 args 是响应式的,则要用 toRaw 将其还原为原始对象,然后再调用。
            const res = arr[key](...args);
            
            // 'includes' 如果找到匹配的字符串则返回 true,否则返回 false
            // 'indexOf' 和 'lastIndexOf' 如果没有找到匹配的字符串则返回 -1
            if (res === -1 || res === false) {
                return arr[key](...args.map(toRaw));
            } else {
                return res;
            }
        };
    });
    
    ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
        instrumentations[key] = function (this, ...args) {
            const arr = toRaw(this);
        
            // 通过数组的原生方法调用,并通过 apply 方法,防止this 指向改变
            const res = arr[key].apply(this, args);
            return res;
        };
    });

    return instrumentations;
}
  1. 修改 createGetter 函数,添加对数组的处理代码。
// reactivity / src / baseHandler.ts

function createGetter() {
    return function get(target, key, receiver) {
        
        // 略...
        
        // 判断 target 是否为数组,且 key 存在于 arrayInstrumentations 上
        const targetIsArray = isArray(target);
        if (!isReadonly) {
          if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
            console.log(`数组-get-操作: key->${key}`);
            return Reflect.get(arrayInstrumentations, key, receiver);
          }
          
          // hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
          if (key === 'hasOwnProperty') {
            return hasOwnProperty
          }
        }
        
        // 略...
    };
}

将上述代码添加到 const res = Reflect.get(target, key, receiver) 代码的前面。

  1. 修改 createSetter 函数,添加对数组的处理代码。
// reactivity / src / baseHandler.ts

function createSetter() {
    return function set(target, key, value, receiver) {
        let oldValue = target[key];

        // 排除浅模式
        if (!shallow) {
          if (!isShallow(value) && !isReadonly(value)) {
            oldValue = toRaw(oldValue);
            value = toRaw(value);
          }
        } else {
          // 在浅模式下,不管是否响应,对象都被设置为原样
        }

        // isArray 和 isIntegerKey 方法,是用来判断当前操作的对象是否为数组
        // Number(key) < target.length,为 真,则是对数组中的某个值进行修改,否则就是对数组执行新增,即添加值
        // hasOwn(target, key),对象上是否存在某个属性,为真,则是修改,否则就是新增
        const hadKey =
            isArray(target) && isIntegerKey(key)
                ? Number(key) < target.length
                : hasOwn(target, key);

        // Reflect.set 方法允许你在对象上设置属性。它返回一个 Boolean 值表明是否成功设置属性。
        const result = Reflect.set(target, key, value, receiver);

        // 是同一个对象,才能执行触发操作
        if (target === toRaw(receiver)) {
          // hadKey 为 false,表示当前对象执行新增操作,否则就是在执行修改操作。
          // 这样判断,是为了区分对象执行的操作(新增或修改),以便明确如何执行触发。
          if (!hadKey) {
            console.log(`set-新增操作: key->${key}, hadKey->${hadKey}`);
          } else if (hasChanged(value, oldValue)) {
            console.log(`set-修改操作: key->${key}, hadKey->${hadKey}`);
          }
        }

        return result;
    };
}

注意,target === toRaw(receiver) 这段代码,只有当其为真,才能执行触发操作。下面,介绍一下这样做的缘由。

receiver 指的是最初被调用的对象。通常是 proxy 本身,但 handlerset 方法也有可能在原型链上,或以其他方式被间接地调用。换句话说,也不一定就是 proxy 本身。

看下面这个例子:

let p = new Proxy({}, {
    set: function (target, key, value, receiver) {
      console.log(`target:`, target);
      console.log(`receiver:`, receiver);
      return Reflect.set(target, key, value, receiver);
    }
});

// Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
let obj = Object.create(p);
obj.name = 'jen';

执行 obj.name = 'jen', obj 不是一个 proxy,且自身不含 name 属性,但是它的原型链上有一个 proxy,那么,这个 proxy 的 set() 处理器会被调用,而此时,obj 会作为 receiver 参数传进来。

那么,就会出现 receiver 不是 proxy 本身的情况。

所以,我们需要通过 target === toRaw(receiver) 来判断是不是同一个对象,即为 true 时,才执行触发操作。

另外,触发操作,分两种情况:新增修改

hadKey 值为 false 时,表示对象执行新增操作,否则就是在执行修改操作。因此,我们可以根据 hadKey 来判断如何执行触发操作。

注意,这里所说的触发,是指和依赖收集相关的操作。但,在目前的代码中,只是加以区分,并未做相应处理。

测试案例:

const { reactive } = VueReactivity;
const arr = ['js', 'node'];
const data = reactive(arr);

data.unshift('html')
console.log(data, '结果');

测试结果:

1.png

toRaw

toRaw() 返回由 reactive()readonly() shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。

// reactivity/src/reactive.ts

// 返回代理对象的原始对象
export function toRaw(observed) {
    const raw = observed && observed[ReactiveFlags.RAW];
    return raw ? toRaw(raw) : observed;
}

observed[ReactiveFlags.RAW] 的触发原理,可参考上文 问题2 中做出的解析。

isReadonly

检查传入的值是否为只读对象。

// reactivity/src/reactive.ts

export function isReadonly(value) {
  return !!(value && value[ReactiveFlags.IS_READONLY]);
}

value[ReactiveFlags.IS_READONLY] 的触发原理,可参考上文 问题2 中做出的解析。

isReactive

检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。

// reactivity/src/reactive.ts

export function isReactive(value) {
  if (isReadonly(value)) {
    return isReactive(value[ReactiveFlags.RAW]);
  }
  return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}

value[ReactiveFlags.RAW]value[ReactiveFlags.IS_REACTIVE] 的触发原理,可参考上文 问题2 中做出的解析。

isShallow

检查一个对象是否是由 shallowReactive() 或 shallowReadonly() 创建的代理。

// reactivity/src/reactive.ts

export function isShallow(value) {
  return !!(value && value[ReactiveFlags.IS_SHALLOW]);
}

value[ReactiveFlags.IS_SHALLOW] 的触发原理,可参考上文 问题2 中做出的解析。

结束

本篇文章中的代码及其变量名都是根据源码来写的,希望对想要深入研究的同学们,能有所帮助。