Vue3 源码解读之副作用函数与依赖收集

目录
  • 副作用函数
  • 副作用函数的全局变量
    • targetMap
    • targetMap 为什么使用 WeakMap
    • activeEffect
    • shouldTrack
  • 副作用的实现
    • effect 函数
    • ReactiveEffect 类
  • track 收集依赖
    • track 函数
    • trackEffects 函数
  • trigger 派发更新
    • trigger 函数
    • triggerEffects 函数
  • 总结

版本:3.2.31

副作用函数

副作用函数是指会产生副作用的函数,如下面的代码所示:

function effect(){
  document.body.innerText = 'hello vue3'
}

当 effect 函数执行时,它会设置 body 的文本内容,但除了 effect 函数之外的任何函数都可以读取或设置 body 的文本内容。也就是说,effect 函数的执行会直接或间接影响其他函数的执行,这时我们说 effect 函数产生了副作用。副作用很容易产生,例如一个函数修改了全局变量,这其实也是一个副作用。

// 全局变量
let val = 1
function effect() {
  val = 2 // 修改全局变量,产生副作用
}

副作用函数的全局变量

在副作用模块中,定义了几个全局的变量,提前认识这些变量有助与我们了解副作用函数的生成以及调用的过程。

// packages/reactivity/src/effect.ts
export type Dep = Set<ReactiveEffect> & TrackedMarkers
type KeyToDepMap = Map<any, Dep>
// WeakMap 集合存储副作用函数
const targetMap = new WeakMap<any, KeyToDepMap>()

// 用一个全局变量存储当前激活的 effect 函数
export let activeEffect: ReactiveEffect | undefined

// 标识是否开启了依赖收集
export let shouldTrack = true
const trackStack: boolean[] = []

targetMap

targetMap 是一个 WeakMap 类型的集合,用来存储副作用函数,从类型定义可以看出 targetMap的数据结构方式:

  • WeakMap 由 target --> Map 构成
  • Map 由 key --> Set 构成

其中 WeakMap 的键是原始对象 target,WeakMap 的值是一个 Map 实例,Map 的键是原始对象 target 的 key,Map 的值是一个由副作用函数组成的 Set。它们的关系如下:

targetMap 为什么使用 WeakMap

我们来看下面的代码:

const map = new Map();
const weakMap = new WeakMap();

(function() {
  const foo = {foo: 1};
  const bar = {bar: 2};

  map.set(foo, 1); // foo 对象是 map 的key
  weakMap.set(bar, 2); // bar 对象是 weakMap 的 key
})

在上面的代码中,定义了 map 和 weakMap 常量,分别对应 Map 和 WeakMap 的实例。在立即执行的函数表达式内部定义了两个对象:foo 和 bar,这两个对象分别作为 map 和 weakMap 的key。

当函数表达式执行完毕后,对于对象 foo 来说,它仍然作为 map 的 key 被引用着,因此垃圾回收器不会把它从内存中移除,我们仍然可以通过 map.keys 打印出对象 foo 。

对于对象 bar 来说,由于 WeakMap 的 key 是弱引用,它不影响垃圾收集器的工作,所以一旦表达式执行完毕,垃圾回收器就会把对象 bar 从内存中移除,并且我们无法获取 weakMap 的 key 值,也就无法通过 weakMap 取得对象 bar 。

简单地说,WeakMap 对 key 是弱引用,不影响垃圾回收器的工作**。根据这个特性可知,一旦 key 被垃圾回收器回收,那么对应的键和值就访问不到了。所以 WeakMap 经常用于存储那些只有当 key 所引用的对象存在时 (没有被回收) 才有价值的信息**。

例如在上面的场景中,如果 target 对象没有任何引用了,说明用户侧不再需要它了,这时垃圾回收器会完成回收任务。但如果使用 Map 来代替 WeakMap,那么即使用户侧的代码对 target 没有任何引用,这个 target 也不会被回收,最终可能导致内存溢出。

activeEffect

activeEffect 变量用来维护当前正在执行的副作用

shouldTrack

shouldTrack 变量用来标识是否开启依赖搜集,只有 shouldTrack 的值为 true 时,才进行依赖收集,即将副作用函数添加到依赖集合中。

