第二节:vue3源码响应系统:reactive创建响应式代理
1. reactive 基本使用
reactive
API接收一个对象作为参数, 返回一个具有响应性的对象
使用方式
import { reactive } from 'vue'
const user = reactive({ age: 10 })
响应式对象是 JavaScript 代理,其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。
并且reactive 响应式转换是"深层的": 会影响对象内部所有嵌套的深层属性.
实例:
<div id="app">
<div>{{user.age}}</div>
<button @click="change">age++</button>
</div>
<script>
const { createApp, reactive } = Vue
const App = {
setup() {
// 声明reactive
const user = reactive({ age: 10 })
// 修改数据
const changeUser = () => {
user.age++
}
return { user, changeUser }
}
}
createApp(App).mount('#app')
</script>
注意: 响应式是基于ES2015
的Proxy
实现的, 返回的代理对象不等于原始对象, 建议仅使用代理对象而避免依赖原始对象, 因为原始对象不具有响应性.
接下里我们查看一下源码中 reactive API 的实现
2. reactive 源码分析
2.1 reactive API 函数
Vue3中响应数据核心是 reactive ,我们先来看一下 reactive
API 函数的定义
源码:
export function reactive(target: object) {
// 1. 如果target 目标对象是一个只读代理, 则直接返回target
if (isReadonly(target)) {
return target
}
// 2. 调用createReactiveObject 方法创建响应式对象
return createReactiveObject(
target, // 代理源对象
false, // 是否只读
mutableHandlers, // 普通对象的代理配置对象
mutableCollectionHandlers, // Set,Map,WeakSet,WeakMap的代理配置对象
reactiveMap // 缓存源对象与代理对象的映射Map
)
}
在 reactive
函数存在两条分支:
- 如果参数
target
为只读响应代理,即具有__v_isReadonly
属性且值为true
,则直接返回参数target
- 如果参数
target
不是只读响应代理,则调用createReactiveObject
函数创建proxy
代理对象,并返回
通过reactiveAPI
的实现可以看出几件事情:
-
reactive API
函数只接受一个参数 -
reactiveAPI
参数如果通过isReadonly
判断为true
时, 表示为只读响应, 则返回参数 -
reactiveAPI
实现创建代理对象, 是通过createReactiveObject
函数实现的, 把实现逻辑抽为单独函数, 时因为readonlyAPI
也是创建响应代理, 区别只是浅层响应
2.2 createReactiveObject 创建响应代理
createReractiveObejct 函数是用来创建响应式对象的方法
源码:
/**
* targer 源对象
* isReadonly 是否只读
* baseHandlers 普通对象(Object,Array)代理的handlers
* collectionHandlers 主要针对(Set、Map、WeakSet、WeakMap)的 handlers
* proxyMap WeakMap 缓存源对象与代理对象的映射Map
*/
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 1. 如果target 不是对象, 则直接返回target
if (!isObject(target)) {
// targer 不是object, 开发环境控制台输出警告
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
// 返回参数
return target
}
// 2. 如果target 已经是 proxy 代理对象对象, 则直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 3. 如果缓存 proxyMap 中target 已有对应的代理对象, 则直接返回代理对象
// 避免重复创建代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 4. 如果 target 是不可观察类型(无效类型INVALID), 则直接返回 targt
// 例如正则对象就视为无效类型(不可观察类型)
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 5. 创建响应式代理对象
// TargetType.COLLECTION 集合类型, 值为2
// Map,Set,WeakMap,WeakSet集合类型,使用collectionHandlers 代理配置对象
// Object,Array 普通对象, 使用 baseHandlers 代理配置对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 6. 在 proxyMap 中新增一对键值,保存 target 与 proxy 映射
proxyMap.set(target, proxy)
// 返回 proxy
return proxy
}
createReactiveObject
方法的核心功能就是创建参数target
对象的响应式对象,并返回代理对象.
同时在通过Proxy
创建代理对象之前通过各种边缘判断, 处理参数不符合代理的场景,以及提升性能优化
createReactiveObject
方法中具体边缘判断:
- 通过
isObject
函数判断参数target
不是对象的情况,也就是能通过判断的参数typeof
检测类型都是object
, 注意null
除外 - 接着判断参数是否已经是
reactive
创建的代理对象, 如果是,则返回参数,这里通过判读表示参数并不是具有相应性的代理对象 - 其次通过
proxyMap
缓存中判断当前源对象是否已经创建过代理对象, 如果已经创建过, 则直接返回代理对象, 提升性能,避免通过一个源对象,多次调用reactive
函数,多次创建返回不同的代理对象 - 最后判断参数是否可以创建代理对象, 也就是说虽然走到这里的参数
target
类型都是object
, 但object
有可详细区分为普通对象Object
,数组Array
,正则RegExp
等各详细的类型. 最后判断参数如果不能创建代理对象, 则直接返回参数, 例如正则
这里只是简单罗列了一下边缘判断, 更详细的分析, 我们放在下一章节
2.3 总结
从上面的源码中了解:
1.reactive
API函数只能对对象类型进行代理转换, 如果传入原始类型数据或函数,则不会有任何操作, 如果在开发环境,控制台还会输出警告
- 在创建代理对象之前,通过一系列优化校验,避免重复操作, 优化框架性能
-
reactive
会根据对象的不同类型(更详细的类型),在创建代理对象时,使用不同的handler
配置对象 - 创建完代理对象使用
proxyMap
缓存 目标对象target
和 代理对象proxy
之间对应关系
其实去除用于优化校验判断外,reactive
方法功能很简单: 就是创建一个proxy
对象并返回
reactive
API 创建proxy对象流程图:
3. 深入分析各种校验
3.1 判断 target 是否为只读响应数据
在上面reactive
源码中,我们会通过isReadonly
方法判断目标target
是否为只读响应数据, 如果是则返回target
本身,接下来我们分析一下这些工具函数的实现, 来理解更细致的判断
3.1.1 isReadonly 函数
那么接下来我们看看 isReadonly
方法
源码:
// 检查传递的值是否为只读代理对象
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
源码中!!
是为了保证函数返回始终是确定的boolean
值
isReadonly
方法内会判读参数 value
是否为只读响应
- 首先判断参数value值是否存在, 不存在, 直接返回false
- 如果
value
存在, 则判断value的[ReactiveFlags.IS_READONLY]
属性
ReactiveFlags
则是一个枚举
export const enum ReactiveFlags {
// 用于标识一个对象是否不可被转为代理对象
SKIP = '__v_skip',
// 用于标识一个对象是否是响应式对象
IS_REACTIVE = '__v_isReactive',
// 用于标识一个对象是否是只读的代理对象
IS_READONLY = '__v_isReadonly',
// 用于标识一个对象是否是浅层代理对象
IS_SHALLOW = '__v_isShallow',
// 用于保存原始对象的key
RAW = '__v_raw'
}
通过源码中 ReactiveFlags
枚举定义可知
判断value
的[ReactiveFlags.IS_READONLY]
属性,其实就是判断是否具有__v_isReadonly
属性, 以及属性值是否为 true
3.1.2 通过只读computed 测试
未设置set
的computed
为只读响应数据, 通过computed
计算属性测试
示例:
const { createApp, nextTick, reactive, readonly } = Vue
const App = {
setup() {
// 1. computed 计算属性只读
const student = computed(() => ({ age: 10 }))
console.log(student)
/*
{
dep: undefined
effect: ReactiveEffect2 {active: true, deps: Array(0), parent: undefined, fn: ƒ, scheduler: ƒ, …}
__v_isReadonly: true
__v_isRef: true
_cacheable: true
_dirty: true
_setter: () => {…}
value: {
age: 10
}
}
*/
// 2. 将computed 只读ref 作为reactive()的参数
const user = reactive(student)
// 3.判断reactive()返回的是否是原代理对象
console.log(student === user) // true
return { }
}
}
createApp(App).mount('#app')
通过控制台输出可知:
- 未设置
set
的computed
返回的student
是只读响应式对象, 对象本身就具有__v_isReadonly
属性, 且属性值为true
- 因此当通过
reactive
函数创建响应对象时, 通过isReadonly()
方法判断,因为__v_isReadonly
属性为true
, 所以直接返回参数对象
3.2 判断 target 是否为 对象
此时程序已经进入到createReactiveObject
函数中, 在函数中首先判断参数target
的类型是否为对象
源码:
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
可以看到在源码中会通过isObject
方法判断目标 taget
是否为对象
如果目标target
是非对象,则直接返回参数 target
, 开发环境下则警告
那么接下来我们看一下isObject
函数是如何判断参数是否为对象的
源码:
export const isObject = (val: unknown): val is Record<any, any> =>
val !== null && typeof val === 'object'
isObject
函数通过判断参数不为 null
,并且 typeof
检测类型为object
,来识别object
类型
我们知道在JavaScript
中,typeof
检测数据类型, 诸如:{}(普通对象)
[](数组)
, /./(正则)
, Set
,WeakSet
,Map
,WeakMap
, null
等都是object
简而来说isObject
工具函数, 是通过typeof
检测参数是否为object
, 并剔除null
至此我们可以明白, 如果reactive
参数是基本数据类型, 以及function
时, 会直接返回参数
验证reactive
参数是非 object
的情况
示例代码:
// string 类型
const msg = reactive('aaa')
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: 'aaa'
msg 'aaa'
*/
// number
const msg = reactive(100)
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: 100
msg 100
*/
// boolean
const msg = reactive(true)
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: true
msg true
*/
// null
const msg = reactive(null)
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: null
msg null
*/
// undefined
const msg = reactive(undefined)
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: undefined
msg undefined
*/
// symbol
const msg = reactive(Symbol('a'))
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: Symbol(a)
msg Symbol(a)
*/
// bigInt
const msg = reactive(100n)
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: 100
msg 100n
*/
/*
备注: 警告输出的值会调用String()转为字符串,String()方法在转bigInt类型时没有n
String(100n) ==> '100'
*/
// function
const msg = reactive(() => {})
console.log('msg', msg)
/**
警告:reactive.ts:190 value cannot be made reactive: () => {}
msg () => {}
*/
我们会发现除了基本数据类型, 包括引用类型的 函数也会警告
总结: 只有值不为null
, 并typeof
检测类型为object
才会通过判断,否则原样输出
其实这里我们会发现一个问题: 将正则作为目标target
传入也是原样返回
例如:
const msg = reactive(/aa/)
console.log('msg', msg) // /aa/
示例中正则typeof
检测时类型也是object
, 为什么返回的却是原目标本身呢?
其实大家也不用纠结, 出现当前问题的原因是在于后续的判断.
因为当前判断中, 正则的typeof
类型检测是object
, 通过了当前判断, 但之后还有他的校验, 接下来让我们继续查看
3.3 判断 target 是否为 proxy对象
源码:
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
源码分析:
- 在这个判断中,
isReadonly
, 是调用createReactiveObject
方法传入的第二个参数,reactive()
方法中调用固定传入false
, 因此!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
结果固定为true
- 因此这里判断条件是
true
,还是false
, 取决于target[ReactiveFlags.RAW]
通过之前了解ReactiveFlags
的枚举, ReactiveFlags.RAW
值为"__v_raw"
字符串
因此这里判断参数target
是否具有__v_raw
属性, 存在,则返回原参数target
示例:
// 声明reactive响应式数据
const student = reactive({ age: 10 })
// 将student proxy对象作为参数传入reactive()
const user = reactive(student)
console.log(user === student) // true
通过分析:
源码在判断参数student
是否为 proxy
对象时, 在获取"__v_raw"
属性
在获取属性时,就会进入student
代理对象的get
拦截操作
源码:
function createGetter(isReadonly = false, shallow = false) {
// isReadonly: false, shallow:false
return function get(target: Target, key: string | symbol, receiver: object) {
// target: 目标对象, 示例中的:{ age: 10 },
// key : "__v_raw"
// receiver: 代理对象, 实例中的student
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (
// reactiveMap: target对象与代理对象的映射缓存
// 原对象target的缓存代理对象与recevier相同
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
// 返回原对对象
return target
}
// ...
return res
}
}
通过拦截函数分析得知,get拦截函数最后返回原对象
因此
// target[ReactiveFlags.RAW]的值 为目标对象,即示例中`{ age: 10 }`
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
此时判断条件为true
, 返回原目标对象target
, 即示例中的student
代理对象, 因此user
和target
为同一个代理对象
3.4 判断 target 是否存在代理对象
当前判断用于处理同一个目标对象多次调用reactive()
的场景, 规避重复创建代理对象,提升性能
源码:
// 通过目标对象target 获取存在缓存的代理对象
const existingProxy = proxyMap.get(target)
// 存在缓存的代理对象,则返回已创建的代理对象
if (existingProxy) {
return existingProxy
}
通过源码分析, 判断当前目标对象target是否存在缓存的代理对象proxy
, 如果存在直接返回代理对象, 避免同一个目标对象重复创建代理对象
proxyMap
用于记录源对象与代理对象的映射关系
示例:
// 目标对象
const target = { age: 10 }
// 目标对象第一次创建代理对象
const student = reactive(target)
// 目标对象第二次创建代理对象
const user = reactive(target)
// 判断为:true, user和student 时同一个代理对象
console.log(user === student)
通过示例分析:
- 目标对象
target
多次调用reactive()
方法创建代理对象 - 目标
target
第一个调用reactive()
创建代理对象时,会缓存目标对象target
与代理对象的映射关系 - 当
target
第二个调用reactive()
创建代理对象, 因为target
即不是只读代理, 并且是一个普通对象,不存在__v_raw
属性,因此会一路畅通走到当前源码位置 - 通过
proxyMap
获取当前目标对象target
是否存在缓存代理, 如果存在,则直接返回已创建的代理对象
这里的好处是避免同一个目标对象,多次创建代理对象
3.5 判断 target 目标对象的类型
这节判断主要用来更细致的判断参数target
,reactive
只能创建特定类型的代理.
不知道大家是否还记得在3.2 节,在通过typeof
检测目标对象类型是否为object
时, 正则也会通过检测.
但是正则不能通过Proxy
创建代理对象, 原因就在于本次判断检测,
那么我们先看看源码判断
源码:
// 获取target目标对象类型
const targetType = getTargetType(target)
// 如果targetType === TargetType.INVALID则直接返回目标对象
if (targetType === TargetType.INVALID) {
return target
}
在源码中vue主要做了两件事:
- 获取目标对象
target
的类型targetType
- 判断
targetType
类型,如果是指定的类型(INVALID表示无效类型)
,则直接返回目标对象
3.5.1 getTargetType 与 TargetType
首先,我们先看看getTargetType
方法, 看此方法是如何定义目标对象类型, 以及TargetType
类型的定义
源码:
// 目标对象类型 targetType
const enum TargetType {
INVALID = 0, // 无效类型
COMMON = 1, // 普通对象类型(Object, Array)
COLLECTION = 2 // 集合类型(Map,Set,WeakMap,WeakSet)
}
// 通过target 数据类型 获取 targetType 类型
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
}
}
// 获取 target 类型
function getTargetType(value: Target) {
// value[ReactiveFlags.SKIP] 判断目标对象是否具有'__v_skip'属性
// !Object.isExtensible(value) 判断目标对象是否可扩展
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
源码分析:
-
TargetType
是一个枚举类型,标识不可创建代理的无效类型(0)
,普通对象类型(1)
,Set/Map 等集合类型(2)
三种 - 通过如果目标对象
target
的__v_skip
属性为true
,或者target
为不可扩展对象, 则返回TargetType.INVALID(0)
无效类型 - 如果目标对象不具有
__v_skip
或此属性值为false
, 并且 目标对象为可扩展对象,则调用targetTypeMap(toRawType(value))
获取目标对象类型
这里我们要知道targetTypeMap(toRawType(value))
返回的类型
则需要先了解toRawType(value)
方法返回的值
3.5.2 toRawType
源码:
// 获取元素Object原型上的toString方法
export const objectToString = Object.prototype.toString
// 获取参数类型字符串(例如: [] => [Object Array])
export const toTypeString = (value: unknown): string =>
objectToString.call(value)
// 截取类型字符串
export const toRawType = (value: unknown): string => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1)
}
通过源码分析:
- 这里通过原生
toString
方法,获取对象类型字符串,例如:[Object Array]
- 在
toRawType
方法中截取参数类型获取关键字符串,例如:Array
,Object
,String
等 - 返回类型字符串,
在通过toRawType
获取类型后, 在调用targetTypeMap()
方法,获取并返回目标对象类型
也就是说, 源码将typeof
检测的类型为object
的数据,通过Object.toString
获取更详细的类型区分, 并以此将这些类型的数据归总为三类:
-
INVALID
: 值为0, 表示无效类型 -
COMMON
: 值为1, 表示普通对象类型(Object, Array)
-
COLLECTION
: 值为2, 表示集合类型(Map,Set,WeakMap,WeakSet)
此三种类型中, 只有后两者允许创建代理对象, 无效类型直接返回参数
3.5.3 分析无效类型
通过实例分析无效类型
示例:
const reg = reactive(/aa/)
console.log('reg', reg) // /aa/
const freezeObject = reactive(Object.freeze({ age: 18 }))
console.log('freezeObject', freezeObject) // { age: 18 }
示例中两个参数返回的都是参数本身:
- 参数
/aa/
, 获取类型为RegExp
, 但是在targetTypeMap
方法中被判断为TargetType.INVALID
无效类型 - 参数
Object.freeze({ age: 18 })
冻结对象,在!Object.isExtensible(value)
判断为不可扩展对象, 直接返回TargetType.INVALID
无效类型 - 无效类型不会创建代理,返回目标对象本身
扩展: Object.isExtensible()
此方法用于判断参数对象是否为可扩展对象, 返回布尔值, true:可扩展, false:不可扩展
默认创建的对象都是可扩展的,可以为其添加属性, 以及修改属性
通过Object.preventExtension
, Object.seal
, Object.freeze
方法标记的对象为不可扩展对象(non-extensible)
// 新对象默认是可扩展的。
var empty = {};
Object.isExtensible(empty); // === true
// ...可以变的不可扩展。
// preventExtensions 将参数标记为不可扩展
// 此不可扩展只是对象本身不可新增属性, 但是原型对象上依然可以扩展属性
Object.preventExtensions(empty);
Object.isExtensible(empty); // === false
// 密封对象是不可扩展的。
var sealed = Object.seal({});
Object.isExtensible(sealed); // === false
// 冻结对象也是不可扩展。
var frozen = Object.freeze({});
Object.isExtensible(frozen); // === false
4. 创建代理对象
4.1 创建代理对象
reactive
核心功能就是创建响应性对象(代理对象), 因此reactive
除了边缘判断, 最核心的代码就是如下几行:
源码:
// 创建代理对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 新增目标对象与代理对象的映射缓存, 避免重复创建
proxyMap.set(target, proxy)
// 返回代理对象
return proxy
通过源码了解:
- 通过目标对象
target
创建代理对象proxy
- 判断
targetType(目标对象类型)
使用不同的代理配置
可以看到创建代理对象的关键点在与代理对象的handler
, 而这个handler
就是collectionHandlers
和baseHandlers
两个对象
通过targetType
判断使用哪个 handler
, 值为TargetType.COLLECTION
,即枚举值2的使用, 使用collectionHandlers
, 否则使用baseHandlers
也就是说:
-
TargetType
类型为COMMON(1)
时, 表示普通对象, 使用baseHandlers
作为代理的配置对象 -
TargetType
类型为COLLECTION(2)
时, 表示普通对象, 使用collectionHandlers
作为代理的配置对象
collectionHandlers
和baseHandlers
对象在reactive()
方法中调用createReactiveObject()
方法时传入的参数
源码:
export function reactive(target: object) {
// ...
return createReactiveObject(
target,
false,
mutableHandlers, // baseHandlers 普通对象代理配置对象
mutableCollectionHandlers, // collectionHandlers 集合代理配置对象
reactiveMap // proxyMap 目标对象与代理对象映射缓存map
)
}
4.2 baseHandler 普通对象和数组 代理配置对象
如果目标对象target
类型时TargetType.COMMON(值为1)
普通对象类型, 则使用baseHandlers
配置对象
而baseHandlers
对象是在reactive()
方法中调用createReactiveObject()
方法中传入的mutableHandlers
配置对象
因此接下来我们分析mutableHandler
4.2.1 baseHandler 对象
源码:
// reactive 代理对象的配置对象
export const mutableHandlers: ProxyHandler<object> = {
get, // get拦截
set, // set拦截
deleteProperty, // delete拦截
has, // has拦截
ownKeys // ownKeys拦截
}
源码中可以看出 baseHandler
实现了get
,set
,deleteProperty
,has
,ownKeys
五个方法拦截器,
简单介绍一下几个拦截器的作用:
-
get
: 拦截对象的getter
操作, 例如:obj.name
-
set
: 拦截对象的setter
操作,例如:obj.name="xx"
-
deleteProperty
: 拦截对象的delete
操作,例如:delete obj.name
-
has
: 拦截in操作,例如'name' in obj
-
ownKeys
: 拦截Object.getOwnPropertyNames
,Object.getOwnPropertySymbols
,Object.keys
等操作
更具体可以查看MDN的介绍,接下来看看拦截器的具体实现
4.2.2 get 拦截器
源码:
// get 拦截方法
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
// 闭包中两个参数,isReadonly: 标记是否只读, shallow: 标记是否浅层响应式
// 闭包返回get 拦截器
return function get(target: Target, key: string | symbol, receiver: object) {
// 1. 处理特殊属性, 基本是vue 内部定义的属性,用于判断是否是reactive,readonly,shallow
// 1.1. 如果访问`__v_isReactive` 属性, 返回 isReadonly取反值
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
// 1.2. 如果访问`__v_isReadonly` 属性, 返回isReadonly 值
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
// 1.3. 如果访问`__v_isShallow` 属性, 返回 shallow 的值
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
// 1.4. 如果访问`__v_raw` 属性, 通过条件判断返回 target
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
// 2. 对数组方法 以及 hasOwnProperty 方法处理
// 判断target 是否为数组
const targetIsArray = isArray(target)
// 如果不是只读代理
if (!isReadonly) {
// 如果target是数组, 并方法数组方法, 那么就返回对应方法调用结果
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 如果访问 hasOwnProperty 方法, 则返回hasOwnProperty 函数
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// 3. 获取 target 的 key 属性值
const res = Reflect.get(target, key, receiver)
// 4. Symbol 特殊性属性处理,不需要收集依赖,例如:System.iterator
// 如果是内置的 Symbol, 或者不可追踪的key , 直接返回res
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 5. 调用 track 方法进行依赖收集
// 如果不是只读, 那么进行依赖收集
if (!isReadonly) {
// TrackOpTypes.GET => 'get'
track(target, TrackOpTypes.GET, key)
}
// 6. 浅层响应性, 直接返回res,不进行递归代理
if (shallow) {
return res
}
// 7. 如果res 是 ref数据, 则返回值的解包
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
// 对于数组 并且 key是整数类型, 不进行解包
return targetIsArray && isIntegerKey(key) ? res : res.value
}
// 8. 如果res 是对象, 则递归代理
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
// 返回res
return res
}
}
4.2.3 set 拦截器
源码:
// set 方法
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
// 闭包中shallow 判断是否为浅层代理
// 闭包,返回set 拦截器
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// 获取旧值
let oldValue = (target as any)[key]
// 判断如果旧值是只读的, 并且是ref, 并且新值不是ref, 直接返回false,代表设置失败
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
// 如果不是浅层代理对象
if (!shallow) {
// 如果不是浅层代理,并且不是只读
if (!isShallow(value) && !isReadonly(value)) {
// 获取旧值(响应性)的原始值
oldValue = toRaw(oldValue)
// 获取新值的原始值
value = toRaw(value)
}
// 如果目标对象不是数组, 并且旧值是ref, 同时新值不是ref
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
// 将新值设置在 旧值(ref)的 value属性上, 返回true,表示设置成功
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)
// 判断key 是否小于 数组长度
? Number(key) < target.length
// 如果不是数组, 判断 target 是否具有key 属性
: hasOwn(target, key)
// 通过Reflect.set 设置属性值
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)) {
// 如果没有这个key , 那么就是新增一个属性, 触发add事件
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
// 如果存在这个key, 那么就是修改这个属性, 触发set 事件
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
// 返回结果, 这个结果是一个boolean值,代表是否设置成功
return result
}
}
4.2.4 deleteProperty 拦截器
源码:
// deleteProperty 删除属性方法
function deleteProperty(target: object, key: string | symbol): boolean {
// 判断当前属性 key 是否为 target 对象自己的属性,而非原型链上的属性
const hadKey = hasOwn(target, key)
// 获取旧值
const oldValue = (target as any)[key]
// 调用Reflect.deleteProperty 删除属性
const result = Reflect.deleteProperty(target, key)
// 如果删除成功,并且target 对象自身具有此属性,则触发依赖 delete
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
// 返回删除结果
return result
}
deleteProperty
方法的实现对比get
和set
方法的实现都要简单很多,也没有什么特别的地方,就是通过Reflect.deleteProperty
删除属性,然后通过trigger
触发delete
事件,最后返回删除是否成功的结果;
4.2.5 hash 拦截器
源码:
// has 方法
function has(target: object, key: string | symbol): boolean {
// 通过 Reflect.has 判断当前对象 target 是否具有属性key
const result = Reflect.has(target, key)
// 如果当前对象不是Symbol 或 内置的Symbol, 则触发 has 收集依赖
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}
has
方法的实现也是比较简单的,就是通过Reflect.has
判断当前对象是否有这个 key
,然后通过track
触发has
事件,最后返回是否有这个 key
的结果;
4.2.6 ownKeys 拦截器
源码:
// ownKeys 方法
function ownKeys(target: object): (string | symbol)[] {
// 触发依赖收集
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
// 调用Reflect.ownKeys 获取当前对象的所有key属性
return Reflect.ownKeys(target)
}
ownKeys
方法的实现也是比较简单的,直接触发iterate
事件,然后通过Reflect.ownKeys
获取当前对象的所有 key
,最后返回这些 key
;
注意点在于对数组的特殊处理,如果当前对象是数组的话,那么就会触发length
的iterate
事件,如果不是数组的话,那么就会触发ITERATE_KEY
的iterate
事件;
这一块的区别都是在track
方法中才会有体现,这个就是响应式的核心思路,
4.3 collectionHandlers 配置对象
集合指的是Set
,Map
.WeakSet
,WeakMap
对象,
而collectionHandlers
对象是在reactive()
方法中调用createReactiveObject()
方法中传入的的mutableCollectionHandlers
源码:
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
集合只做了get
拦截, get
方法是调用 createInstrumentationGetter(false, false)
函数返回的函数
4.3.1 get 拦截器
源码:
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
// 闭包参数 isReadonly 是否只读, shallow: 是否浅层
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
// 闭包返回get 拦截器
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
// 1. 处理vue 内部属性,用于判断是否为 reactive, readonly 等数据
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
// 调用 Reflect.get 处理获取
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
通过上面的分析, 我们大概清楚 reactive
作为响应式的入口, 处理目标对象是否可观察以及是否已经被观察的逻辑, 核心是最后使用 Proxy
进行目标对象的代理, 以及Proxy
代理的 handlers
配置对象
5. shallowReactive, readonly, shallowReadonly
5.1 shallowReactive 浅层响应代理
shallowReactive
用于创建浅层响应代理对象
源码:
export function shallowReactive<T extends object>(
target: T
): ShallowReactive<T> {
return createReactiveObject(
target, // target:创建代理的目标对象
false, // false: 表示创建代理对象不是只读
shallowReactiveHandlers, // 普通对象使用的处理浅层代理响应对象的配置对象
shallowCollectionHandlers, // 集合对象使用的处理浅层代理响应对象的配置对象
shallowReactiveMap // 用于缓存目标对象与浅层响应代理对象的映射
)
}
在源码中可以看出shallowReactive
也是调用createReactiveObject
函数实现浅层响应的代理对象
在调用createReactiveObject
函数时,传入不同的参数实现浅层响应代理
5.2 readonly 只读响应代理
readonly
用于创建只读浅层响应代理对象
源码:
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target, // target:创建代理的目标对象
true, // true: 表示创建代理对象是只读响应代理
readonlyHandlers, // 普通对象: 使用的处理只读代理响应对象的配置对象
readonlyCollectionHandlers, // 集合对象: 使用的处理 只读代理响应对象的配置对象
readonlyMap // 用于缓存目标对象与 只读响应代理对象的映射
)
}
5.3 shallowReadonly 只读浅层响应代理
shallowReadonly
用于创建只读浅层响应代理对象
源码:
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
return createReactiveObject(
target, // target:创建代理的目标对象
true, // true: 表示创建代理对象是只读响应代理
shallowReadonlyHandlers, // 普通对象: 使用的处理只读代理响应对象的配置对象
shallowReadonlyCollectionHandlers, // 集合对象: 使用的处理 只读代理响应对象的配置对象
shallowReadonlyMap // 用于缓存目标对象与 只读响应代理对象的映射
)
}
5.4 总结
其实通过上面的分析我们会发现, 所有创建代理响应对象通过createReactiveObject
函数调用实现的
在调用时通过传入不同的参数, 返回不同的代理对象, 使用不同的代理配置对象, 使用不同的WeakMap
缓存目标对象与代理对象的映射
唯一相同的就是都将API 函数接收的第一个参数作为代理对象的目标对象, 即createReactiveObject
函数的第一个参数: