响应式原理实现(2)vue2和vue3

响应式2

vue2响应式实现

提供shallow,决定是否需要深度响应

/*******************新增 shallow*******************/
export function defineReactive(obj, key, val, shallow) {
/****************************************************/
    const property = Object.getOwnPropertyDescriptor(obj, key);
    // 读取用户可能自己定义了的 get、set
    const getter = property && property.get;
    const setter = property && property.set;
    // val 没有传进来话进行手动赋值
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }
    const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher
    /*******************新增****************************/
    // 将新的val也收集响应 observe(val)
    !shallow && observe(val);
  	/******************************************************/
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val;
            if (Dep.target) {
                dep.depend();
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            childOb = !shallow && observe(newVal);
            dep.notify();
        },
    });
}
/*
util.js
export function isObject(obj) {
    return obj !== null && typeof obj === "object";
}
*/
export function observe(value) {
    if (!isObject(value)) {
        return;
    }
    let ob = new Observer(value);
    return ob;
}

代理模式

import { def } from "./util";

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);

const methodsToPatch = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "sort",
    "reverse",
];

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
    // cache original method
    const original = arrayProto[method];
    def(arrayMethods, method, function mutator(...args) {
        const result = original.apply(this, args);
        /*****************这里相当于调用了对象 set 需要通知 watcher ************************/
        // 待补充
        /**************************************************************************** */
        return result;
    });
});
export class Observer {
    constructor(value) {
        /******新增 *************************/
        this.dep = new Dep();
        /************************************/
      	this.walk(value);
    }

    /**
     * 遍历对象所有的属性,调用 defineReactive
     * 拦截对象属性的 get 和 set 方法
     */
    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i]);
        }
    }
}
export function defineReactive(obj, key, val, shallow) {
    const property = Object.getOwnPropertyDescriptor(obj, key);
    // 读取用户可能自己定义了的 get、set
    const getter = property && property.get;
    const setter = property && property.set;
    // val 没有传进来话进行手动赋值
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }
    const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher

    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val;
            if (Dep.target) {
                dep.depend();
                /******新增 *************************/
                if (childOb) {
                    // 当前 value 是数组,去收集依赖
                    if (Array.isArray(value)) {
                        childOb.dep.depend();
                    }
                }
                /************************************/
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            dep.notify();
        },
    });
}

上面已经重写了array方法,不可以直接覆盖全局的array方法,如果当前value是数组,在observer中拦截array方法。

import { arrayMethods } from './array' // 上边重写的所有数组方法
/* export const hasProto = "__proto__" in {}; */
export class Observer {
    constructor(value) {
        this.dep = new Dep();
      	/******新增 *************************/
        if (Array.isArray(value)) {
            if (hasProto) {
                protoAugment(value, arrayMethods);
            } else {
                copyAugment(value, arrayMethods, arrayKeys);
            }
        /************************************/
        } else {
            this.walk(value);
        }
    }

    /**
     * 遍历对象所有的属性,调用 defineReactive
     * 拦截对象属性的 get 和 set 方法
     */
    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i]);
        }
    }
}
/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment(target, src) {
    /* eslint-disable no-proto */
    target.__proto__ = src;
    /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment(target, src, keys) {
    for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i];
        def(target, key, src[key]);
    }
}

上面代码中,实现了数组的操作方法,但是并没有给数组中的元素添加响应式,所以我们需要给数组中的元素添加响应式。

export class Observer {
    constructor(value) {
        this.dep = new Dep();
        def(value, "__ob__", this);
        if (Array.isArray(value)) {
            if (hasProto) {
                protoAugment(value, arrayMethods);
            } else {
                copyAugment(value, arrayMethods, arrayKeys);
            }
          	/******新增 *************************/
            this.observeArray(value);
            /************************************/

        } else {
            this.walk(value);
        }
    }

    /**
     * 遍历对象所有的属性,调用 defineReactive
     * 拦截对象属性的 get 和 set 方法
     */
    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i]);
        }
    }
    observeArray(items) {
        for (let i = 0, l = items.length; i < l; i++) {
            observe(items[i]);
        }
    }
}

