petite-vue源碼剖析-逐行解讀@vue-reactivity之effect

當我們通過effect將副函數向響應上下文註冊後,副作用函數內訪問響應式對象時即會自動收集依賴,並在相應的響應式屬性發生變化後,自動觸發副作用函數的執行。

// ./effect.ts

export funciton effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  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)
  }
  // 默認是馬上執行副作用函數收集依賴,但可通過lazy屬性延遲副作用函數的執行,延遲依賴收集。
  if (!options || !options.lazy) {
    _effect.run()
  }
  // 類型爲ReactiveEffectRunner的runner是一個綁定this的函數
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

effect函數的代碼十分少,主要流程是

  1. 將基於副作用函數構建ReactiveEffect對象
  2. 若爲默認模式則馬上調用ReactiveEffect對象的run方法執行副作用函數。

不過這裏我們有幾個疑問

  1. ReactiveEffectRunner是什麼?
  2. ReactiveEffect生成的對象究竟是什麼?顯然ReactiveEffectrun方法纔是夢開始的地方,到底它做了些什麼?
  3. 針對配置項scoperecordEffectScope的作用?

ReactiveEffectRunner是什麼?

// ./effect.ts

// ReactiveEffectRunner是一個函數,而且有一個名爲effect的屬性且其類型爲RectiveEffect
export interface ReactiveEffectRunner<T = any> {
  (): T
  effect: ReactiveEffect
}

ReactiveEffect生成的對象究竟是什麼?

// 用於記錄位於響應上下文中的effect嵌套層次數
let effectTrackDepth = 0
// 二進制位,每一位用於標識當前effect嵌套層級的依賴收集的啓用狀態
export left trackOpBit = 1
// 表示最大標記的位數
const maxMarkerBits = 30

const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined

export class ReactiveEffect<T = any> {
  // 用於標識副作用函數是否位於響應式上下文中被執行
  active = true
  // 副作用函數持有它所在的所有依賴集合的引用,用於從這些依賴集合刪除自身
  deps: Dep[] = []
  // 默認爲false,而true表示若副作用函數體內遇到`foo.bar += 1`則無限遞歸執行自身,直到爆棧
  allowRecurse?: boolean

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

  run() {
    /**
     * 若當前ReactiveEffect對象脫離響應式上下文,那麼其對應的副作用函數被執行時不會再收集依賴,並且其內部訪問的響應式對象發生變化時,也會自動觸發該副作用函數的執行
     */
    if (!this.active) {
      return this.fn()
    }
    // 若參與響應式上下文則需要先壓棧
    if (!effectStack.includes(this)) {
      try {
        // 壓棧的同時必須將當前ReactiveEffect對象設置爲活躍,即程序棧中當前棧幀的意義。
        effectStack.push(activeEffect = this)
        enableTracking()

        trackOpBit = 1 << ++effectTrackDepth

        if (effectTrackDepth <= maxMarkerBits) {
          // 標記已跟蹤過的依賴
          initDepMarkers(this)
        }
        else {
          cleanupEffect(this)
        }

        return this.fn()
      }
      finally {
        if (effectTrackDepth <= maxMarkerBits) {
          /**
           * 用於對曾經跟蹤過,但本次副作用函數執行時沒有跟蹤的依賴,採取刪除操作。
           * 即,新跟蹤的 和 本輪跟蹤過的都會被保留。
           */
          finalizeDepMarkers(this)
        }

        trackOpBit = 1 << --effectTrackDepth
        resetTracking()
        // 最後當然彈棧,把控制權交還給上一個棧幀咯
        effectStack.pop()
        const n = effectStack.length
        activeEffect = n > 0 ? effectStack[n - 1] : undefined  
      }
    }

    /**
     * 讓當前ReactiveEffect對象脫離響應式上下文,請記住這是一去不回頭的操作哦!
     */ 
    stop() {
      if (this.active) {
        cleanupEffect(this)
        this.active = false
      }
    }
  }
}

爲應對嵌套effect內部將當前位於響應上下文的ReactiveEffect對象壓入棧結構effectStack: ReactiveEffect[],噹噹前副作用函數執行後再彈出棧。另外,雖然我們通過effect函數將副作用函數註冊到響應上下文中,但我們仍能通過調用stop方法讓其脫離響應上下文。

function cleanupEffect(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    // 將當前ReactiveEffect對象從它依賴的響應式屬性的所有Deps中刪除自己,那麼當這些響應式屬性發生變化時則不會遍歷到當前的ReactiveEffect對象
    for (let i = 0; i < deps.length; ++i) {
      deps[i].delete(effect)
    }
    // 當前ReactiveEffect對象不再參與任何響應了
    deps.length = 0
  }
}