副作用的实现

effect 函数

effect API 用来创建一个副作用函数,接受两个参数,分别是用户自定义的fn函数和options 选项。源码如下所示:

// packages/reactivity/src/effect.ts

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // 当传入的 fn 中存在 effect 副作用时,将这个副作用的原始函数赋值给 fn
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }

  // 创建一个副作用
  const _effect = new ReactiveEffect(fn)

  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }

//   如果不是延迟执行的,则立即执行一次副作用函数
  if (!options || !options.lazy) {
    _effect.run()
  }
  // 通过 bind 函数返回一个新的副作用函数
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  // 将副作用添加到新的副作用函数上
  runner.effect = _effect
  // 返回这个新的副作用函数
  return runner
}

由上面的代码可以知道,当传入的参数 fn 中存在 effect 副作用时,将这个副作用的原始函数赋值给 fn。然后调用 ReactiveEffect 类创建一个封装后的副作用函数。

在有些场景下,我们不希望 effect 立即执行,而是希望它在需要的时候才执行,我们可以通过在 options 中添加 lazy 属性来达到目的。在 effect 函数源码中,判断 options.lazy 选项的值,当值为true 时,则不立即执行副作用函数,从而实现懒执行的 effect。

接着通过 bind 函数返回一个新的副作用函数runner,这个新函数的this被指定为 _effect,并将 _effect 添加到这个新副作用函数的 effect 属性上,最后返回这个新副作用函数。

由于 effect API 返回的是封装后的副作用函数,原始的副作用函数存储在封装后的副作用函数的effect属性上,因此如果想要获取用户传入的副作用函数,需要通过 fn.effect.fn 来获取。

在 effect 函数中调用了 ReactiveEffect 类创建副作用,接下来看看 ReactiveEffect 类的实现。

ReactiveEffect 类

// packages/reactivity/src/effect.ts

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined

  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T>
  /**
   * @internal
   */
  allowRecurse?: boolean

  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope
  ) {
    recordEffectScope(this, scope)
  }

  run() {
    // 如果 effect 已停用,返回原始副作用函数执行后的结果
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      // 创建一个新的副作用前将当前正在执行的副作用存储到新建的副作用的 parent 属性上,解决嵌套effect 的情况
      this.parent = activeEffect
      // 将创建的副作用设置为当前正则正在执行的副作用
      activeEffect = this
      // 将 shouldTrack 设置为 true,表示开启依赖收集
      shouldTrack = true

      trackOpBit = 1 << ++effectTrackDepth

      if (effectTrackDepth <= maxMarkerBits) {
        // 初始化依赖
        initDepMarkers(this)
      } else {
        // 清除依赖
        cleanupEffect(this)
      }
    //   返回原始副作用函数执行后的结果
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }

      trackOpBit = 1 << --effectTrackDepth

      // 重置当前正在执行的副作用
      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined
    }
  }
  // 停止(清除) effect
  stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

在 ReactiveEffect 类中,定义了一个 run 方法,这个 run 方法就是创建副作用时实际运行方法。每次派发更新时,都会执行这个run方法,从而更新值。

全局变量 activeEffect 用来维护当前正在执行的副作用,当存在嵌套渲染组件的时候,依赖收集后,副作用函数会被覆盖,即 activeEffect 存储的副作用函数在嵌套 effect 的时候会被内层的副作用函数覆盖。为了解决这个问题,在 run 方法中,将当前正在执行的副作用activeEffect保存到新建的副作用的 parent 属性上,然后再将新建的副作用设置为当前正在执行的副作用。在新建的副作用执行完毕后,再将存储到 parent 属性的副作用重新设置为当前正在执行的副作用。

在 ReactiveEffect 类中,还定义了一个 stop 方法,该方法用来停止并清除当前正在执行的副作用。

track 收集依赖

当使用代理对象访问对象的属性时,就会触发代理对象的 get 拦截函数执行,如下面的代码所示:

const obj = { foo: 1 }

const p = new Proxy(obj, {
  get(target, key, receiver) {
    track(target, key)
    return Reflect.get(target, key, receiver)
  }
}) 

p.foo