如果是多维数组,则需要对多维数组中的元素进行依赖

export function defineReactive(obj, key, val, shallow) {
    const property = Object.getOwnPropertyDescriptor(obj, key);
    // 读取用户可能自己定义了的 get、set
    const getter = property && property.get;
    const setter = property && property.set;
    // val 没有传进来话进行手动赋值
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }
    const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher
    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val;
            if (Dep.target) {
                dep.depend();
                if (childOb) {
                    if (Array.isArray(value)) {
                        childOb.dep.depend(); // [["hello", "wind"],["hello", "liang"]]  这个整体进行了依赖的收集
                        /******新增 *************************/
                        dependArray(value); // 循环数组中的元素,如果是数组的话进行依赖收集。
                        /************************************/
                    }
                }
            }
            return value;
        },
      ...
    }
function dependArray(value) {
    for (let e, i = 0, l = value.length; i < l; i++) {
        e = value[i];
        if (Array.isArray(e)) {
           	e && e.__ob__ && e.__ob__.dep.depend();
            dependArray(e); // 递归进行
        }
    }
}      

同时也需要对插入的数据进行依赖收集,如果是数组,进行数组的依赖收集。
如果是数组,需要将数组中的元素进行响应式处理,对于新添加的元素也进行响应式处理。收集依赖的时候,需要对数组中的数组进行依赖收集。
数组的set和delete方法
数组set
list[0]不会触发watcher收集。数组只能通过重写的push,splice方法去触发

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(target, key, val) {
    if (Array.isArray(target)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val;
    }

    // targe 是对象的情况
    // ...
}

数组del

/**
 * Delete a property and trigger change if necessary.
 */
export function del(target, key) {
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1);
        return;
    }
    // targe 是对象的情况
    // ...
}

对象set和delete方法

  • 对象set方法
// 例子
import { observe, set, del } from "./reactive";
import Watcher from "./watcher";
const data = {
    obj: {
        a: 1,
        b: 2,
    },
};
observe(data);
const updateComponent = () => {
    const c = data.obj.c ? data.obj.c : 0;
    console.log(data.obj.a + data.obj.b + c);
};

new Watcher(updateComponent);

data.obj.c = 3;
/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(target, key, val) {
    if (Array.isArray(target)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val;
    }

    // targe 是对象的情况
    // key 在 target 中已经存在
    if (key in target && !(key in Object.prototype)) { 
        target[key] = val;
        return val;
    }

    const ob = target.__ob__;
    // target 不是响应式数据
    if (!ob) {
        target[key] = val;
        return val;
    }
  	// 将当前 key 变为响应式的
    defineReactive(target, key, val);
    return val;
}

上面的代码虽然设置了set,但是不会新收集watcher, 需要手动调用。
这里将代码中的对象的dep进行收集对象元素中dep收集的watcher

export function defineReactive(obj, key, val, shallow) {
    const property = Object.getOwnPropertyDescriptor(obj, key);
    // 读取用户可能自己定义了的 get、set
    const getter = property && property.get;
    const setter = property && property.set;
    // val 没有传进来话进行手动赋值
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }
    const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher
    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val;
            if (Dep.target) {
                dep.depend();
                if (childOb) {
                  	/******新位置 *************************/
                		childOb.dep.depend();
                  	/**********************************/
                    if (Array.isArray(value)) {
                       // childOb.dep.depend(); //原来的位置
                        dependArray(value);
                    }
                }
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            childOb = !shallow && observe(newVal);
            dep.notify();
        },
    });
}

function dependArray(value) {
    for (let e, i = 0, l = value.length; i < l; i++) {
        e = value[i];
      	/******新位置 *************************/
        e && e.__ob__ && e.__ob__.dep.depend();
      	/**********************************/
        if (Array.isArray(e)) {
           //  e && e.__ob__ && e.__ob__.dep.depend(); // 原位置
            dependArray(e);
        }
    }
}

