import Watcher from 'core/observer/watcher' import { warn } from 'core/util' export let activeEffectScope: EffectScope | undefined export class EffectScope { /** * @internal */ active = true /** * @internal */ effects: Watcher[] = [] /** * @internal */ cleanups: (() => void)[] = [] /** * @internal */ parent: EffectScope | undefined /** * record undetached scopes * @internal */ scopes: EffectScope[] | undefined /** * indicates this being a component root scope * @internal */ _vm?: boolean /** * track a child scope's index in its parent's scopes array for optimized * removal */ private index: number | undefined constructor(public detached = false) { this.parent = activeEffectScope if (!detached && activeEffectScope) { this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push( this ) - 1 } } run(fn: () => T): T | undefined { if (this.active) { const currentEffectScope = activeEffectScope try { activeEffectScope = this return fn() } finally { activeEffectScope = currentEffectScope } } else if (__DEV__) { warn(`cannot run an inactive effect scope.`) } } /** * This should only be called on non-detached scopes * @internal */ on() { activeEffectScope = this } /** * This should only be called on non-detached scopes * @internal */ off() { activeEffectScope = this.parent } stop(fromParent?: boolean) { if (this.active) { let i, l for (i = 0, l = this.effects.length; i < l; i++) { this.effects[i].teardown() } for (i = 0, l = this.cleanups.length; i < l; i++) { this.cleanups[i]() } if (this.scopes) { for (i = 0, l = this.scopes.length; i < l; i++) { this.scopes[i].stop(true) } } // nested scope, dereference from parent to avoid memory leaks if (!this.detached && this.parent && !fromParent) { // optimized O(1) removal const last = this.parent.scopes!.pop() if (last && last !== this) { this.parent.scopes![this.index!] = last last.index = this.index! } } this.parent = undefined this.active = false } } } export function effectScope(detached?: boolean) { return new EffectScope(detached) } /** * @internal */ export function recordEffectScope( effect: Watcher, scope: EffectScope | undefined = activeEffectScope ) { if (scope && scope.active) { scope.effects.push(effect) } } export function getCurrentScope() { return activeEffectScope } export function onScopeDispose(fn: () => void) { if (activeEffectScope) { activeEffectScope.cleanups.push(fn) } else if (__DEV__) { warn( `onScopeDispose() is called when there is no active effect scope` + ` to be associated with.` ) } }