关于javascript:petitevue源码剖析逐行解读vuereactivity之Map和Set的reactive

本篇咱们会持续摸索reactive函数中对Map/WeakMap/Set/WeakSet对象的代理实现。

Map/WeakMap/Set/WeakSet的操作

因为WeakMap和WeakSet别离是Map和Set的不影响GC执行垃圾回收的版本,这里咱们只钻研Map和Set即可。

Set的属性和办法

  • size: number 为拜访器属性(accessor property),返回Set对象中的值的个数
  • add(value: any): Set 向Set对象队尾增加一个元素
  • clear(): void 移除Set对象内所有元素
  • delete(value: any): boolean 移除Set中与入参值雷同的元素,移除胜利则返回true
  • has(value: any): boolean 判断Set中是否存在与入参值雷同的元素
  • values(): Iterator 返回一个新的迭代器对象,蕴含Set对象中按插入顺序排列的所有元素
  • keys(): Iteratorvalues(): Iterator一样的效用
  • @@iteratorvalues(): Iterator一样的效用,for of中调用
  • entries(): Iterator 返回一个新的迭代器对象,蕴含Set对象中按插入顺序排列的所有元素,但为与Map应用统一每次迭代返回的内容为[value, value]
  • forEach(callbackFn: { (value: any, set: Set) => any } [, thisArg]) 按插入程序遍历Set对象的每一个元素

Map的属性和办法

  • size: number 为拜访器属性(accessor property),返回Set对象中的值的个数
  • set(key: any, value: any): Map 向Map对象增加或更新一个指定键的值
  • clear(): void 移除Map对象内所有键值对
  • delete(key: any): boolean 移除Map对象中指定的键值对,移除胜利则返回true
  • has(key: any): boolean 判断Map中是否存在键与入参值雷同的键值对
  • values(): Iterator 返回一个新的迭代器对象,蕴含Map对象中按插入顺序排列的所有值
  • keys(): Iterator 返回一个新的迭代器对象,蕴含Map对象中按插入顺序排列的所有键
  • @@iteratorentries(): Iterator一样的效用,for of中调用
  • entries(): Iterator 返回一个新的迭代器对象,蕴含Map对象中按插入顺序排列的所有键值对
  • forEach(callbackFn: { (value: any, key: any, map: Map) => any } [, thisArg]) 按插入程序遍历Map对象的每一个键值对
  • get(key: any): any 返回Map对象中指定键对应的值,若没有则返回undefined

逐行看代码我是认真的

// reactive.ts

export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}

因为Map/Set不像Object或Array那样可间接通过属性拜访的形式获取其中的元素,而是通过add,has,delete操作,因而须要像解决Array的slice等办法那样代理Map/Set的这些办法。

// collectionHandlers.ts

type MapTypes = Map<any, any> | WeakMap<any, any>
type SetTypes = Set<any, any> | WeakSet<any, any>

// 代理Map/Set原生的办法
// 没有代理返回迭代器的办法??
const mutableInstrumentations = {
  get(this: MapTypes, key: unknown) {
    return get(this, key)
  }
  get size() {
    // 原生的size属性就是一个拜访器属性
    return size(this as unknown as IterableCollections)
  },
  has,
  add,
  set,
  delete: deleteEntry, // delete 是关键字不能作为变量或函数名称
  clear,
  forEach: createForEach(false, false)
}

function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  const instrumentations = mutableInstrumentations

  return (
    target: CollectionTypes,
    key: string | symbol,
    receiver: CollectionTypes
  ) => {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    }
    else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    }
    else if (key === ReactiveFlags.RAW) {
      return target
    }

    // 代理Map/WeakMap/Set/WeakSet的内置办法
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}

TypeScript小课堂as断言——this as unknown as IterableCollections
在TypeScript中可通过类型申明定义变量的类型(其中蕴含复合类型),而类型推导则能够依据赋值语句中右侧字面量推导出变量的理论类型,或通过以后变量应用的场景推导出以后理论类型(尤其是定义为复合类型)。但有时无奈通过以后应用场景执行准确的类型推导,这时开发者能够通过as断言告知TypeScript编译器该变量以后应用范畴的数据类型(要置信本人肯定比编译器更理解本人的代码:D)。
那么as unknown即示意将类型批改为unknown,那么类型为unknown是示意什么呢?unknown是TypeScript3.0引入的top type(任何其余类型都是它的subtype),意在提供一种更平安的形式代替any类型(any类型是top type也是bottom type,应用它象征和绕过类型查看),具备如下特点:

  1. 任何其它类型都能够赋值给unknown类型的变量
  2. unknown类型的变量只能赋值给anyunknown类型的变量
  3. 如果不对unknown类型的变量执行类型膨胀,则无奈执行其它任何操作
