美文网首页
第二节:vue3源码响应系统:reactive创建响应式代理

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

作者: 时光如剑 | 来源:发表于2023-11-01 14:34 被阅读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函数的第一个参数:

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/oqmeidtx.html