并不知道 c 被哪些 Watcher 依赖,我们只知道和 c 同属于一个对象的 a 和 b 被哪些 Watcher 依赖,但大概率 c 也会被其中的 Watcher 依赖。所以我们可以在set中手动执行一下obj的Dep,依赖c的Watcher大概率会被执行,相应的c也会成功收集到依赖。
c中的watcher不会在收集,这种情况下,只能通过父级收集依赖,触发依赖,来更新后续步骤。

  • 对象delete方法
    如果要是删除a属性,删除后执行他相应的dep就行。
    同上诉的思想一样,这里也需要更新父级。这样就可以完成delete。
/**
 * Delete a property and trigger change if necessary.
 */
export function del(target, key) {
    if (Array.isArray(target)) {
        target.splice(key, 1);
        return;
    }
    // targe 是对象的情况
    const ob = target.__ob__;
    if (!hasOwn(target, key)) {
        return;
    }
    delete target[key];
    if (!ob) {
        return;
    }
    ob.dep.notify();
}

vue3响应式实现

reactive

在vue2中需要重写数组的方法,来达到对数组响应式。
但是在vue3中,却是不需要,因为vue3中使用的是proxy来做数据拦截,可以原生支持数组的响应式。
这里来解释一下object.definePropertyProxy
object.defineproperty实际上是通过定义或者修改对象属性的描述符来实现数据劫持,缺点是只能拦截get和set操作,无法拦截delete,in,方法调用等属性。动态添加新属性,保证后续使用的属性要在初始化生命data的时候定义,通过this. s e t 设置新属性。通过 d e l e t e 删除属性,响应式丢失,通过 t h i s . set设置新属性。通过delete删除属性,响应式丢失,通过this. set设置新属性。通过delete删除属性,响应式丢失,通过this.delete()删除属性。通过数组索引替换/新增元素,响应式丢失,使用this.$set来设置新元素。使用数组push,pop,shift,unshif,splice,sort,reverse等原生方法来改变原数组的时候,会响应式丢失,需要使用重写/增强后的push,pop,shift,unshift,splice,sort,reverse方法。一次只能对一个属性实现数据劫持,需要遍历对所有的属性进行劫持。数据结构复杂的时候,属性值为引用类型数据,需要通过递归进行处理。
其实object.defineProperty也能拦截Array。但是,还是有如下原因。1. 数组和普通对象在使用场景下会有区别,在项目中使用数组的目的是为了遍历,比较少会使用array[index]=xxx的形式。2. 数组长度是多变的,不可能和普通对象一样在data选项中提前声明好的所有元素。通过array[index]=xxx方式赋值的时候,一旦index超过了现有最大的索引值,那么当前添加的新元素也不会具有响应式。3. 数组存储的元素比较多,不可能为每个数组元素都这是getter/setter。4.无法拦截数组原生方法,比如push,pop,shift,unshift等的调用,最终任然需要重写和增强方法。
proxy主要是用来创建一个对象的代理,从而实现基本操作的拦截和自定义(比如属性查找, 赋值, 枚举, 函数调用等),本质上是通过拦截对象内部方法的执行实现代理,而对象本身根据规范定义的不同又会分为常规对象和异质对象。

  1. get()属性读取操作捕获的捕获器。
  2. set()属性设置操作的捕获器。
  3. deleteProperty()是delete操作符的捕捉器。
  4. ownkeys()是object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕获器。
  5. has()是in操作符的捕获器。
export function reactive(target: object) {
	is(isReadonly(target)) {
		return target
	}
	return createReactiveObject(
		target,
		false,
		mutableHandlers,
		mutableCollectionHandlers,
		reactiveMap
	)
}

target为传进来的对象

function createReactiveObject(
	target: Target,
	isReadonly: boolean,
	baseHandlers: ProxyHandler<any>,
	collectionHandlerrs: ProxyHandler<any>,
	proxyMap: WeakMap<Target>
) {
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {

  // 非对象类型直接返回
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }

  // 目标数据的 __v_raw 属性若为 true,且是【非响应式数据】或 不是通过调用 readonly() 方法,则直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

  // 目标对象已存在相应的 proxy 代理对象,则直接返回
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // 只有在白名单中的值类型才可以被代理监测,否则直接返回
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target     
  }

  // 创建代理对象
  const proxy = new Proxy(
    target,
    // 若目标对象是集合类型(Set、Map)则使用集合类型对应的捕获器,否则使用基础捕获器
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers 
  )
}

