响应式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.defineProperty
和Proxy
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主要是用来创建一个对象的代理,从而实现基本操作的拦截和自定义(比如属性查找, 赋值, 枚举, 函数调用等),本质上是通过拦截对象内部方法的执行实现代理,而对象本身根据规范定义的不同又会分为常规对象和异质对象。
- get()属性读取操作捕获的捕获器。
- set()属性设置操作的捕获器。
- deleteProperty()是delete操作符的捕捉器。
- ownkeys()是object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕获器。
- 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]
}
}