在上面的代码中,通过代理对象p 访问 foo 属性,便会触发 get 拦截函数的执行,此时就在 get 拦截函数中调用 track 函数进行依赖收集。源码中 get 拦截函数的解析可阅读《Vue3 源码解读之非原始值的响应式原理》一文中的「访问属性的拦截」小节。

下面,我们来看看 track 函数的实现。

track 函数

// packages/reactivity/src/effect.ts

// 收集依赖
export function track(target: object, type: TrackOpTypes, key: unknown) {
    // 如果开启了依赖收集并且有正在执行的副作用,则收集依赖
  if (shouldTrack && activeEffect) {
    // 在 targetMap 中获取对应的 target 的依赖集合
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 如果 target 不在 targetMap 中,则加入,并初始化 value 为 new Map()
      targetMap.set(target, (depsMap = new Map()))
    }
    // 从依赖集合中获取对应的 key 的依赖
    let dep = depsMap.get(key)
    if (!dep) {
      // 如果 key 不存在,将这个 key 作为依赖收集起来,并初始化 value 为 new Set()
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    trackEffects(dep, eventInfo)
  }
}

在 track 函数中,通过一个 if 语句判断是否进行依赖收集,只有当 shouldTrack 为 true 并且存在 activeEffect,即开启了依赖收集并且存在正在执行的副作用时,才进行依赖收集。

然后通过 target 对象从 targetMap 中尝试获取对应 target 的依赖集合depsMap,如果 targetMap 中不存在当前target的依赖集合,则将当前 target 添加进 targetMap 中,并将 targetMap 的 value 初始化为 new Map()。

// 在 targetMap 中获取对应的 target 的依赖集合
let depsMap = targetMap.get(target)
if (!depsMap) {
  // 如果 target 不在 targetMap 中,则加入,并初始化 value 为 new Map()
  targetMap.set(target, (depsMap = new Map()))
}

接着根据target中被读取的 key,从依赖集合depsMap中获取对应 key 的依赖,如果依赖不存在,则将这个 key 的依赖收集到依赖集合depsMap中,并将依赖初始化为 new Set()。

// 从依赖集合中获取对应的 key 的依赖
let dep = depsMap.get(key)
if (!dep) {
  // 如果 key 不存在,将这个 key 作为依赖收集起来,并初始化 value 为 new Set()
  depsMap.set(key, (dep = createDep()))
}

最后调用 trackEffects 函数,将副作用函数收集到依赖集合depsMap中。

const eventInfo = __DEV__
  ? { effect: activeEffect, target, type, key }
  : undefined

trackEffects(dep, eventInfo)

trackEffects 函数

// 收集副作用函数
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    // 如果依赖中并不存当前的 effect 副作用函数
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    // 将当前的副作用函数收集进依赖中
    dep.add(activeEffect!)
    // 并在当前副作用函数的 deps 属性中记录该依赖
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}

在 trackEffects 函数中,检查当前正在执行的副作用函数 activeEffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。

trigger 派发更新

当对属性进行赋值时,会触发代理对象的 set 拦截函数执行,如下面的代码所示:

const obj = { foo: 1 }

const p = new Proxy(obj, {
  // 拦截设置操作
  set(target, key, newVal, receiver){
    // 如果属性不存在,则说明是在添加新属性,否则设置已有属性
    const type = Object.prototype.hasOwnProperty.call(target,key) ?  'SET' : 'ADD'

    // 设置属性值
    const res = Reflect.set(target, key, newVal, receiver)
    // 把副作用函数从桶里取出并执行,将 type 作为第三个参数传递给 trigger 函数
    trigger(target,key,type)

    return res
  }

  // 省略其他拦截函数
})

p.foo = 2

在上面的代码中,通过代理对象p 访问 foo 属性,便会触发 set 拦截函数的执行,此时就在 set 拦截函数中调用 trigger 函数中派发更新。源码中 set 拦截函数的解析可阅读《Vue3 源码解读之非原始值的响应式原理》一文中的「设置属性操作的拦截」小节。

下面,我们来看看 track 函数的实现。

trigger 函数