createReactiveObject()函数需要做一些前置判断处理。

  • 目标数据是原始值类型,直接返回原数据。
  • __v_raw属性为true,而且非响应式数据或不是通过调用readonly()方法。直接返回原数据。
  • 若目标数据中已经存在响应的proxy代理对象,则直接返回对应的代理对象。
  • 若目标数据中不存在对应的白名单数据类型中,则直接返回原数据。
    支持响应式的数据类型为
    • 可拓展的对象,即他的上面上可以添加新的属性
    • __v_skip属性不存在或者是值为false的对象
    • 数据类型为object, Array, Map, Set, WeakMap, WeakSet对象。
    • 其他数据都被认为无效的响应式对象。
// 白名单
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
  }
}
TargetType.COMMON对应的Handlers捕获器
export const mutableHandlers: ProxyHandler<object> = {
	get,
	set,
	deleteProperty,
	has,
	ownKeys
}

这些对应的就是读取,设置,删除,判断是否存在对应的属性,获取对象自身的属性值。

get捕获器
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _isShallow = false,
  ) {}

  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly,
      isShallow = this._isShallow
    // 当直接通过指定 key 访问 vue 内置自定义的对象属性时,返回其对应的值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return isShallow
    } else if (key === ReactiveFlags.RAW) {
      if (
        receiver ===
          (isReadonly
            ? isShallow
              ? shallowReadonlyMap
              : readonlyMap
            : isShallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        // receiver is not the reactive proxy, but has the same prototype
        // this means the reciever is a user proxy of the reactive proxy
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      // early return undefined
      return
    }
    // 判断是否为数组类型
    const targetIsArray = isArray(target)
    // 数组对象
    if (!isReadonly) {
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      	// 重写/增强数组的方法
      	// - 查找方法: includes, indexof, lastIndexOf
      	// - 修改原数组的方法: push, pop, unshift, shift, splice
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }
    // 获取对应属性值
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    if (isShallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
  • 若当前数据对象是数组,则重写/增强数组对应的方法。
    • 数组的查找方法: includes, indexOf, lastIndexOf
    • 修改原数组的方法: push, pop, unshift, shift, splice
  • 若当前数据对象是普通对象,且非只读的则通过track(target, TrackOpTypes.GET, key)进行依赖收集
    • 若当前数据对象是浅层响应的,则直接返回其对应属性值。
    • 当前对象是ref类型的,则会自动脱ref
  • 若当前数据对象的属性值是对象类型
    • 若当前属性值属于是只读的,则通过readonly(res)向外返回其结果
    • 否则会将当前的属性值以reactie(res)向外返回proxy代理对象
    • 否则直接想外返回对应的属性值
数组类型捕获

数组的查找方法中包含includes, indexof, lastIndexOf,这些方法通常情况下是能够按照预期工作的,但是还需要对某些情况进行特殊处理。

  • 查找的目标数据是响应式数据本身。
const obj = {}
const proxy = reactive([obj])
console.log(proxy.includes(proxy[0])) // false
- 产生原因: 这里涉及到了两次读取操作,第一次是proxy[0],这个时候会触发get捕获器并为obj生成对应代理对象并返回。第二次是proxy.includes()的调用,会遍历数组的每个元素,就是触发get捕获其,生成一个新的代理对象并返回,这两次生成的代理独享不是同一个,因此返回false
- 解决方法: 会在get中设置一个名为proxyMap的weakMap集合用来存储每个响应式对象,在触发get时优先返回proxyMap存在的响应式对象,不管触发多少次都能返回相同的响应式对象。
  • 当在响应式对象中查找原始数据的时候,得到的不是预期结果
const obj = {}
const proxy = reactive([obj])
console.log(proxy.includes(obj)) // fasle
- proxy.includes()会触发get不获取并为obj生成对应代理对象并返回,而includes方法的参数传递是原始数据,相当于此时是响应式和原始数据对象进行比较,结果一定是false
- 解决方案: 核心就是将它们的数据类型统一,就是统一使用原始值数据对象或者响应式数据对比,由于includes()的方法本身不支持对传入参数或者内部响应式数据的处理,因此需要自定义以上的数组查找方法。 在重写/增强的includes, indexOf, lastIndexOf等方法中,会将当前方法内部访问到的响应式数据转换为原始数据,然后调用数组对应的原始方法进行查找,若查找结果为true则直接返回结g果。如果以上操作没有查找到,则通过当前方法传入的参数转换为原始数据,则调用数组的原始方法,此时直接将对应的结果向外进行返回。
  • 处理数组影响length的方法
    隐式修改数组长度的原型方法包括push,pop,shift,unshift, splice等,在调用这些方法的同时会间接的读取数组的length属性,又因为这些方法具有修改数组长度的能力,即相当于length的设置操作,如果不进行特殊处理,会导致与lenght属性相关的副作用函数被重复执行。就是栈溢出。
    • 在调用真正的数组原型方法之前,会通过设置pauseTracking()方法来禁止track依赖收集
    • 在调用数组原生方法之后,在通过resetTracking()方法恢复track进行依赖收集
    • 上面的方法是通过控制shouldTrack变量为true或false,使得在track函数执行是否需要执行原来的依赖收集逻辑
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()

function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // instrument identity-sensitive Array methods to account for possible reactive
  // values
  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      const arr = toRaw(this) as any
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })
  // instrument length-altering mutation methods to avoid length being tracked
  // which leads to infinite loops in some cases (#2137)
  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      pauseTracking()
      pauseScheduling()
      const res = (toRaw(this) as any)[key].apply(this, args)
      resetScheduling()
      resetTracking()
      return res
    }
  })
  return instrumentations
}
set捕获器

