前置
本文主要是对vue3
中响应式reactive
源码的解读,解读之前需要先一些前置知识
1、Proxy
在vue3的响应式中,用proxy
取代了vue2的Object.defineProperty
主要原因是
Object.defineProperty
劫持对象需要递归对对象的每个属性,性能不佳;proxy
可以劫持整个对象,只要对象的属性变了,都能劫持到。- 数组不采用
Object.defineProperty
来进行劫持,需要对数组单独进行处理;proxy
可以直接监听数组的变化。 Object.defineProperty
新增属性和删除属性时无法监控变化。需要通过$set
、$delete
实现
用法:
ts
// target 代理对象
// mutableHandlers 代理对象的操作逻辑
new Proxy(target, mutableHandlers)
2、Reflect
Reflect
是JavaScript中的一个内置对象,它提供了一组静态方法,用于操作对象
Reflect
主要用来配合proxy
使用,用来映射代理对象
示例如下:
ts
let person = {
age: 18, // 如果用户修改了,无法监控到
get num(){ // 属性访问器
return this.age;
}
}
const people1 = new Proxy(person,{
get(target, key, receiver){
console.log('key:',key);
return target[key] // this=person
}
})
console.log(people1.num);
// key:num
// 18
const people2 = new Proxy(person,{
get(target, key, receiver){
console.log('key:',key);
return Reflect.get(target, key, receiver) //this=people2
}
})
console.log(people2.num);
// key:num
// key:age
// 18
推导过程
1.在调用people1.num
的时候,触发了代理对象 的get
方法,返回的target[key]
。这里的target
是原对象 ,也就是实例的person.num
,直接打印出结果。
2.在调用people2.num
的时候,返回的Reflect.get(target, key, receiver)
。这里的receiver
指向的是people2
,也就是实例代理对象 people2.num
。所以在num
访问器里面访问this.age
又可以触发代理对象 的get
方法。
3.使用Reflect
保证this指向永远指向代理对象 ,在对代理对象获取和设置值的时候都能走Proxy
的操作逻辑。
简化代码
首先把reactive
函数进行简化,为了解大概的内容,方便一下步的解读
ts
function reactive(target: object) {
// 要求传入的是对象,不是对象直接返回
if (!isObject(target)) {
return target
}
const proxy = new Proxy(target, baseHandlers)
return proxy;
}
const baseHandlers: ProxyHandler<object> = {
// 原始对象 属性 代理对象
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
return res;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
return result;
}
}
结合上面的方法来创建一个简单的reactive
ts
const people = reactive({age: 18});
people.age; // 触发baseHandlers的get方法
people.age = 20; // 触发baseHandlers的set方法
源码解读
前置
源码提供的变量
ts
// 标识
const enum ReactiveFlags {
SKIP = '__v_skip', // 不可代理的对象
IS_REACTIVE = '__v_isReactive', // 对象是响应式对象
IS_READONLY = '__v_isReadonly', // 对象是只读对象
IS_SHALLOW = '__v_isShallow', // 对象是浅代理对象
RAW = '__v_raw' // 取原始对象
}
// 原对象与代理对象的映射表
const reactiveMap = new WeakMap<Target, any>()
const shallowReactiveMap = new WeakMap<Target, any>()
const readonlyMap = new WeakMap<Target, any>()
const shallowReadonlyMap = new WeakMap<Target, any>()
映射表可以把原对象和代理对象关联起来,可以通过原对象找到代理对象,也可以通过代理对象找到原对象
ts
import { mutableHandlers } from './baseHandlers'
import { mutableCollectionHandlers } from './collectionHandlers'
// reactive方法
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target, // 原对象
false, // 该对象不是只读的
mutableHandlers, // 代理对象的操作逻辑
mutableCollectionHandlers, // 代理集合数据的操作逻辑
reactiveMap // 原对象与代理对象的映射表
)
}
// 是否只读
export function isReadonly(value: unknown): boolean {
// ReactiveFlags.IS_READONLY = __v_isReadonly 对象是只读对象标志
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
// 创建一个reactive
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {}
解读过程
1.reactive接收一个对象 ,通过isReadonly
方法来判断这个对象是否只读,只读就直接返回本身。
如果接收的对象是普通对象,就直接判断对象是否含有
__v_isReadonly
。如果接收的对象是proxy
,则会调用传入proxy
的第二个参数里面的get
方法
2.接下来调用createReactiveObject
方法,这个方法是个公共方法,reactive
,shallowReactive
,readonly
,shallowReadonly
都会调用这个方法,区别就在于传入的参数不同。
核心部分
源码位置:
- reactive代码
/packages/reactivity/src/reactive.ts
- proxy处理逻辑代码
packages/reactivity/src/baseHandlers.ts
这里将源码功能拆分出来方便理解,想看源码的可以从上面的路径找到源码参照阅读。
createReactiveObject
ts
// isReadonly = false,
// baseHandlers = mutableHandlers,
// collectionHandlers = mutableCollectionHandlers,
// proxyMap = reactiveMap
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
// target不是对象,则直接返回,如果是在开发环境就报警。
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 1、对象已经是代理对象,返回本身
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 2、对象已经被代理过了,在映射表中找出代理对象并返回
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 3、只有在白名单中的类型能被观测
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
baseHandlers
ts
export const baseHandlers: ProxyHandler<object> = {
get(target, key, receiver){
// ...
const res = Reflect.get(target, key, receiver)
return res
},
set(target, key, receiver){
// ...
const result = Reflect.set(target, key, value, receiver)
return result
},
}
解读过程:
1、对象已经是代理对象 ,返回本身。因为代理对象已经添加了get
方法,里面的处理逻辑如下
kotlin
function get(target, key, receiver){
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly // reactive里面isReadonly变量的值为false
} // ...省略无关代码
else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly // reactive里面isReadonly变量的值为false
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap // reactiveMap就是上面的proxyMap.set(target, proxy)里面的proxyMap,是对象和代理对象的映射表
).get(target) // 所以这里拿到的就是代理对象,receiver指代的也是代理对象,结果是true
) {
return target
}
// ...省略无关代码
}
调用target[ReactiveFlags.RAW]
,在get
里面key === ReactiveFlags.RAW
。
调用target[ReactiveFlags.IS_REACTIVE]
,在get
里面key === ReactiveFlags.IS_REACTIVE
,返回!isReadonly
也就是true
,结果这里return target
返回代理对象本身
示例如下:
ts
const obj = { age: 18 };
const proxy1 = reactive(obj);
const proxy2 = reactive(proxy1);
console.log(proxy1 === proxy2); // true
2、对象已经被代理过了,在映射表中找出代理对象并返回
ts
// 对象已经被代理,所以在映射表proxyMap里存在target->new Proxy的映射关系
// 这里就直接取出代理对象返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
示例如下:
ts
const obj = { age: 18 };
const proxy1 = reactive(obj);
const proxy2 = reactive(obj);
console.log(proxy1 === proxy2); // true
3、只有在白名单中的类型能被代理
ts
function createReactiveObject(){
// ...省略无关代码
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
}
// target
function getTargetType(value: Target) {
// 对象中有跳过字段、或者不能被扩展
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value)) // toRawType看原始类型,原理是[object xxx].slice(8, -1)得出类型
}
// 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
}
}
结论就是对象没有跳过SKIP
标识、可以被扩展,并且类型是Object、Array还有Map、Set、WeakMap、WeakSet可以代理
拓展
- 什么时候会有跳过
SKIP
标识?
markRaw
方法可以把对象标记为不可代理
ts
const proxy0 = markRaw({ a: 1}) // proxy0 = {a: 1,__v_skip: true}
markRaw原理
ts
const enum ReactiveFlags {
SKIP = '__v_skip', // 不可代理的对象
// ...省略无关代码
}
function markRaw(obj: object){
Object.defineProperty(obj, ReactiveFlags.SKIP, {
configurable: true,
enumerable: false,
value: true
})
}
toRaw
方法
ts
const obj = { age: 18 };
const proxy1 = reactive(obj); // target -> proxy
const obj2 = toRaw(proxy); // proxy -> target
console.log(obj === obj2); // true
原理
ts
function toRaw<T>(observed: T): T {
// 递归将对象转换成原始类型
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
// (observed as Target)[ReactiveFlags.RAW] 触发响应式get方法
function get(target: Target, key: string | symbol, receiver: object) {
// ...
else if (
key === ReactiveFlags.RAW && // 实现toRaw方法
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target // 返回映射表reactiveMap里目标对象的原对象
}
// ...
}
baseHandlers
解读完reactive
部分的源码,发现baseHandlers
里面的部分源码还未涉及,接下来继续解读baseHandlers
关于get
和set
部分的源码
get
ts
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly // true 是响应式reactive
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly // false 不是readonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow // 浅代理
} else if (
key === ReactiveFlags.RAW && // 实现toRaw方法
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target)
// 1、如果是数组,对数组的方法进行特殊处理
if (!isReadonly) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
// 访问hasOwnProperty,也会触发依赖收集
return hasOwnProperty
}
}
// 取值
const res = Reflect.get(target, key, receiver)
// 是内置symbol或者是访问的是__proto__属性直接返回,不用做依赖收集
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 如果是仅读的不需要做依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是浅代理的直接返回取值
if (shallow) {
return res
}
// 2、取值是ref,判断是否脱ref
if (isRef(res)) {
// 如果通过数组索引访问的是ref类型则不进行拆包
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
// 3、如果取的结果是对象,会进行递归代理
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
}
}
解读过程:
1、如果是数组,对数组的方法进行特殊处理
为什么要特殊处理?
响应式对象是数组的时候,比如调用
includes
方法,当数组里面的值变化了,这里也要能触发更新重新判断;当includes
传入的是原数组某个对象类型的值,与响应式对象取出的响应式的值就一直不相等。所以这里需要重写数组的方法。
ts
const targetIsArray = isArray(target)
if (!isReadonly) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
// 访问hasOwnProperty,也会触发依赖收集
return hasOwnProperty
}
}
const arrayInstrumentations = () => {
const instrumentations: Record<string, Function> = {}
// 数组对 'includes', 'indexOf', 'lastIndexOf' 方法进行处理
// 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)
// 如果参数找不到,会将参数转成原数据再找一次,也就是传入响应式的值也可以判断,这里会做处理
// includes(原对象)、includes(代理对象)这两种都可以
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
}
}
})
// 数组对 'push', 'pop', 'shift', 'unshift', 'splice' 方法进行处理
// 这些方法会导致数组长度发生变化,会导致无限更新问题
// 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() // 调用此方法停止依赖收集
const res = (toRaw(this) as any)[key].apply(this, args)
resetTracking() // 恢复依赖收集
return res
}
})
return instrumentations
}
2、取值是ref,判断是否脱ref
ts
if (isRef(res)) {
// 如果通过数组索引访问的是ref类型则不进行拆包
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
实例如下:
ts
const obj = reactive({ age: ref(18) });
const age = obj.age // 18 age取值的时候,会判断是否是ref,是ref自动调用.value,也就是脱ref
const proxyArr = reactive([ref(1), 2, 3])
console.log(proxyArr[0]); // key是数组索引,这种情况不支持脱ref
3、如果取的结果是对象,会进行递归代理
这里其实是proxy
做的优化---懒代理。代理对象的对象属性,没有被取值,是不会转成代理的
set
ts
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key] // 获取老值
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
if (!shallow) {
// 如果深度代理,在赋值的时候先转换成非proxy
if (!isShallow(value) && !isReadonly(value)) {
// 把老值和新值都转换成原始值
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// 1、不是数组,老的是ref,新的不是ref,则会给老的ref赋值
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
// 更新原来ref的值
oldValue.value = value
return true
}
} else {
// 浅层代理无所谓咋设置
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 2、设置值的时候,要判断是修改还是新增
// 查看是否有过这个key
const hadKey =
isArray(target) && isIntegerKey(key) // 访问的是数组的索引
? Number(key) < target.length // 根据索引长度和原数组长度来判断
: hasOwn(target, key) // 对象有这个属性
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
// 减少原型链的触发
if (!hadKey) {
// 添加
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果前后置有变化触发修改逻辑
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
解读过程:
1、不是数组,老的是ref,新的不是ref,则会给老的ref赋值
ts
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
// 更新原来ref的值
oldValue.value = value
return true
}
示例如下:
ts
const obj = reactive({ age: ref(18) });
obj.age = 20; // 相当于obj.age.value = 20
obj.age = ref(20); // 相当于重新赋一个ref
2、设置值的时候,要判断是修改还是新增
如果访问的是数组的索引,索引的长度在原数组长度之内的就是修改
如果访问的是对象,就判断对象里面有没有这个值,有就是修改
ts
const hadKey =
isArray(target) && isIntegerKey(key) // 访问的是数组的索引
? Number(key) < target.length // 根据索引长度和原数组长度来判断
: hasOwn(target, key) // 对象有这个属性
const result = Reflect.set(target, key, value, receiver)
// 不要触发对象的原型链上
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) { // 如果是调用原型链,这里的receiver和target不相等,就会屏蔽
if (!hadKey) {
// 添加逻辑
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果前后有变化触发修改逻辑
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
其他代理处理
ts
// 使用delete删除某个属性触发
function 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
}
// 用in判断某个属性是否在对象中
function has(target: object, key: string | symbol): boolean {
const result = Reflect.has(target, key)
// 内置属性不做依赖收集
if (!isSymbol(key) || !builtInSymbols.has(key)) {
// 对key进行依赖收集,因为删除或增加这个key,也要触发更新
track(target, TrackOpTypes.HAS, key)
}
return result
}
// 使用for in 数组收集长度,对象收集,也要依赖收集
function ownKeys(target: object): (string | symbol)[] {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}