// 1. 任何其它类型都能够赋值给`unknown`类型的变量 
let uncertain: unknown = 'Hello'
uncertain = 12
uncertain = { hello: () => 'Hello' }

// 2.`unknown`类型的变量只能赋值给`any`或`unknown`类型的变量 
let uncertain: unknown = 'Hello'
let noSure: any = uncertain
let notConfirm: unknown = uncertain

// 3. 如果不对`unknown`类型的变量执行类型膨胀,则无奈执行其它任何操作
let uncertain = { hello: () => 'Hello' }
uncertain.hello() // 编译报错 
// 通过断言as膨胀类型
(uncertain as {hello: () => string}).hello()

let uncertain: unknown = 'Hello'
// 通过typeof或instanceof膨胀类型
if (typeof uncertain === 'string') {
  uncertain.toLowerCase()
}

那么as unknown后的as IterableCollections用意就非常显著了,就是对变量进行类型膨胀。this as unknown as IterableCollections其实就是as IterableCollections啦。

而后咱们逐个看看代理办法的实现吧

Mapget办法

get办法只有Map对象领有,因而其中次要思路是从Map对象中获取值,跟踪键值变动后将值转换为响应式对象返回即可。
但因为要解决readonly(reactive(new Map()))这一场景,增加了很多一时让人看不懂的代码而已。

const getProto = <T extends CollectionTypes>(v: T): any => Reflect.getProrotypeOf(v)

// 代理Map/WeakMap的get办法
function get(
  target: MapTypes, // 指向this,因为Map对象曾经被代理,因而this为代理代理
  key: unknown,
  isReadonly = false,
  isShallow = false
) {
  /**
   * 1. 针对readonly(reactive(new Map()))的状况,
   *    target获取的是代理对象,而rawTarget的是Map对象
   * 2. 针对reactive(new Map())的状况,
   *    target和rawTarget都是指向Map对象
   */ 
  target = (target as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  /**
   * 若key为代理对象,那么被代理对象和代理对象的键都会被跟踪,即
   * const key = { value: 'foo' }
   * const pKey = reactive(key), 
   * const kvs = reactive(new Map())
   * kvs.set(pKey, 1)
   * 
   * effect(() => {
   *   console.log('pKey', kvs.get(pKey))
   * })
   * effect(() => {
   *   console.log('key', kvs.get(key))
   * })
   * 
   * kvs.set(pKey, 2)
   * // 回显 pkey 2 和 key 2
   * kvs.set(key, 3)
   * // 回显 key 2
   */  
  const rawKey = toRaw(key)
  if (key !== rawKey) {
    !isReadonly && track(rawTraget, TrackOpTypes.GET, key)
  }
  !isReadonly && track(rawTraget, TrackOpTypes.GET, rawKey)

  // 获取Map原型链上的has办法用于判断获取成员是否存在于Map對象上
  const { has } = getProto(rawTarget)
  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
  /**
   * Map对象中存在则从Map对象或代理对象上获取值并转换为响应式对象返回。
   * 针对readonly(reactive(new Map()))为什么是从响应对象上获取值,而不是间接从Map对象上获取值呢?
   * 这是为了放弃返回的值的构造,从响应式对象中获取值是响应式对象,在通过readonly的解决则返回的值就是readonly(reactive({value: 'foo'}))。
   */ 
  if (has.call(rawTarget, key)) {
    return wrap(target.get(key))
  }
  else if (has.call(rawTarget, rawKey)) {
    return wrap(target.get(rawKey))
  }
  else if (target !== rawTarget) {
    /**
     * 针对readonly(reactive(new Map())),即便没有匹配的键值对,也要跟踪对响应式对象某键的依赖信息
     * const state = reactive(new Map())
     * const readonlyState = readonly(state)
     * 
     * effect(() => {
     *  console.log(readonlyState.get('foo'))
     * })
     * // 回显 undefined
     * state.set('foo', 1)
     * // 回显 1
     */
    target.get(key)
  }

  // 啥都没有找到就默认返回undefined,所以啥都不必写
}

MapSetsize拜访器属性

function size(target: IterableCollections, isReadonly = false) {
  // 针对readonly(reactive(new Map())) 或 readonly(reactive(new Set()))只需获取响应式对象即可,因而reactive对象也会对size的拜访进行雷同的操作。
  target = (target as any)[RectiveFlags.RAW]
  // 跟踪ITERATE_KEY即所有批改size的操作均会触发拜访size属性的副作用函数
  !iReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  /**
   * 因为size为拜访器属性因而若第三个参数传递receiver(响应式对象),而响应式对象并没有size拜访器属性须要拜访的属性和办法,则会报异样``。因而须要最终将Map或Set对象作为size拜访器属性的this变量。
   */
  return Reflect.get(target, 'size', target)
}

MapSethas办法

function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
  const target = (this as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  // 和get办法代理一样,若key为代理对象则代理对象或被代理对象作为键的键值对发生变化都会触发拜访has的副作用函数
  if (key !== rawKey) {
    !isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
  }
  !isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)

  return key === rawKey
    ? target.has(key)
    : target.has(key) || target.has(rawKey)
}