处理数组索引index和length
数组的index和length是会相互影响的。

  • 当Number(key)<target.length => 证明是修改操作。对应的是TriggerOpTypes.SET类型,即当前操作不会改变length的值,不需要触发和length相关副作用的执行
  • 当Number(key)>=target.length => 证明是新增操作,TriggerOpTypes.ADD类型,当前操作会改变length的值,需要触发和length相关副作用函数的执行。
  set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
  	// 保存旧的数据
    let oldValue = (target as any)[key]
    // 若原数据值属于只读且ref类型,并且新数据值不属于ref类型,则意味着修改失败
    if (!this._isShallow) {
      const isOldValueReadonly = isReadonly(oldValue)
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        if (isOldValueReadonly) {
          return false
        } else {
          oldValue.value = value
          return true
        }
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    // 是否存在对应的key
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // 设置对应值
    const result = Reflect.set(target, key, value, receiver)
	// 若目标对象是原始原型链上的内容(非自定义添加),则不触发依赖更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
      	// 若目标对象不在对应的key上,则为新增操作。
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
      	// 目标对象存在对应的值,则为修改操作。
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
deleteProperty has ownKeys捕获器

这三个捕获器内容非常简洁,其中has和ownKeys本质上也属于读取操作,因此需要通过track()进行依赖搜集,而deleteProperty相当于修改操作。因此需要trigger()触发更新

  deleteProperty(target: object, key: string | symbol): boolean {
    const hadKey = hasOwn(target, key)
    const oldValue = (target as any)[key]
    const result = Reflect.deleteProperty(target, key)
    if (result && hadKey) {
      trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
    }
    return result
  }
  has(target: object, key: string | symbol): boolean {
    const result = Reflect.has(target, key)
    if (!isSymbol(key) || !builtInSymbols.has(key)) {
      track(target, TrackOpTypes.HAS, key)
    }
    return result
  }
  ownKeys(target: object): (string | symbol)[] {
    track(
      target,
      TrackOpTypes.ITERATE,
      isArray(target) ? 'length' : ITERATE_KEY,
    )
    return Reflect.ownKeys(target)
  }
TargetType.COLLECTION对应的Handlers捕获器

集合类型包括Map,WeakMap,Set,WeakSet等,而对集合类型的代理模式和对象模式有所不同,因为集合类型和对象类型的操作是不同的
Map类型的原型属性和方法为 clear delete(key) has(key) get(key) set(key) keys() values() size entries() forEach(cb)
set类型的原型属性和方法为 size add(value) clear() delete(key) has(value) keys() values() entries() forEach(cb)
代理对象无法访问集合类型对应的属性和方法: 代理集合类无法将代理对象没法获取到集合类型的属性和方法。

const set = new Set()
const proxy = new Proxy(set, {
    get(target, key, receiver) {
        return Reflect.get(target, key, receiver)
    }
})
set.size // 0
proxy.size // undefined this指向的是代理对象,通过mutableInstrumentations对象,并且在对应的属性和方法中将this指向为源对象
function createInstrumentations() {
  const mutableInstrumentations: Instrumentations = {
    get(this: MapTypes, key: unknown) {
      return get(this, key)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, false),
  }

  const shallowInstrumentations: Instrumentations = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, false, true)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add(this: SetTypes, value: unknown) {
      return add.call(this, value, true)
    },
    set(this: MapTypes, key: unknown, value: unknown) {
      return set.call(this, key, value, true)
    },
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, true),
  }

  const readonlyInstrumentations: Instrumentations = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, true)
    },
    get size() {
      return size(this as unknown as IterableCollections, true)
    },
    has(this: MapTypes, key: unknown) {
      return has.call(this, key, true)
    },
    add: createReadonlyMethod(TriggerOpTypes.ADD),
    set: createReadonlyMethod(TriggerOpTypes.SET),
    delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    forEach: createForEach(true, false),
  }

  const shallowReadonlyInstrumentations: Instrumentations = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, true, true)
    },
    get size() {
      return size(this as unknown as IterableCollections, true)
    },
    has(this: MapTypes, key: unknown) {
      return has.call(this, key, true)
    },
    add: createReadonlyMethod(TriggerOpTypes.ADD),
    set: createReadonlyMethod(TriggerOpTypes.SET),
    delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    forEach: createForEach(true, true),
  }

  const iteratorMethods = [
    'keys',
    'values',
    'entries',
    Symbol.iterator,
  ] as const

  iteratorMethods.forEach(method => {
    mutableInstrumentations[method] = createIterableMethod(method, false, false)
    readonlyInstrumentations[method] = createIterableMethod(method, true, false)
    shallowInstrumentations[method] = createIterableMethod(method, false, true)
    shallowReadonlyInstrumentations[method] = createIterableMethod(
      method,
      true,
      true,
    )
  })

  return [
    mutableInstrumentations,
    readonlyInstrumentations,
    shallowInstrumentations,
    shallowReadonlyInstrumentations,
  ]
}