在執行副作用函數前和執行後我們會看到分別調用了enableTracking()resetTracking()函數,它們分別表示enableTracking()執行後的代碼將啓用依賴收集,resetTracking()則表示後面的代碼將在恢復之前是否收集依賴的開關執行下去。要理解它們必須結合pauseTracking()和實際場景說明:

let shouldTrack = true
const trackStack: boolean[] = []

export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}

export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}

export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}

假設我們如下場景

const values = reactive([1,2,3])
effect(() => {
  values.push(1)
})

由於在執行push時內部會訪問代理對象的length屬性,並修改length值,因此會導致不斷執行該副作用函數直到拋出異常Uncaught RangeError: Maximum call stack size exceeded,就是和(function error(){ error() })()不斷調用自身導致棧空間不足一樣的。而@vue/reactivity是採用如下方式處理

;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
    pauseTracking()
    const res = (toRaw(this) as any)[key].apply(this, args)
    resetTracking()
    return res
  }
})

即通過pauseTracking()暫停push內部的發生意外的依賴收集,即push僅僅會觸發以其他形式依賴length屬性的副作用函數執行。然後通過resetTracking()恢復到之前的跟蹤狀態。

最後在執行副作用函數return this.fn()前,居然有幾句難以理解的語句

try {
  trackOpBit = 1 << ++effectTrackDepth

  if (effectTrackDepth <= maxMarkerBits) {
    initDepMarkers(this)
  }
  else {
    cleanupEffect(this)
  }

  return this.fn()
}
finally {
  if (effectTrackDepth <= maxMarkerBits) {
    finalizeDepMarkers(this)
  }

  trackOpBit = 1 << --effectTrackDepth
}

我們可以將其簡化爲

try {
  cleanupEffect(this)
  
  return this.fn()
}
finally {}

爲什麼在執行副作用函數前需要清理所有依賴呢?我們可以考慮一下如下的情況:

const state = reactive({ show: true, values: [1,2,3] })
effect(() => {
  if (state.show) {
    console.log(state.values)
  }
})
setTimeout(() => {
  state.values.push(4)
}, 5000)

setTimeout(() => {
  state.show = false
}, 10000)

setTimeout(() => {
  state.values.push(5)
}, 15000)

一開始的時候副作用函數將同時依賴showvalues,5秒後向values追加新值副作用函數馬上被觸發重新執行,再過10秒後show轉變爲false,那麼if(state.show)無論如何運算都不成立,此時再對values追加新值若副作用函數再次被觸發顯然除了佔用系統資源外,別無用處。
因此,在副作用函數執行前都會先清理所有依賴(cleanupEffect的作用),然後在執行時重新收集。

面對上述情況,先清理所有依賴再重新收集是必須的,但如下情況,這種清理工作反而增加無謂的性能消耗

const state = reactive({ show: true, values: [1,2,3] })
effect(() => {
  console.log(state.values)
})

@vue/reactivity給我們展示了一個非常優秀的處理方式,那麼就是通過標識每個依賴集合的狀態(新依賴和已經被收集過),並對新依賴和已經被收集過兩個標識進行對比篩選出已被刪除的依賴項。

優化無用依賴清理算法

export type Dep = Set<ReactiveEffect> & Trackedmarkers

type TrackedMarkers = {
  /**
   * wasTracked的縮寫,採用二進制格式,每一位表示不同effect嵌套層級中,該依賴是否已被跟蹤過(即在上一輪副作用函數執行時已經被訪問過)
   */ 
  w: number
  /**
   * newTracked的縮寫,採用二進制格式,每一位表示不同effect嵌套層級中,該依賴是否爲新增(即在本輪副作用函數執行中被訪問過)
   */ 
  n: number
}

export const createDep = (effects) => {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  // 雖然TrackedMarkers標識是位於響應式對象屬性的依賴集合上,但它每一位僅用於表示當前執行的副作用函數是否曾經訪問和正在訪問該響應式對象屬性
  dep.w = 0
  dep.n = 0

  return dep
}

export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0

export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0

/**
 * 將當前副作用函數的依賴標記爲 `已經被收集`
 */
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].w |= trackOpBit
    }
  }
}

