前言
本文属于笔者Vue3源码阅读系列第一篇文章
响应式是什么
在阅读源码之前,我们先来了解一下什么是响应式 用大白话讲就是,数据改变,状态也自动保持同步。 举个例子:
javascript
const obj = {text:'hello world'}
// 一个副作用函数
functione effect(){
// 读取obj.text
document.bdoy.innnerText = obj.text
}
上面这段代码,如果我们修改了obj.text的值,effec函数自动执行,那么ob就可以称之为响应式数据。
Vue2的响应式
上图来自 官方文档,Vue初始化时对状态数据通过Obejct.defineProperty方法进行劫持,在执行组件render时会读取这些数据,触发数据的getter,然后组件的 watcher实例会把这些数据状态收集为依赖,当数据状态变更触发setter,setter通知watcher,然后render 函数重新执行更新组件。这样就完成了响应式的过程。 Object.defineProperty的特点如下。
Object.defineProperty是通过给对象属性进行数据劫持的,需要对每个对象,每个属性进行遍历,如果是嵌套对象,还需要深度遍历。性能问题会比较明显。- 无法劫持属性的添加、删除操作,只能劫持已有属性的变化。
- 兼容性比较好吧。
阅读Vue3 Reactivity源码
Vue3使用Proxy来实现响应式,因为它可以对整个对象进行代理,包括对象的所有属性、数组的所有元素以及类数组对象的所有元素**,**支持13种拦截操作。
reactive
接下来看看reactive的源码 (packages/reactivity/src/reactive.ts)
reactive方法的逻辑,先判断传入的对象是不是只读,如果是直接返回,否则调用createReactiveObject。
createReactiveObject
createReactiveObject 主要是做一些校验。
- 判断target 是不是 object,不是直接返回。
- 判断target是不是已经是Proxy,直接返回。
- 对一个原始对象多次响应式处理,直接返回。
- 不能被代理的情况。
- 最后返回
new Proxy(...)
下面看一下mutableHandlers的实现
BaseHandlers
mutableHandlers实现在packages\reactivity\src\baseHandlers.ts,在baseHandlers中包含四种handler
- mutableHandlers 可变处理。
- readonlyHandlers 只读处理。
- shallowReactiveHandlers 浅响应式处理。
- shallowReadonlyHandlers 浅响应和只读处理。
我们重点关注mutableHandlers的实现 
deleteProperty
用于拦截从target删除某个属性的操作(delete obj.xxx), 先检查target 中是否有这个key,然后获取这个属性的值,然后调用Reflect.deleteProperty删除key,如果删除成功的话,就调用tigger触发更新。
has
用于拦截判断某个key 是否存在于target中,先调用Reflect.has来拿到结果,然后判断是不是Symbol,如果不是,或者也不在builtInSymbols中,就调用track收集依赖。
ownKeys
用于拦截遍历操作,先调用track收集依赖,然后调用Reflect.ownkeys返回结果。
get
上图逻辑,处理这个四个标识,该分别返回什么值。 
上图逻辑,判断target是不是Array,如果是,判断key是不是'includes', 'indexOf', 'lastIndexOf','push', 'pop', 'shift', 'unshift', 'splice' 中的一种,如果是就返回arrayInstrumentations中对应的方法。
我们看看重写includes, indexOf, lastIndexOf干了些什么:
- 先调用toRaw 获取原始数组
- 遍历每一项,对每一项都
track依赖收集。 - 调用数组的'includes', 'indexOf', 'lastIndexOf'得到结果res
- 如果是-1 或者是false,将参数调用toRow得到原始对象后再次调用数组的'includes', 'indexOf', 'lastIndexOf'并返回结果
- 否则返回第三步的结果。
我们看看重写push, pop, shift, unshift, splice干了些什么:
- 暂停track依赖收集
- 调用数组API 得到结果
- 恢复track
- 返回结果
然后看看get后面的逻辑
调用Reflect.get 获取本次get的结果res 如果key 是Symbol,并且在builtInSymbols,就返回res ,或者不是Symbol,但是在isNonTrackableKeys 也返回res 如果不是只读,就track 依赖收集 如果是浅响应,就返回res 如果res 是Ref,并且target是数组,并且key 是正整数,就返回res,否则返回res.value 如果res是对象类型,就接着进行响应式处理,并返回代理对象,根据isReadonly调用readonly/reactive, 嵌套对象的响应式是在get 才会响应式处理 如果上面的都没命中就会返回res.
set

- 先获取到当前值的oldValue
- 如果不是浅响应,新值(value)不是浅响应并且也不是只读的就通过
toRaw获得就值和新值的原始数据 - 如果target 不是数组,并且oldValue是Ref并且,value不是Ref,但是oldValue是只读的,就返回false,否则oldValue.value 的值设置为新值(value),并返回true.
- 如果target是数组,并且key 是整数的话就判断key 是不是小于数组的length,否则就调用hasOwn判断是否key 是否在 target上
- 调用Reflect.set,设置value
- 如果target是原始原型链中的某个内容,则不触发
- 如果hadKey是false,就调用trigger 触发更新
- 如果value和oldValue 相等,不触发更新
总结
到此,我们已经读完了响应式reactive的内容,来总结一下
- 响应式的概念
- vue2 响应式的实现
- reactive 的源码
- createReactiveObject的源码
- baseHandlers的源码
如果本文对你有一点帮助,请点一个大大的赞,你的支持就是我创作的动力。