Setadd办法

function add(this: SetTypes, value: unknown) {
  value = toRaw(value)
  const target = toRaw(this)
  const proto = getProto(target)
  const hadKey = proto.has.call(target, value)
  // 当Set对象中没有该元素时则触发依赖ITERATE_KEY的副作用函数,因而ADD操作会影响Set对象的长度
  if (!hadKey) {
    target.add(value)
    trigger(target, TriggerOpTypes.ADD, value, value)
  }

  return this
}

Mapset办法

function set(this: MapTypes, key: unknown, value: unknown) {
  value = toRaw(value)
  const target = toRaw(this)
  const { has, get } = getProto(target)

  // 别离查看代理和非代理版本的key是否存在于Map对象中
  let hadKey = has.call(target, key)
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target.key)
  }

  const oldValue = get.call(target, key)
  target.set(key, value)
  if (!hadKey) {
    // 当Map对象中没有该元素时则触发依赖ITERATE_KEY的副作用函数,因而ADD操作会影响Map对象的长度
    trigger(target, TriggerOpTypes.ADD, key, value)
  }
  else if (hasChanged(value, oldValue)) {
    // 如果新旧值不同则触发批改,依赖该键值对的副作用函数将被触发
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
}

留神:gethas办法中会同时跟踪代理和非代理版本的键对应的元素变动,而set办法则只会触发查找到的代理或非代理版本的键对应的元素变动。

deleteEntry办法

