reactive
的函数实现如下:
function reactive (target) {
// 如果尝试把一个 readonly proxy 变成响应式,直接返回这个 readonly proxy
if (target && target.__v_isReadonly) {
return target
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
// 目标必须是对象或数组类型
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
// target 已经是 Proxy 对象,直接返回
// 有个例外,如果是 readonly 作用于一个响应式对象,则继续
return target
}
if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) {
// target 已经有对应的 Proxy 了
return isReadonly ? target.__v_readonly : target.__v_reactive
}
// 只有在白名单里的数据类型才能变成响应式
if (!canObserve(target)) {
return target
}
// 利用 Proxy 创建响应式
const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)
// 给原始数据打个标识,说明它已经变成响应式,并且有对应的 Proxy 了
def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
return observed
}
可以看到, reactive
内部通过createReactiveObject
函数吧target
变成了一个响应式对象.
在这个过程中, createReactiveObject
函数主要做了一下几件事:
- 函数首先判断
target
是不是数组或者对象类型, 如果不是则直接返回. 所以原始属于target必须是对象或者数组 - 如果对一个已经响应式的对象再次执行
reactive
, 还是返回这个响应式对象 - 如果对同一个原始数据多次执行
reactive
, 那么会返回相同的响应式对象. - 使用
canObserve(白名单)
对target
对象做进一步限制
const canObserve = (value) => {
return (!value.__v_skip &&
isObservableType(toRawType(value)) &&
!Object.isFrozen(value))
}
const isObservableType = /*#__PURE__*/ makeMap('Object,Array,Map,Set,WeakMap,WeakSet')
比如, 带有__v_skip
属性的对象, 被冻结的对象, 以及不再白名单内的对象如Date
类型的对象实例是不能变成响应式的.
5.通过Proxy Api
劫持target
对象, 把它编程响应式, 我们把Proxy
函数返回的结果称作响应式对象, 这里Proxy
对应的处理器对象会根据数据类型的不同而不同. 我们稍后会重点分析基本数据类型的Proxy
处理器对象, reactive
函数传入的baseHandlers
值是mutableHandlers
.
6.给原始数据打个标识, 作为对同一个原始数据多次执行reactive
的判断依据
mutableHandlers :
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
}
它其实就是劫持了我们对 observed 对象的一些操作,比如:
-
访问对象属性会触发 get 函数;
-
设置对象属性会触发 set 函数;
-
删除对象属性会触发 deleteProperty 函数;
-
in 操作符会触发 has 函数;
-
通过 Object.getOwnPropertyNames 访问对象属性名会触发 ownKeys 函数。
因为无论命中哪个处理器函数,它都会做依赖收集和派发通知这两件事其中的一个,所以这里我只要分析常用的 get 和 set 函数就可以了。
依赖收集: get函数
依赖收集发生在数据访问的阶段, 使用Proxy API
劫持数据对象, 当这个响应式对象属性被访问的时候就会执行get
函数.
const get = /*#__PURE__*/ createGetter()
// ...
function createGetter(isReadonly = false) {
return function get(target, key, receiver) {
if (key === "__v_isReactive" /* isReactive */) {
// 代理 observed.__v_isReactive
return !isReadonly
}
else if (key === "__v_isReadonly" /* isReadonly */) {
// 代理 observed.__v_isReadonly
return isReadonly;
}
else if (key === "__v_raw" /* raw */) {
// 代理 observed.__v_raw
return target
}
const targetIsArray = isArray(target)
// arrayInstrumentations 包含对数组一些方法修改的函数
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 求值
const res = Reflect.get(target, key, receiver)
// 内置 Symbol key 不需要依赖收集
if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') {
return res
}
// 依赖收集
!isReadonly && track(target, "get" /* GET */, key)
return isObject(res)
? isReadonly
?
readonly(res)
// 如果 res 是个对象或者数组类型,则递归执行 reactive 函数把 res 变成响应式
: reactive(res)
: res
}
}
get
函数主要做了四件事情:
- 对特殊的
key
进行代理 - 通过
Reflect.get
方法求值, 如果target
是数组, 且key
命中了arrayInstrumentations
, 则执行对应的函数. 大致实现如下:
const arrayInstrumentations = {}
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
arrayInstrumentations[key] = function (...args) {
// toRaw 可以把响应式对象转成原始数据
const arr = toRaw(this)
for (let i = 0, l = this.length; i < l; i++) {
// 依赖收集
track(arr, "get" /* GET */, i + '')
}
// 先尝试用参数本身,可能是响应式数据
const res = arr[key](...args)
if (res === -1 || res === false) {
// 如果失败,再尝试把参数转成原始数据
return arr[key](...args.map(toRaw))
}
else {
return res
}
}
})
当target
是一个数组的时候, 我们在访问'includes', 'indexOf', 'lastIndexOf'方法的时候就会执行代理函数.
- 通过
Reflect.get
求值, 然后会执行track
函数收集依赖. - 通过对计算的值
res
进行判断, 如果它也是数组或对象, 则递归执行reactive
把res
变成响应式对象, 则递归执行reactive
把res
变成响应式对象.
#Track
// 是否应该收集依赖
let shouldTrack = true
// 当前激活的 effect
let activeEffect
// 原始数据对象 map
const targetMap = new WeakMap()
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
// 每个 target 对应一个 depsMap
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
// 每个 key 对应一个 dep 集合
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
// 收集当前激活的 effect 作为依赖
dep.add(activeEffect)
// 当前激活的 effect 收集 dep 集合作为依赖
activeEffect.deps.push(dep)
}
}
收集依赖就是数据变化后执行的副作用函数.
派发通知: set函数
派发通知发生在数据更新的阶段, 响应式对象在数据更新的时候就会执行set
函数:
const set = /*#__PURE__*/ createSetter()
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key]
value = toRaw(value)
const hadKey = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// 如果目标的原型链也是一个 proxy,通过 Reflect.set 修改原型链上的属性会再次触发 setter,这种情况下就没必要触发两次 trigger 了
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value)
}
else if (hasChanged(value, oldValue)) {
trigger(target, "set" /* SET */, key, value, oldValue)
}
}
return result
}
}
set
函数的实现逻辑很简单:
- 通过
Reflect.set
求值 - 通过
trigger
函数派发通知
并依据key
是否存在target
上来确定通知类型: 新增/修改
核心部分就是通过trigger
函数派发通知. trigger
函数的实现如下:
// 原始数据对象 map
const targetMap = new WeakMap()
function trigger(target, type, key, newValue) {
// 通过 targetMap 拿到 target 对应的依赖集合
const depsMap = targetMap.get(target)
if (!depsMap) {
// 没有依赖,直接返回
return
}
// 创建运行的 effects 集合
const effects = new Set()
// 添加 effects 的函数
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
effects.add(effect)
})
}
}
// SET | ADD | DELETE 操作之一,添加对应的 effects
if (key !== void 0) {
add(depsMap.get(key))
}
const run = (effect) => {
// 调度执行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
// 直接运行
effect()
}
}
// 遍历执行 effects
effects.forEach(run)
}
trigger
函数就是根据target
和key
从targetMap
中找到相关的所有副作用函数执行一遍.
主要是这么几个事情:
- 通过 targetMap 拿到 target 对应的依赖集合 depsMap;
- 创建运行的 effects 集合;
- 根据 key 从 depsMap 中找到对应的 effects 添加到 effects 集合;
- 遍历 effects 执行相关的副作用函数。
副作用函数
vue内部存在一个effect
副作用函数栈:
// 全局 effect 栈
const effectStack = []
// 当前激活的 effect
let activeEffect
function effect(fn, options = EMPTY_OBJ) {
if (isEffect(fn)) {
// 如果 fn 已经是一个 effect 函数了,则指向原始函数
fn = fn.raw
}
// 创建一个 wrapper,它是一个响应式的副作用的函数
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
// lazy 配置,计算属性会用到,非 lazy 则直接执行一次
effect()
}
return effect
}
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect(...args) {
if (!effect.active) {
// 非激活状态,则判断如果非调度执行,则直接执行原始函数。
return options.scheduler ? undefined : fn(...args)
}
if (!effectStack.includes(effect)) {
// 清空 effect 引用的依赖
cleanup(effect)
try {
// 开启全局 shouldTrack,允许依赖收集
enableTracking()
// 压栈
effectStack.push(effect)
activeEffect = effect
// 执行原始函数
return fn(...args)
}
finally {
// 出栈
effectStack.pop()
// 恢复 shouldTrack 开启之前的状态
resetTracking()
// 指向栈最后一个 effect
activeEffect = effectStack[effectStack.length - 1]
}
}
}
effect.id = uid++
// 标识是一个 effect 函数
effect._isEffect = true
// effect 自身的状态
effect.active = true
// 包装的原始函数
effect.raw = fn
// effect 对应的依赖,双向指针,依赖包含对 effect 的引用,effect 也包含对依赖的引用
effect.deps = []
// effect 的相关配置
effect.options = options
return effect
}
因此针对嵌套 effect 的场景,我们不能简单地赋值 activeEffect,应该考虑到函数的执行本身就是一种入栈出栈操作,因此我们也可以设计一个 effectStack,这样每次进入 reactiveEffect 函数就先把它入栈,然后 activeEffect 指向这个 reactiveEffect 函数,接着在 fn 执行完毕后出栈,再把 activeEffect 指向 effectStack 最后一个元素,也就是外层 effect 函数对应的 reactiveEffect。
effect内部通过执行creteReactiveEffect
函数去创建一个新的efect
函数. 后续称为reactiveEffect
函数. effect
函数还支持传入一个配置参数来支持更多的feature
.
reactiveEffect
函数就是响应式的副作用函数, 当执行trigger
过程派发通知的时候, 执行的effect
就是它
reactiveEffect
函数做这么几件事:
- 判断
effect
是否是active
, 这里控制的是允许非active
状态且非调度执行状态直接执行原始函数fn
并返回 - 判断
effectStack
中是否包含effect
, 如果没有就把它推入栈中 - 把全局的
activeEffect
指向它 - 执行被包装的原始函数
fn
#只读对象
readonly
对象可以劫持一整个对象阻止属性的修改
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers
)
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
// 目标必须是对象或数组类型
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
// target 已经是 Proxy 对象,直接返回
// 有个例外,如果是 readonly 作用于一个响应式对象,则继续
return target
}
if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) {
// target 已经有对应的 Proxy 了
return isReadonly ? target.__v_readonly : target.__v_reactive
}
// 只有在白名单里的数据类型才能变成响应式
if (!canObserve(target)) {
return target
}
// 利用 Proxy 创建响应式
const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)
// 给原始数据打个标识,说明它已经变成响应式,并且有对应的 Proxy 了
def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
return observed
}
readonly
和reactive
对象的主要区别在于传入参数的不同.
readonly
传入的isReadonly
为true, 并且baseHandlers
传入的是readonlyHandlers
, 该handle实现如下:
const readonlyHandlers = {
get: readonlyGet,
has,
ownKeys,
set(target, key) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target)
}
return true
},
deleteProperty(target, key) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target)
}
return true
}
}
readonlyHandlers
不允许属性的修改和删除. 其中readonlyGet
是函数createGetter(true)
的返回:
function createGetter(isReadonly = false) {
return function get(target, key, receiver) {
// ...
// isReadonly 为 true 则不需要依赖收集
!isReadonly && track(target, "get" /* GET */, key)
return isObject(res)
? isReadonly
?
// 如果 res 是个对象或者数组类型,则递归执行 readonly 函数把 res readonly
readonly(res)
: reactive(res)
: res
}
}