第二节:vue3源码响应系统:reactive创建响应式代理

2023-11-01  本文已影响0人  时光如剑

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>

注意: 响应式是基于ES2015Proxy实现的, 返回的代理对象不等于原始对象, 建议仅使用代理对象而避免依赖原始对象, 因为原始对象不具有响应性.

接下里我们查看一下源码中 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 函数存在两条分支:

  1. 如果参数target 为只读响应代理,即具有__v_isReadonly属性且值为true,则直接返回参数 target
  2. 如果参数 target 不是只读响应代理,则调用createReactiveObject函数创建proxy 代理对象,并返回

通过reactiveAPI 的实现可以看出几件事情:

  1. reactive API 函数只接受一个参数
  2. reactiveAPI 参数如果通过isReadonly判断为true时, 表示为只读响应, 则返回参数
  3. 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 方法中具体边缘判断:

  1. 通过isObject函数判断参数target不是对象的情况,也就是能通过判断的参数typeof检测类型都是object, 注意null除外
  2. 接着判断参数是否已经是reactive创建的代理对象, 如果是,则返回参数,这里通过判读表示参数并不是具有相应性的代理对象
  3. 其次通过proxyMap缓存中判断当前源对象是否已经创建过代理对象, 如果已经创建过, 则直接返回代理对象, 提升性能,避免通过一个源对象,多次调用reactive函数,多次创建返回不同的代理对象
  4. 最后判断参数是否可以创建代理对象, 也就是说虽然走到这里的参数target类型都是object, 但object有可详细区分为普通对象Object, 数组Array,正则RegExp等各详细的类型. 最后判断参数如果不能创建代理对象, 则直接返回参数, 例如正则
    这里只是简单罗列了一下边缘判断, 更详细的分析, 我们放在下一章节

2.3 总结

从上面的源码中了解:
1.reactiveAPI函数只能对对象类型进行代理转换, 如果传入原始类型数据或函数,则不会有任何操作, 如果在开发环境,控制台还会输出警告

  1. 在创建代理对象之前,通过一系列优化校验,避免重复操作, 优化框架性能
  2. reactive会根据对象的不同类型(更详细的类型),在创建代理对象时,使用不同的handler配置对象
  3. 创建完代理对象使用 proxyMap 缓存 目标对象target 和 代理对象proxy 之间对应关系

其实去除用于优化校验判断外,reactive方法功能很简单: 就是创建一个proxy对象并返回



reactiveAPI 创建proxy对象流程图:

reactive.png

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 是否为只读响应

  1. 首先判断参数value值是否存在, 不存在, 直接返回false
  2. 如果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 测试

未设置setcomputed 为只读响应数据, 通过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')

通过控制台输出可知:

  1. 未设置setcomputed 返回的 student是只读响应式对象, 对象本身就具有__v_isReadonly属性, 且属性值为true
  2. 因此当通过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
  }

源码分析:

  1. 在这个判断中, isReadonly, 是调用createReactiveObject 方法传入的第二个参数, reactive() 方法中调用固定传入false, 因此!(isReadonly && target[ReactiveFlags.IS_REACTIVE])结果固定为true
  2. 因此这里判断条件是 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 代理对象, 因此usertarget 为同一个代理对象

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)

通过示例分析:

  1. 目标对象target多次调用reactive()方法创建代理对象
  2. 目标target第一个调用reactive()创建代理对象时,会缓存目标对象target与代理对象的映射关系
  3. target第二个调用reactive()创建代理对象, 因为target即不是只读代理, 并且是一个普通对象,不存在__v_raw属性,因此会一路畅通走到当前源码位置
  4. 通过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主要做了两件事:

  1. 获取目标对象target的类型 targetType
  2. 判断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))
}

源码分析:

  1. TargetType是一个枚举类型,标识不可创建代理的无效类型(0), 普通对象类型(1), Set/Map 等集合类型(2)三种
  2. 通过如果目标对象target__v_skip属性为true,或者target为不可扩展对象, 则返回TargetType.INVALID(0) 无效类型
  3. 如果目标对象不具有__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)
}

通过源码分析:

  1. 这里通过原生toString方法,获取对象类型字符串,例如:[Object Array]
  2. toRawType方法中截取参数类型获取关键字符串,例如: Array, Object, String
  3. 返回类型字符串,

在通过toRawType获取类型后, 在调用targetTypeMap()方法,获取并返回目标对象类型

也就是说, 源码将typeof检测的类型为object的数据,通过Object.toString获取更详细的类型区分, 并以此将这些类型的数据归总为三类:

  1. INVALID: 值为0, 表示无效类型
  2. COMMON: 值为1, 表示普通对象类型(Object, Array)
  3. 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 }

示例中两个参数返回的都是参数本身:

  1. 参数/aa/, 获取类型为RegExp, 但是在targetTypeMap方法中被判断为TargetType.INVALID 无效类型
  2. 参数Object.freeze({ age: 18 }) 冻结对象,在!Object.isExtensible(value)判断为不可扩展对象, 直接返回TargetType.INVALID 无效类型
  3. 无效类型不会创建代理,返回目标对象本身

扩展: 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

通过源码了解:

  1. 通过目标对象target创建代理对象proxy
  2. 判断 targetType(目标对象类型) 使用不同的代理配置

可以看到创建代理对象的关键点在与代理对象的handler, 而这个handler 就是collectionHandlersbaseHandlers两个对象
通过targetType判断使用哪个 handler, 值为TargetType.COLLECTION,即枚举值2的使用, 使用collectionHandlers, 否则使用baseHandlers
也就是说:

  1. TargetType类型为COMMON(1) 时, 表示普通对象, 使用baseHandlers 作为代理的配置对象
  2. TargetType类型为COLLECTION(2) 时, 表示普通对象, 使用collectionHandlers 作为代理的配置对象

collectionHandlersbaseHandlers对象在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五个方法拦截器,

简单介绍一下几个拦截器的作用:

  1. get : 拦截对象的getter操作, 例如:obj.name
  2. set : 拦截对象的setter操作,例如:obj.name="xx"
  3. deleteProperty: 拦截对象的delete操作,例如:delete obj.name
  4. has: 拦截in操作,例如'name' in obj
  5. 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方法的实现对比getset方法的实现都要简单很多,也没有什么特别的地方,就是通过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

注意点在于对数组的特殊处理,如果当前对象是数组的话,那么就会触发lengthiterate事件,如果不是数组的话,那么就会触发ITERATE_KEYiterate事件;

这一块的区别都是在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函数的第一个参数:

上一篇下一篇

猜你喜欢

热点阅读