const [
  mutableInstrumentations,
  readonlyInstrumentations,
  shallowInstrumentations,
  shallowReadonlyInstrumentations,
] = /* #__PURE__*/ createInstrumentations()



// 分隔符

function deleteEntry(this: CollectionTypes, key: unknown) {
  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 (__DEV__) {
    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, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

function clear(this: IterableCollections) {
  const target = toRaw(this)
  const hadItems = target.size !== 0
  const oldTarget = __DEV__
    ? isMap(target)
      ? new Map(target)
      : new Set(target)
    : undefined
  // forward the operation before queueing reactions
  const result = target.clear()
  if (hadItems) {
    trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
  }
  return result
}

function createForEach(isReadonly: boolean, isShallow: boolean) {
  return function forEach(
    this: IterableCollections,
    callback: Function,
    thisArg?: unknown,
  ) {
    const observed = this as any
    const target = observed[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    !isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
    return target.forEach((value: unknown, key: unknown) => {
      // 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, wrap(value), wrap(key), observed)
    })
  }
}
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
  const target = (this as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (!isReadonly) {
    if (hasChanged(key, rawKey)) {
      track(rawTarget, TrackOpTypes.HAS, key)
    }
    track(rawTarget, TrackOpTypes.HAS, rawKey)
  }
  return key === rawKey
    ? target.has(key)
    : target.has(key) || target.has(rawKey)
}

function size(target: IterableCollections, isReadonly = false) {
  target = (target as any)[ReactiveFlags.RAW]
  !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  return Reflect.get(target, 'size', target)
}

function add(this: SetTypes, value: unknown, _isShallow = false) {
  if (!_isShallow && !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, TriggerOpTypes.ADD, value, value)
  }
  return this
}
依赖收集,依赖触发

track=> 依赖收集
trigger => 依赖触发
track的时机: get(), get size(), has(), forEach()
trigger的时机: add(), set(), delete(), clear()
优化内容

  • 在add()中通过has()判断当前添加的元素是否已经存在于set集合中时,若是已经存在,则不需要进行trigger()操作,因为set集合本身的一个特性就是去重
  • 在delete()中通过has()判断当前删除的元素或属性是否存在,若不存在,则不需要进行trigger()操作,因此当前的操作是无效的
    避免污染原始数据
  • 通过重写集合类型的方法并手动指定其中this指向为原始对象的方式,能够解决代理对象无法访问集合类型对应的属性和方法的问题。但是会导致原始数据污染的问题
  • 只希望代理对象才具备依赖收集和依赖更新的能力,然后通过原始数据进行的操作不具有响应式的能力
// 原数数据 originalData1
const originalData1 = new Map({});
// 代理对象 proxyData1
const proxyData1 = reactive(originalData1);
// 另一个代理对象 proxyData2
const proxyData2 = reactive(new Map({}));
// 将 proxyData2 做为 proxyData1 一个键值
// 【注意】此时的 set() 经过重写,其内部 this 已经指向 原始对象(originalData1),等价于 原始对象 originalData1 上存储了一个 响应式对象 proxyData2
proxyData1.set("proxyData2", proxyData2);
// 若不做额外处理,如下基于 原始数据的操作 就会触发 track 和 trigger
originalData1.get("proxyData2").set("name", "zs");

在源码中可以通过value=toRaw(value)获取当前设置值对应的原始数据,可以避免响应式数据对原始数据的污染。
处理forEach回调参数
Map.prototype.forEach(callbackFn, [, thisArg])其中的callback回调函数会接受三个参数, 当前的值value, 当前的键key,正在被遍历的Map对象
遍历操作等价于读取操作,在处理普通对象的get()捕获器中有一个处理,如果当前访问的属性值是对象类型,那么就会向外返回其对应的代理对象。

function createForEach(isReadonly: boolean, isShallow: boolean) {
  return function forEach(
    this: IterableCollections,
    callback: Function,
    thisArg?: unknown,
  ) {
    const observed = this as any
    const target = observed[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    !isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
    return target.forEach((value: unknown, key: unknown) => {
      // 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.
      // 将map类型的键和值进行响应式处理,以及进行track操作
      return callback.call(thisArg, wrap(value), wrap(key), observed)
    })
  }
}

处理迭代器
Map和Set都实现了可迭代协议,因此它们还可以通过for…of的方式进行遍历。

  const iteratorMethods = [
    'keys',
    'values',
    'entries',
    Symbol.iterator,
  ] as const

  iteratorMethods.forEach(method => {
    mutableInstrumentations[method] = createIterableMethod(method, false, false)
    readonlyInstrumentations[method] = createIterableMethod(method, true, false)
    shallowInstrumentations[method] = createIterableMethod(method, false, true)
    shallowReadonlyInstrumentations[method] = createIterableMethod(
      method,
      true,
      true,
    )
  })
  function createIterableMethod(
  method: string | symbol,
  isReadonly: boolean,
  isShallow: boolean,
) {
  return function (
    this: IterableCollections,
    ...args: unknown[]
  ): Iterable & Iterator {
    const target = (this as any)[ReactiveFlags.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,
        TrackOpTypes.ITERATE,
        isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY,
      )
    // return a wrapped iterator which returns observed versions of the
    // values emitted from the real iterator
    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
      },
    }
  }
}
const target = {
    name: 'name',
    age: 10
}
function effect1(fn) {
    console.log(target.name)
}
let activeEffect = null
effect1()
activeEffect = effect1
effect2()
activeEffect = effect2
track(target, 'get', 'name')
track(target, 'get', 'age')
activeEffect = null
{
    [target]: {
        name: [effect1, effect2],
        age: [effect2]
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值