/**
 * 用於對曾經跟蹤過,但本次副作用函數執行時沒有跟蹤的依賴,採取刪除操作。
 * 即,新跟蹤的 和 本輪跟蹤過的都會被保留。
 */
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
  const { deps } = effect
  if (deps.length) {
    let ptr = 0
    for (let i = 0; i < deps.length; i++) {
      const dep = deps[i]
      if (wasTracked(dep) && !newTracked(dep)) {
        // 對於曾經跟蹤過,但本次副作用函數執行時沒有跟蹤的依賴,採取刪除操作。
        dep.delete(effect)
      }
      else {
        // 縮小依賴集合的大小
        deps[ptr++] = dep
      }
      // 將w和n中對應的嵌套層級的二進制位置零,如果缺少這步後續副作用函數重新執行時則無法重新收集依賴。
      dep.w &= ~trackOpBit
      dep.n &= ~trackOpBit
    }
    // 縮小依賴集合的大小
    deps.length = ptr
  }
}
// 在位於響應式上下文執行的副作用函數內,訪問響應式對象屬性,將通過track收集依賴
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!isTracking()) {
    return
  }

  // targetMap用於存儲響應式對象-對象屬性的鍵值對
  // depsMap用於存儲對象屬性-副作用函數集合的鍵值對
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    target.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = createDep()))
  }

  trackEffects(dep)
}

// 收集依賴
export function trackEffects(
  dep: Dep
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    // 如果本輪副作用函數執行過程中已經訪問並收集過,則不用再收集該依賴
    if (!newTracked(dep)) {
      dep.n |= trackOpBit
      shouldTrack = !wasTracked(dep)
    }
  }
  else {
    // 對於全面清理的情況,如果當前副作用函數對應的ReactiveEffect對象不在依賴集合中,則標記爲true
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
  }
}

單單從代碼實現角度能難理解這個優化方式,不如我們從實際的例子出發吧!

const runAync = fn => setTimeout(fn, 1000)

const state = reactive({ show: true, values: [1,2,3] })
// 1
effect(() => {
  if (state.show) {
    console.log(state.values)
  }
})

// 2
runAync(() => {
  state.values.push(4)
})

// 3
runAync(() => {
  state.show = false
})
  1. 首次執行副作用函數
    a. effectTrackDepth爲0,因此1 << ++effectTrackDepth得到的effectTrackDepthtrackOpBit均爲1,但由於此時副作用函數還沒有收集依賴,因此initDepMarkers函數沒有任何效果;
    b. 訪問state.show時由於之前沒有收集過響應式對象stateshow屬性,因此會調用createDep創建wn均爲0的依賴集合,並調用trackEffects發現newTracked(dep)爲未跟蹤過,則將n設置爲1,然後開始收集依賴;
    c. 訪問state.values會重複第2步的操作;
    d. 由於state.showstate.values都是新跟蹤的(n爲1),因此在finalizeDepMarkers處理後仍然將副作用函數保留在這兩個屬性對應的依賴集合中。
  2. 執行state.values.push(4)觸發副作用函數變化
    a. effectTrackDepth爲0,因此1 << ++effectTrackDepth得到的effectTrackDepthtrackOpBit均爲1,此時副作用函數已經收集過依賴,因此initDepMarkers將該副作用函數所在的依賴集合都都標記爲已收集過(w爲1);
    b. 訪問state.show時會調用trackEffects發現newTracked(dep)爲未跟蹤過(在finalizeDepMarkers中已被置零),則將n設置爲1,然後開始收集依賴;
    c. 訪問state.values會重複第2步的操作;
    d. 由於state.showstate.values都是新跟蹤的(n爲1),因此在finalizeDepMarkers處理後仍然將副作用函數保留在這兩個屬性對應的依賴集合中。
  3. 執行state.show = false觸發副作用函數變化
    a. effectTrackDepth爲0,因此1 << ++effectTrackDepth得到的effectTrackDepthtrackOpBit均爲1,此時副作用函數已經收集過依賴,因此initDepMarkers將該副作用函數所在的依賴集合都都標記爲已收集過(w爲1);
    b. 訪問state.show時會調用trackEffects發現newTracked(dep)爲未跟蹤過(在finalizeDepMarkers中已被置零),則將n設置爲1,然後開始收集依賴;
    c. 由於state.values沒有標記爲新跟蹤的(n爲0),因此在finalizeDepMarkers處理後會將副作用函數從state.values對應的依賴集合中移除,僅保留在state.values對應的依賴集合中。

到這裏,我想大家已經對這個優化有更深的理解了。那麼接下來的問題自然而然就是爲什麼要硬編碼將優化算法啓動的嵌套層級設置爲maxMarkerBits = 30

SMI優化原理

首先maxMarkerBits = 30表示僅支持effect嵌套31層,註釋中描述該值是因爲想讓JavaScript影響使用SMI。那麼什麼是SMI呢?