function deleteEntry(this: CollectionTypes, key: unknown) {
  const target = toRaw(this)
  const { has, get } = getProto(target)
  let hadKey = has.call(target, key)
  // 别离查看代理和非代理版本的key是否存在于Map/Set对象中
  let hadKey = has.call(target, key)
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target.key)
  }

  // 如果以后操作的是Map对象则获取旧值
  const oldValue = get ? get.call(target, key) : undefined
  const result = target.delete(key)
  if (hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

留神:gethas办法中会同时跟踪代理和非代理版本的键对应的元素变动,而deleteEntry办法则只会触发查找到的代理或非代理版本的键对应的元素变动。

MapSetclear办法

function clear(this: IterableCollections) {
  const target = toRaw(this)
  const hadItems = target.size !== 0
  const oldTarget = undefined
  const result = target.clear()
  if (hadItems) {
    trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
  }
  return result
}

MapSetforEach办法

function createForEach(isReadonly: boolean, isShallow: boolean) {
  return function forEach(
    this: IterableCollections,
    callback: Function,
    thisArg?: unknown
  ) {
    const observed = this as any
    const target = observed[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    !isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
    return target.forEach((value: unknown, key: unknown) => {
      // 将key和value都转换为代理对象
      return callback.call(thisArg, wrap(value), wrap(key), observed)
    })
  }
}

因为forEach会遍历所有元素(Map对象则是所有键值对),因而跟踪ITERATE_KEY即Map/Set对象元素个数发生变化则触发forEach函数的执行。

迭代器对象相干办法

至此咱们还没对entries,values,keys@@iterator这些返回迭代器的对象办法进行代理,而源码中则在最初为mutableInstrumentations增加这些办法的代理。

const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator/*就是@@iterator*/]
iteratorMethods.forEach(method => {
  mutableInstrumentations[method as string] = createIterableMethod(
    method,
    false,
    false
  )
})
function createIterableMethod(
  method: string | symbol,
  isReadonly: boolean,
  isShallow: boolean
) {
  return function(
    this: IterableCollections,
    ...args: unknown[]
  ): Iterable & Iterator {
    /**
     * 1. 针对readonly(reactive(new Map()))的状况,
     *    target获取的是代理对象,而rawTarget的是Map或Set对象
     * 2. 针对reactive(new Map())的状况,
     *    target和rawTarget都是指向Map或Set对象
     */ 
    const target = (this as any)[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)

    const targetIsMap = isMap(rawTarget)
    const isPair = method === 'entries' || (method === Symbol.iterator && targetIsMap)
    /**
     * 当调用的是Map对象的keys办法,副作用函数并没有拜访值对象,即副作用函数只依赖Map对象的键而没有依赖值。
     * 而键只能减少或删除,值可减少、删除和批改,那么此时当且仅当键增删即size属性发生变化时才会触发副作用函数的执行。
     * 若依赖值,那么批改其中一个值也会触发副作用函数执行。
     */
    const isKeyOnly = method === 'keys' && targetIsMap
    const innerIterator = target[method](...args)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    !isReadonly &&
      track(
        rawTarget,
        TrackOpTypes.ITERATE,
        isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
      )

    return {
      // 迭代器协定
      next() {
        const { value, done } = innerIterator.next()
        return done
          ? { value, done }
          : {
            value: isPair ? [wrap(value[0], wrap(value[1]))] : wrap(value),
            done
          }
      },
      // 可迭代协定
      [Symbol.iterator]() {
        return this
      }
    }
  }
}

可迭代协定(iterable protocol)

可迭代协定(iterable protocol),用于创立迭代器(iterator)。
如下内置类型都实现了可迭代协定:

  • 字符串
  • 数组
  • Set
  • Map
  • arguements对象
  • NodeList等DOM汇合类型

上面的语言个性将会接管可迭代协定返回的迭代器

  • for...of循环
  • 数据解构(const [a, b] = [1, 2])
  • 扩大操作符(const a = [1,2], b = [...a])
  • Array.from()
  • 创立Set
  • 创立Map
  • Promise.all()承受可迭代对象
  • Promise.race()承受可迭代对象
  • yield*操作符

让对象反对可迭代协定其实很简略,只需实现返回迭代器的[Symbol.iterator]办法即可。JavaScript Plain Old Object默认并没有反对可迭代协定,那么咱们能够自行实现以下:

const iterablizeKeys = (obj: {}) => {
  if (!obj[Symbol.iterator]) {
    obj[Symbol.iterator] = () => {
      const keys = Object.keys(obj) as const
      let i = 0

      // 返回一个迭代器
      return {
        next() {
          return { value: keys[i++], done: i > keys.length }
        }
      }
    }
  }

  return obj
} 

const iterableObj = iterablizeKeys({a: 1, b: 2})
for (let item of iterableObj) {
  console.log(item)
}
// 回显 a 
// 回显 b
Array.from(iterableObj) // 返回 ['a', 'b']

迭代器协定(iterator protocol)

迭代器协定(iterator protocol),提供不承受任何参数并返回IteratorResult对象的next办法,而IteratorResult对象蕴含指向以后元素的value属性和示意迭代是否已完结的done属性,当done属性值为true时示意迭代已完结。
迭代器协定的实现正如下面可迭代协定的示例中那样,不过咱们还能够将可迭代协定和迭代对象在同一个对象上实现。

const iterablizeKeys = (obj: {}) => {
  if (!obj[Symbol.iterator]) {
    let iteratorState = {
      keys: []
      i: 0
    }
    // 迭代器协定
    obj.next = () => ({ value: iteratorState.keys[iteratorState.i++], done: iteratorState.i > iteratorState.key.length })

    // 可迭代协定
    obj[Symbol.iterator] = () => {
      iteratorState.keys = Object.keys(obj) as const
      iteratorState.i = 0

      // 返回一个迭代器
      return this
    }
  }

  return obj
} 

const iterableObj = iterablizeKeys({a: 1, b: 2})
for (let item of iterableObj) {
  console.log(item)
}
// 回显 a 
// 回显 b
Array.from(iterableObj) // 返回 ['a', 'b']

总结

本篇咱们通过逐行浏览源码理解到reactive如何解决Map和Set对象了,下一篇咱们将开始以effect为入口进一步理解副作用函数是如何通过tracktrigger记录依赖和触发的。
尊重原创,转载请注明来自:https://www.cnblogs.com/fsjoh…肥仔John

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理