阅读 1961

给vue3源码添加注释:数据响应式部分

项目地址

vue 3.0,版本Pre-Alpha

数据响应源码地址

packages->reactivity->src->reactive.ts

引入的代码加注释

isObject

export const isObject = (val: any): val is Record<any, any> => val !== null && typeof val === 'object'
// 引入packages->shared 文件夹下的isObject函数
// 类型保护, al is Record 类型谓词,每当调用isObject这个函数的时候,传入任意的值必须是Record这个函数里的一个参数名,这个函数定义了任意的泛型,返回的是一个是否是对象的布尔值判断,并且不能为空
复制代码

toTypeString

export const objectToString = Object.prototype.toString
export const toTypeString = (value: unknown): string => objectToString.call(value)
// 引入packages->shared 文件夹下的toTypeString函数
// 类型判断,调用toTypeString这个函数的时候,传入一个安全的任意类型(unknown),并且一定要是字符串,然后通过Object.prototype.toString.call()的方式判断到底是不是字符串
// unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。
// (value: unknown): string 可能是表示参数必须是unknown类型,返回值是string类型
复制代码

builtInSymbols

const builtInSymbols = new Set(
  Object.getOwnPropertyNames(Symbol)
    .map(key => (Symbol as any)[key])
    .filter(value => typeof value === 'symbol')
)
// 引入 packages/reactivity/src/baseHandlers.ts 中的builtInSymbols
// 定义了一个Set集合类
// Object.getOwnPropertyNames(Symbol) 返回symbol所有的可枚举和不可枚举属性名称字符串
// map(key => (Symbol as any)[key]) 转换成js是 map(function (key) { return Symbol[key]; }), as any 的目的是为了防止 TS 编译器抱怨,因为从类型上 key 和 Symbol 无关联,你直接拿 key 访问会让 TS 害怕访问到不存在的属性
// filter(value => typeof value === 'symbol') 筛选出值的类型是symbol的数据
复制代码

JS 内置了一些特殊的 Symbol(比如迭代器),表现上也相当于一个对象的属性,但这种属性是不能被收集响应依赖的,所以要排除掉它。这个代码的主要作用是确定哪些属性(Symbol)符合这个情况(从 Symbol 上获取类型为 Symbol 的属性)

isRef

export const refSymbol = Symbol(__DEV__ ? 'refSymbol' : undefined)
export function isRef(v: any): v is Ref<any> {
  return v ? v[refSymbol] === true : false
}
// 引入 packages/reactivity/src/ref.ts 中的isRef
// 类型保护,传入数据的属性必须要是 refSymbol 的 Symbol 才为true,否则为false
复制代码

track

export const enum OperationTypes {
  // 使用文字字符串替换数字,这样更容易检查
  // debugger events
  SET = 'set',
  ADD = 'add',
  DELETE = 'delete',
  CLEAR = 'clear',
  GET = 'get',
  HAS = 'has',
  ITERATE = 'iterate'
}
// WeakMap弱引用的map对象,这里主要是为了避免内存泄漏
const targetMap = new WeakMap<any, KeyToDepMap>()

///////////////////上面来自其他文件的引用/////////////////////

export interface ReactiveEffect {
  (): any
  isEffect: true
  active: boolean
  raw: Function
  deps: Array<Dep>
  computed?: boolean
  scheduler?: (run: Function) => void
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  onStop?: () => void
}
// 规定activeReactiveEffectStack必须遵从上面的ReactiveEffect接口
export const activeReactiveEffectStack: ReactiveEffect[] = []

export function track(
  target: any,
  type: OperationTypes,
  key?: string | symbol
) {
  // 如果没被跟踪就不执行
  if (!shouldTrack) {
    return
  }
  // 获取追踪到的属性
  const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  // 如果有值就执行操作
  if (effect) {
    // 如果类型是迭代器,那么key设置成迭代器的Symbol
    if (type === OperationTypes.ITERATE) {
      key = ITERATE_KEY
    }
    // 获取存储的值
    let depsMap = targetMap.get(target)
    // 如果没有,就存贮,void 0 就是 undfined,防止 undfined 被重写
    if (depsMap === void 0) {
      targetMap.set(target, (depsMap = new Map()))
    }
    // 获取key,传入的key值不能为空,在ts中属性或者参数后面加感叹号是不能为空的意思
    let dep = depsMap.get(key!)
    // 如果没有,就存贮
    if (dep === void 0) {
      depsMap.set(key!, (dep = new Set()))
    }
    // 如果在存贮中找不到effect,就添加,并且在effect的deps属性中添加dep
    if (!dep.has(effect)) {
      dep.add(effect)
      effect.deps.push(dep)
      if (__DEV__ && effect.onTrack) {
        effect.onTrack({
          effect,
          target,
          type,
          key
        })
      }
    }
  }
}
复制代码

这个函数的主要作用是深入追踪对象中的内层并且把追踪到的数据存贮起来