由於ECMAScript標準約定number數字需要轉換爲64位雙精度浮點數處理,但所有數字都用64位存儲和處理是十分低效的,所以V8內部採用其它內存表示方式(如32位)然後向外提供64位表現的特性即可。其中數組合法索引範圍是[0, 2^32 - 2],V8引擎就是採用32位的方式來存儲這些合法的下標數字。另外,所有在[0, 2^32 - 2]內的數字都會優先使用32位二進制補碼的方式存儲。

針對32位有符號位範圍內的整型數字V8爲其定義了一種特殊的表示法SMI(非SMI的數字則被定義爲HeapNumber),而V8引擎針對SMI啓用特殊的優化:當使用SMI內的數字時,引擎不需要爲其分配專門的內存實體,並會啓用快速整型操作

對於非SMI的數字

let o = {
  x: 42, // SMI
  y: 4.2 // HeapNumber
}

內存結構爲HeapNumber{ value: 4.2, address: 1 }JSObject{ x: 42, y: 1 },由於x值類型爲SMI因此直接存儲在對象上,而y爲HeapNumber則需要分配一個獨立的內存空間存放,並通過指針讓對象的y屬性指向HeapNumber實例的內存空間。

然而在修改值時,然後x爲SMI所以可以原地修改內存中的值,而HeapNumber爲不可變,因此必須再分配一個新的內存空間存放新值,並修改o.y中的內存地址。那麼在沒有啓用Mutable HeapNumber時,如下代碼將產生1.11.21.33個臨時實例。

let o = { x: 1.1 }
for (let i = 0; i < 4; ++i) {
  o.x += 1;
}

SMI是帶符號位的,那麼實際存儲數字是31位,因此設置maxMarkerBits = 30且通過if (effectTrackDepth <= maxMarkerBits)判斷層級,即當effec嵌套到31層時不再使用無用依賴清理優化算法。而優化算法中採用的是二進制位對上一輪已收集和本輪收集的依賴進行比較,從而清理無用依賴。若nw值所佔位數超過31位則內部會採用HeapNumber存儲,那麼在位運算上性能將有所下降。

其實我們還看到若effectTrackDepth等於31時還會執行trackOpBit = 1 << ++effectTrackDepth,這會導致trackOpBitSMI的存儲方式轉換爲HeapNumber,那是不是可以加個判斷修改成下面這樣呢!

const maxMarkerBit = 1 << 30

if (trackOpBit & maxMarkerBit !== 1) {
  trackOpBit = 1 << ++effectTrackDepth
}

副作用函數觸發器-trigger

由於在講解"優化無用依賴清理算法"時已經對track進行了剖析,因此現在我們直接分析trigger就好了。

export function trigger(
  target: object,
  // set, add, delete, clear
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // 該屬性沒有被任何副作用函數跟蹤過,所以直接返回就好了
    return
  }

  /**
   * 用於存儲將要被觸發的副作用函數。
   * 爲什麼不直接通過類似depsMap.values().forEach(fn => fn())執行副作用函數呢?
   * 那是因爲副作用函數執行時可能會刪除或增加depsMap.values()的元素,導致其中的副作用函數執行異常。
   * 因此用另一個變量存儲將要執行的副作用函數集合,那麼執行過程中修改的是depsMap.values()的元素,而正在遍歷執行的副作用函數集合結構是穩定的。
   */
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // 對象的所有屬性值清空,所有依賴該響應式對象的副作用函數都將被觸發
    deps = [...depsMap.values()]
  }
  else if (key === 'length' && isArray(target)) {
    // 若設置length屬性,那麼依賴length屬性和索引值大於等於新的length屬性值的元素的副作用函數都會被觸發
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  }
  else {
    // 將依賴該屬性的
    if (key !== void 0) {
      // 即使插入的是undefined也沒有關係
      deps.push(depsMap.get(key))
    }

    /**
     * 添加間接依賴的副作用函數
     * 1. 新增數組新值索引大於數組長度時,會導致數組容量被擴充,length屬性也會發生變化
     * 2. 新增或刪除Set/WeakSet/Map/WeakMap元素時,需要觸發依賴迭代器的副作用函數
     * 3. 新增或刪除Map/WeakMap元素時,需要觸發依賴鍵迭代器的副作用函數
     * 4. 設置Map/WeakMap元素的值時,需要觸發依賴迭代器的副作用函數
     */ 
    switch(type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          // 對於非數組,則觸發通過迭代器遍歷的副作用函數
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        else if (isIntegerKey(key)) {
          // 對數組插入新元素,則需要觸發依賴length的副作用函數
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          // 對於非數組,則觸發通過迭代器遍歷的副作用函數
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        // 對於Map/WeakMap需要觸發依賴迭代器的副作用函數
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
    }

    if (deps.length === 1) {
      // 過濾掉undefined
      if (deps[0]) {
        triggerEffects(deps[0])
      }
    }
    else {
      const effects: ReactiveEffect[] = []
      // 過濾掉undefined
      for (const dep of deps) {
        if (dep) {
          effects.push(...dep)
        }
      }
      triggerEffects(createDep(effects))
    }
  }
}