trigger 函数的源码如下:

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  // 该 target 从未被追踪,则不继续执行
  if (!depsMap) {
    // never been tracked
    return
  }

  // 存放所有需要派发更新的副作用函数
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 当需要清除依赖时,将当前 target 的依赖全部传入
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    // 处理数组的特殊情况
    depsMap.forEach((dep, key) => {
      // 如果对应的长度, 有依赖收集需要更新
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    // 在 SET | ADD | DELETE 的情况,添加当前 key 的依赖
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            // 操作类型为 ADD 时触发Map 数据结构的 keys 方法的副作用函数重新执行
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            // 操作类型为 DELETE 时触发Map 数据结构的 keys 方法的副作用函数重新执行
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    // 将需要执行的副作用函数收集到 effects 数组中
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

在 trigger 函数中,首先检查当前 target 是否有被追踪,如果从未被追踪过,即target的依赖未被收集,则不需要执行派发更新,直接返回即可。

const depsMap = targetMap.get(target)
// 该 target 从未被追踪,则不继续执行
if (!depsMap) {
  // never been tracked
  return
}

接着创建一个 Set 类型的 deps 集合,用来存储当前target的这个 key 所有需要执行派发更新的副作用函数。

// 存放所有需要派发更新的副作用函数
let deps: (Dep | undefined)[] = []

接下来就根据操作类型type 和 key 来收集需要执行派发更新的副作用函数。

如果操作类型是 TriggerOpTypes.CLEAR ,那么表示需要清除所有依赖,将当前target的所有副作用函数添加到 deps 集合中。

if (type === TriggerOpTypes.CLEAR) {
  // collection being cleared
  // trigger all effects for target
  // 当需要清除依赖时,将当前 target 的依赖全部传入
  deps = [...depsMap.values()]
}

如果操作目标是数组,并且修改了数组的 length 属性,需要把与 length 属性相关联的副作用函数以及索引值大于或等于新的 length 值元素的相关联的副作用函数从 depsMap 中取出并添加到 deps 集合中。

else if (key === 'length' && isArray(target)) {
  // 如果操作目标是数组,并且修改了数组的 length 属性
  depsMap.forEach((dep, key) => {
    // 对于索引大于或等于新的 length 值的元素,
    // 需要把所有相关联的副作用函数取出并添加到 deps 中执行
    if (key === 'length' || key >= (newValue as number)) {
      deps.push(dep)
    }
  })
} 

如果当前的 key 不为 undefined,则将与当前key相关联的副作用函数添加到 deps 集合中。注意这里的判断条件 void 0,是通过 void 运算符的形式表示 undefined 。

if (key !== void 0) {
  deps.push(depsMap.get(key))
}

接下来通过 Switch 语句来收集操作类型为 ADD、DELETE、SET 时与 ITERATE_KEY 和 MAP_KEY_ITERATE_KEY 相关联的副作用函数。

// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
  case TriggerOpTypes.ADD:
    if (!isArray(target)) {
      deps.push(depsMap.get(ITERATE_KEY))
      if (isMap(target)) {
        // 操作类型为 ADD 时触发Map 数据结构的 keys 方法的副作用函数重新执行
        deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
      }
    } else if (isIntegerKey(key)) {
      // new index added to array -> length changes
      deps.push(depsMap.get('length'))
    }
    break
  case TriggerOpTypes.DELETE:
    if (!isArray(target)) {
      deps.push(depsMap.get(ITERATE_KEY))
      if (isMap(target)) {
        // 操作类型为 DELETE 时触发Map 数据结构的 keys 方法的副作用函数重新执行
        deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
      }
    }
    break
  case TriggerOpTypes.SET:
    if (isMap(target)) {
      deps.push(depsMap.get(ITERATE_KEY))
    }
    break
}

最后调用 triggerEffects 函数,传入收集的副作用函数,执行派发更新。

const eventInfo = __DEV__
  ? { target, type, key, newValue, oldValue, oldTarget }
  : undefined

if (deps.length === 1) {
  if (deps[0]) {
    if (__DEV__) {
      triggerEffects(deps[0], eventInfo)
    } else {
      triggerEffects(deps[0])
    }
  }
} else {
  const effects: ReactiveEffect[] = []
  // 将需要执行的副作用函数收集到 effects 数组中
  for (const dep of deps) {
    if (dep) {
      effects.push(...dep)
    }
  }
  if (__DEV__) {
    triggerEffects(createDep(effects), eventInfo)
  } else {
    triggerEffects(createDep(effects))
  }
}

triggerEffects 函数

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  // 遍历需要执行的副作用函数集合
  for (const effect of isArray(dep) ? dep : [...dep]) {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      if (effect.scheduler) {
        // 如果一个副作用函数存在调度器,则调用该调度器
        effect.scheduler()
      } else {
        // 否则直接执行副作用函数
        effect.run()
      }
    }
  }
}