createGetter

function createGetter(isReadonly: boolean) {
  return function get(target: any, key: string | symbol, receiver: any) {
    const res = Reflect.get(target, key, receiver)
    if (typeof key === 'symbol' && builtInSymbols.has(key)) {
      return res
    }
    if (isRef(res)) {
      return res.value
    }
    // 通过track函数做深度追踪处理
    track(target, OperationTypes.GET, key)
    return isObject(res)
      ? isReadonly
        ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          // 需要以只读和延迟反应的方式去避免循环依赖
          readonly(res)
        : reactive(res)
      : res
  }
}
// 引入 packages/reactivity/src/baseHandlers.ts 中的createGetter函数
// 创建一个函数,传入一个布尔值,拦截数据某个属性的读取操作
// target 目标对象
// key 属性名
// receiver 属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象)
// Reflect es6 原生api, Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法
// 上面代码中,每一个Proxy对象的拦截操作(get),内部都调用对应的Reflect方法,目的是为了保证原生行为能够正常执行
复制代码

这个函数的主要作用是拦截数据的get操作,通过Reflect返回的深层数据做进一步的处理,判断是否是对象,是对象的话重新走一遍循环

mutableHandlers

export const mutableHandlers: ProxyHandler<any> = {
  get: createGetter(false),
  set,
  deleteProperty,
  has,
  ownKeys
}
// 引入 packages/reactivity/src/baseHandlers.ts 中的mutableHandlers
// 类型断言,定义一些是proxy的handler,标记一个<any>的泛型,编译器这里会提供ProxyHandler的代码属性提示
复制代码

如果数据是可变的,就执行这个

readonlyHandlers

export const readonlyHandlers: ProxyHandler<any> = {
  get: createGetter(true),

  set(target: any, key: string | symbol, value: any, receiver: any): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Set operation on key "${String(key)}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return set(target, key, value, receiver)
    }
  },

  deleteProperty(target: any, key: string | symbol): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Delete operation on key "${String(
            key
          )}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return deleteProperty(target, key)
    }
  },

  has,
  ownKeys
}
复制代码

这个的作用很简单的了,如果你定义的数据是只读的,那么如果你想要改变这个数据的时候,就会给你警告,failed: target is readonly

hasOwn

const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
  val: object,
  key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)
// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)
// is   ts中有一个特殊的关键字,可以用来判断一个变量属于某个接口|类型
// 这个方法是解决 数组push时,会调用两次 set 的情况,比如 arr.push(1)
// 第一次set,在数组尾部添加1
// 第二次set,给数组添加length属性
复制代码

createInstrumentationGetter

function createInstrumentationGetter(instrumentations: any) {
  return function getInstrumented(
    target: any,
    key: string | symbol,
    receiver: any
  ) {
    // 判断key是否是instrumentations本身的属性,并且target包含key,就使用instrumentations,否则就使用target
    // key in target   in 在这里是做判断用, key 是否在 target中
    // 当“target”为数组时,“key”指的是数组的“索引”;当“target”为对象是,“key”指的是对象的“属性”。
    target =
      hasOwn(instrumentations, key) && key in target ? instrumentations : target
    return Reflect.get(target, key, receiver)
  }
}
复制代码

柯里化函数,初次调用返回getInstrumented函数,getInstrumented函数的作用是,避免多次proxy的重复操作

mutableCollectionHandlers

export const mutableCollectionHandlers: ProxyHandler<any> = {
  get: createInstrumentationGetter(mutableInstrumentations)
}
// 可变数据处理函数
复制代码

readonlyCollectionHandlers

export const readonlyCollectionHandlers: ProxyHandler<any> = {
  get: createInstrumentationGetter(readonlyInstrumentations)
}
// 只读数据处理函数
复制代码

UnwrapNestedRefs

export interface Ref<T> {
  [refSymbol]: true
  value: UnwrapNestedRefs<T>
}
export type UnwrapNestedRefs<T> = T extends Ref<any> ? T : UnwrapRef<T>
// T extends Ref<any> 条件类型,如果 T 能够赋值给 Ref 那么就用T,否则用UnwrapRef<T>
复制代码

作用是遇到 Ref 嵌套 Ref 时能正确(递归)推导出业务数据的类型

UnwrapRef

// Recursively unwraps nested value bindings.
// 递归打开嵌套的绑定值
export type UnwrapRef<T> = {
  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
  array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
  object: { [K in keyof T]: UnwrapRef<T[K]> }
  stop: T
}[T extends Ref<any>
  ? 'ref'
  : T extends Array<any>
    ? 'array'
    : T extends BailTypes
      ? 'stop' // bail out on types that shouldn't be unwrapped  // 在不应该打开的类型上退出
      : T extends object ? 'object' : 'stop']
// T extends Ref<infer V>  infer 前缀表示需要返回的类型,T extends Ref<infer V> 表示 T 可以赋值给 Ref 的返回类型
复制代码