export function triggerEffects(
  dep: Dep | ReactiveEffect[]
) {
  for (const effect of isArray(dep) ? dep : [...dep]) {
    /**
     * 必須保證將要觸發的副作用函數(effect)不是當前運行的副作用函數(activeEffect),否則將嵌入無限遞歸。
     * 假設存在如下情況
     * let foo = reactive({ bar: 1 })
     * effect(() => {
     *   foo.bar = foo.bar + 1
     * })
     * 若沒有上述的保障,則將會不斷遞歸下去直接爆棧。
     * 
     * 假如ReactiveEffect對象的allowRecurse設置爲true,那麼表示不對上述問題作防禦。
     */ 
    if (effect !== activeEffect || effect.allowRecurse) {
      if (effect.scheduler) {
        // 若設置有調度器則調用調用器
        effect.scheduler()
      }
      else {
        // 立即執行副作用函數
        effect.run()
      }
    }
  }
}

調度器

在上一節的triggerEffects中我們看到默認採用同步方式執行副作用函數,若要同步執行數十個副作用函數那麼勢必會影響當前事件循環主邏輯的執行,這時就是調度器閃亮登場的時候了。我們回顧以下petite-vue中提供的調度器吧!

import { effect as rawEffect } from '@vue/reactivity'

const effect = (fn) => {
  const e: ReactiveEffectRunner = rawEffect(fn, {
    scheduler: () => queueJob(e)
  })
  return e
}
// ./scheduler.ts

let queued = false
const queue: Function[] = []
const p = Promise.resolve()

export const nextTick = (fn: () => void) => p.then(fn)

export const queueJob = (job: Function) => {
  if (!queue.includes(job)) queue.push(job)
  if (!queued) {
    queued = true
    nextTick(flushJobs)
  }
}

const flushJobs = () => {
  for (const job of queue) {
    job()
  }
  queue.length = 0
  queued = false
}

副作用函數壓入隊列中,並將遍歷隊列執行其中的副作用函數後清空隊列的flushJobs壓入micro queue。那麼當前事件循環主邏輯執行完後,JavaScript引擎將會執行micro queue中的所有任務。

什麼是EffectScope

Vue 3.2引入新的Effect scope API,可自動收集setup函數中創建的effectwatchcomputed等,當組件被銷燬時自動銷燬作用域(scope)和作用域下的這些實例(effectwatchcomputed等)。這個API主要是提供給插件或庫開發者們使用的,日常開發不需要用到它。

還記得petite-vue中的context嗎?當遇到v-ifv-for就會爲每個子分支創建新的block實例和新的context實例,而子分支下的所有ReactiveEffect實例都將統一被對應的context實例管理,當block實例被銷燬則會對對應的context實例下的ReactiveEffect實例統統銷燬。

block實例對應是DOM樹中動態的部分,可以大概對應上Vue組件,而context實例就是這裏的EffectScope對象了。

使用示例:

cosnt scope = effectScope()
scope.run(() => {
  const state = reactive({ value: 1 })
  effect(() => {
    console.log(state.value)
  })
})
scope.stop()

那麼effect生成的ReactiveEffect實例是如何和scope關聯呢?
那就是ReactiveEffect的構造函數中調用的recordEffectScope(this, scope)

export function recordEffectScope(
  effect: ReactiveEffect,
  scope?: EffectScope | null
) {
  // 默認將activeEffectScope和當前副作用函數綁定
  scope = scope || activeEffectScope
  if (scope && scope.active) {
    scope.effects.push(effect)
  }
}

總結

petite-vue中使用@vue/reactivity的部分算是剖析完成了,也許你會說@vue/reactivity可不止這些內容啊,這些內容我將會在後續的《vue-lit源碼剖析》中更詳盡的梳理分析,敬請期待。
下一篇我們將看看eval中是如何使用new Functionwith來構造JavaScript解析執行環境的。
尊重原創,轉載請註明來自:https://www.cnblogs.com/fsjohnhuang/p/16163888.html肥仔John

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章