在 triggerEffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。

总结

本文深入分析了副作用的实现以及执行时机,并详细分析了用于存储副作用函数的targetMap的数据结构及其实现原理。还深入分析了依赖收集track函数以及派发更新 trigger 函数的实现。Vue 在追踪变化时,通过 track 函数收集依赖,即将副作用函数添加到 targetMap 中,通过 trigger 函数来执行对应的副作用函来完成更新。

到此这篇关于Vue3 源码解读之副作用函数与依赖收集的文章就介绍到这了,更多相关Vue3副作用函数与依赖收集内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Vue依赖收集引发的问题

    问题背景 在我们的项目中有一个可视化配置的模块,是通过go.js生成canvas来实现的.但是,我们发现这个模块在浏览器中经常会引起该tab页崩溃.开启chrome的任务管理器一看,进入该页面内存和cpu就会暴涨,内存经常会飙到700多M.但是我们的canvas实际的像素只有约500x500,根据一些粗略的计算,大概只占了1M的内存,这个计算过程可参考100*100的 canvas 占多少内存.那么我们这700M内存是哪里来的呢? 定位问题 我们可以使用chrome开发者工具来分析我们的调用栈.

  • 简单实现vue中的依赖收集与响应的方法

    开始 声明一个对象man,可以视为vue中的data let man = { height: 180, weight: 70, wealth: 100000000 } 添加Observer 作用在于将参数对象的属性变为响应式,只要对象的属性被读取或者被修改都能观察到.然后新建一个Observer实例,将man作为参数扔进去.这里的proxyData是将man的属性代理到以man为参数的Observer实例上去. class Observer { constructor(obj) { this.w

  • Vue 响应式系统依赖收集过程原理解析

    目录 背景 目标 源码解读 入口函数:observe class Observer Observe 如何处理数组 Observe 如何处理对象 class Dep Dep.target class Watcher Watcher 的应用 何时触发依赖收集? 数据变化时,如何进行更新? 总结 参考资料 背景 在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) ,它的定义在 src/core/instance/state.js 中.在初始化 data 和 pro

  • vue实现双向绑定和依赖收集遇到的坑

    在掘金上买了一个关于解读vue源码的小册,因为是付费的,所以还比较放心 在小册里看到了关于vue双向绑定和依赖收集的部分,总感觉有些怪怪的,然后就自己跟着敲了一遍. 敲完后,发现完全无法运行,  坑啊,  写书人完全没有测试过. 然后自己完善代码, 越写越发现坑, 问题有些大...... 最后自己重新实现了一遍,代码较多. 用到观察订阅者模式实现依赖收集, Object.defineProperty() 实现双向绑定 /* 自己写的代码, 实现vue的双向绑定和依赖收集 场景: 多个子组件用到父

  • Vue3 源码解读之副作用函数与依赖收集

    目录 副作用函数 副作用函数的全局变量 targetMap targetMap 为什么使用 WeakMap activeEffect shouldTrack 副作用的实现 effect 函数 ReactiveEffect 类 track 收集依赖 track 函数 trackEffects 函数 trigger 派发更新 trigger 函数 triggerEffects 函数 总结 版本:3.2.31 副作用函数 副作用函数是指会产生副作用的函数,如下面的代码所示: function effe

  • Vue3 源码解读之 Teleport 组件使用示例

    目录 Teleport 组件解决的问题 Teleport 组件的基本结构 Teleport 组件 process 函数 Teleport 组件的挂载 Teleport 组件的更新 moveTeleport 移动Teleport 组件 hydrateTeleport 服务端渲染 Teleport 组件 总结 Teleport 组件解决的问题 版本:3.2.31 如果要实现一个 “蒙层” 的功能,并且该 “蒙层” 可以遮挡页面上的所有元素,通常情况下我们会选择直接在 标签下渲染 “蒙层” 内容.如果

  • Vue3 源码解读静态提升详解

    目录 什么是静态提升 transform 转换器 hoistStatic 静态提升 walk 函数 walk 函数流程图 总结 什么是静态提升 静态提升是Vue3编译优化中的其中一个优化点.所谓的静态提升,就是指在编译器编译的过程中,将一些静态的节点或属性提升到渲染函数之外.下面,我们通过一个例子来深入理解什么是静态提升. 假设我们有如下模板: <div> <p>static text</p> <p>{{ title }}</p> </di

  • vue3 源码解读之 time slicing的使用方法

    今天给大家带来一篇源码解析的文章,emm 是关于 vue3 的,vue3 源码放出后,已经有很多文章来分析它的源码,我觉得很快又要烂大街了,哈哈 不过今天我要解析的部分是已经被废除的 time slicing 部分,这部分源码曾经出现在 vue conf 2018 的视频中,但是源码已经被移除掉了,之后可能也不会有人关注,所以应该不会烂大街 打包 阅读源码之前,需要先进行打包,打包出一份干净可调试的文件很重要 vue3 使用的 rollup 进行打包,我们需要先对它进行改造 import cle

  • Vue3源码解析watch函数实例

    目录 引言 一.watch参数类型 1. 选项options 2. 回调cb 3. 数据源source 二.watch函数 三.watch的核心:doWatch 函数 引言 想起上次面试,问了个古老的问题:watch和computed的区别.多少有点感慨,现在已经很少见这种耳熟能详的问题了,网络上八股文不少.今天,我更想分享一下从源码的层面来区别这八竿子打不着的两者.本篇针对watch做分析,下一篇分析computed. 一.watch参数类型 我们知道,vue3里的watch接收三个参数:侦听

  • Vue 2源码解读$mount函数原理

    目录 1. $mount 函数来源 2. runtime 运行时的 $mount 函数 2.1 mountComponent 函数 2.2 _update 函数(首次渲染) 3. runtime-with-compiler 的 $mount 函数 4. runtime 对 Vue 构造函数的其他修改 1. $mount 函数来源 上一节虽然直接从 core 目录下找到了 Vue 的构造函数定义,但是缺少 $mount 方法.所以直接从开发过程中使用的 vue.esm.js 找到对应的源码入口.

  • Vue3源码分析侦听器watch的实现原理

    目录 watch 的本质 watch 的函数签名 侦听多个源 侦听单一源 watch 的实现 watch 函数 source 参数 cb 参数 options 参数 doWatch 函数 doWatch 函数签名 初始化变量 递归读取响应式数据 定义清除副作用函数 封装 scheduler 调度函数 设置 job 的 allowRecurse 属性 flush 选项指定回调函数的执行时机 创建副作用函数 执行副作用函数 返回匿名函数,停止侦听 总结 watch 的本质 所谓的watch,其本质就

  • Vue3源码分析组件挂载初始化props与slots

    目录 前情提要 初始化组件 (1).setupComponent (2).initProps (3).initSlots 额外内容 总结 前情提要 上文我们分析了挂载组件主要调用了三个函数: createComponentInstance(创建组件实例).setupComponent(初始化组件).setupRenderEffect(更新副作用).并且上一节中我们已经详细讲解了组件实例上的所有属性,还包括emit.provide等的实现.本文我们将继续介绍组件挂载流程中的初始化组件. 本文主要内

  • Vue3源码通过render patch 了解diff

    目录 引言 render patch processText processCommontNode mountStaticNode 和 patchStaticNode processFragment patchBlockChildren patchChildren patchKeyedChildren patchUnkeyedChildren mountChildren unmountChildren move processElement mountElement patchElement p

  • Vue3源码分析reactivity实现方法示例

    目录 深入分析对于map.set.weakMap.weakSet的响应式拦截 (1).mutableInstrumentations (2).shallowInstrumentations (3).readonlyInstrumentations (4).shallowReadonlyInstrumentations ref.computed等方法的实现 (1).ref与shallowRef源码解析 (2).toRefs (4).computed (5)其他api源码 最后总结: 深入分析对于m

随机推荐