ReactiveEffect

export interface ReactiveEffect {
  (): any
  isEffect: true
  active: boolean
  raw: Function
  deps: Array<Dep>
  computed?: boolean
  scheduler?: (run: Function) => void
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  onStop?: () => void
}
// 定义的接口
复制代码

源码加注释

import { isObject, toTypeString } from '@vue/shared'
import { mutableHandlers, readonlyHandlers } from './baseHandlers'

import {
  mutableCollectionHandlers,
  readonlyCollectionHandlers
} from './collectionHandlers'

import { UnwrapNestedRefs } from './ref'
import { ReactiveEffect } from './effect'

// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
// 从概念上讲,将依赖项看作维护一组订阅服务器的dep类比较容易,但我们只是将它们存储为原始Set以减少内存开销。
export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<string | symbol, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()

// WeakMaps that store {raw <-> observed} pairs.
// WeakMap 存贮 {raw <-> observed} 的映射
// WeakMap弱引用的map对象,这里主要是为了避免内存泄漏
const rawToReactive = new WeakMap<any, any>()
// 存贮可变的
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>()
// 存贮只读的
const readonlyToRaw = new WeakMap<any, any>()

// WeakSets for values that are marked readonly or non-reactive during
// observable creation.
// WeakSet 用来创建只读或者非响应对象的标记
// WeakSet 弱引用Set ,不能存贮值,只能存贮对象引用,主要也是为了避免内存泄漏
const readonlyValues = new WeakSet<any>()
const nonReactiveValues = new WeakSet<any>()

// 所有存贮类型的集合
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
// 设置白名单正则
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/

// 开始检测那些值是可以被proxy
const canObserve = (value: any): boolean => {
  return (
    // 不是vue 对象
    !value._isVue &&
    // 不是 vNode
    !value._isVNode &&
    // 白名单: Object|Array|Map|Set|WeakMap|WeakSet
    observableValueRE.test(toTypeString(value)) &&
    // 没有代理过的
    !nonReactiveValues.has(value)
  )
}

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 如果监测到proxy是只读的,就返回只读的版本
  if (readonlyToRaw.has(target)) {
    return target
  }
  // target is explicitly marked as readonly by user
  // 如果target被用户设置为只读,则让它只读,并返回
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    // 以下是上面代码设置的一堆WeakMap
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

export function readonly<T extends object>(
  target: T
): Readonly<UnwrapNestedRefs<T>>
export function readonly(target: object) {
  // value is a mutable observable, retrieve its original and return
  // a readonly version.
  // 如果监测到值是可改变的,就找出原始值并返回只读版本
  if (reactiveToRaw.has(target)) {
    target = reactiveToRaw.get(target)
  }
  return createReactiveObject(
    target,
    rawToReadonly,
    readonlyToRaw,
    readonlyHandlers,
    readonlyCollectionHandlers
  )
}

// 创建响应式对象
function createReactiveObject(
  target: any,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 判断target类型,是否是对象,不是的话就发送警告
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target already has corresponding Proxy
  // 已经Proxy处理过的,不需要再次处理
  let observed = toProxy.get(target)
  // 不为空的情况下返回值,void 0 就是 undfined,防止 undfined 被重写
  if (observed !== void 0) {
    return observed
  }
  // target is already a Proxy
  // target 已经是Proxy
  if (toRaw.has(target)) {
    return target
  }
  // only a whitelist of value types can be observed.
  // 只有加入白名单的对象才能被代理
  if (!canObserve(target)) {
    return target
  }
  // 如果是已经处理过,并且存贮为 Set, Map, WeakMap, WeakSet 类型的值,那么使用collectionHandlers 集合处理程序,否则使用baseHandlers 基本处理程序
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
  // 这里整个数据响应式最总要的一行代码,new Proxy
  observed = new Proxy(target, handlers)
  // 存贮处理结果,主要是为了避免重复处理
  toProxy.set(target, observed)
  // 一样的是为了避免重复处理
  toRaw.set(observed, target)
  // 这里主要是给每一次的代理做个标记,但是这么做有什么好处还没想到         
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map())
  }
  // 返回代理后的对象
  return observed
}

////////////////////下面这些还不太明白///////////////////////

// 判断是否可改
export function isReactive(value: any): boolean {
  return reactiveToRaw.has(value) || readonlyToRaw.has(value)
}

// 判断是否只读
export function isReadonly(value: any): boolean {
  return readonlyToRaw.has(value)
}


export function toRaw<T>(observed: T): T {
  return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
}

export function markReadonly<T>(value: T): T {
  readonlyValues.add(value)
  return value
}

export function markNonReactive<T>(value: T): T {
  nonReactiveValues.add(value)
  return value
}
复制代码

最后

水平有限,有些地方的注释是自己的猜测,有错漏之处希望大家指出,感谢社区和群里给我解答的大佬!