Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • reactivity响应式

    • ref
    • reactive
    • 基础对象的代理
    • 数组代理的方法
    • 集合对象的代理
    • Reflect和Proxy详解
    • 依赖的收集与触发
    • EffectScope解析
      • 概述
        • 源码解析
        • 核心方法
        • 作用域控制
        • 作用域运行
        • 作用域激活
        • 辅助方法
    • ReactiveEffect类介绍
    • reactive响应式依赖的收集与触发监听
    • 批量更新实现
    • computed
    • 响应式中的watch实现
  • runtime-core运行时核心模块

  • runtime-dom运行时DOM模块

  • 5.18源码学习》
  • reactivity响应式
东流
2025-08-20
目录

EffectScope解析

# 概述

EffectScope是Vue3中一个响应式系统的辅助类,用于管理副作用(effect)的作用域。它可以帮助我们更好地组织和管理多个effect,便于一起停止或暂停以及恢复,避免了全局状态的污染和管理的复杂性。

每一个vue组件的实例上都会挂载一个EffectScope的实例scope,该挂载动作会在createComponentInstance中进行。在组件被激活挂载时,会调用scope.on()方法,将当前作用域设置为活动作用域;而在组件被卸载或者销毁时,会调用scope.stop()方法。

# 源码解析

let activeEffectScope;

class EffectScope {
    constructor(detached = false) {
        this.detached = detached; // 表示该作用域是否独立(不嵌套在父作用域中)
        this._active = true; //表示作用域是否激活
        this._on = 0; // 一个计数器,用于记录当前作用域被开启的次数(通过on方法)
        this.effects = []; // 存储属于该作用域的effect
        this.cleanups = []; // 存储清理函数,当作用域停止时会执行这些清理函数
        this._isPaused = false; // 表示作用域是否被暂停
        this.parent = undefined; //指向父作用域
        this.scopes = undefined; //存储嵌套的子作用域
        this.index = undefined; // 在父作用域的scopes数组中的索引
        this.prevScope = undefined; // 在调用 on 方法时,用于保存当前的activeEffectScope,以便在off时恢复,上一个作用域(用于作用域链)
        this.parent = activeEffectScope; //将父作用域指向激活的作用域
        if (!detached && activeEffectScope) {
          // 若该作用域不独立且当前存在激活的作用域,则将该作用域存放到激活作用域(父作用域)的子作用域的末尾,并设置该作用域的索引
            this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
                this
            ) - 1;
        }
    }
    
    // 暂停作用域
    pause() {
        if (this._active) {
            this._isPaused = true;
            let i, l;
            if (this.scopes) {
                for (i = 0, l = this.scopes.length; i < l; i++) {
                    this.scopes[i].pause();
                }
            }
            for (i = 0, l = this.effects.length; i < l; i++) {
                this.effects[i].pause();
            }
        }
    }
    // 恢复作用域
    resume() {
        if (this._active) {
            if (this._isPaused) {
                this._isPaused = false;
                let i, l;
                if (this.scopes) {
                    for (i = 0, l = this.scopes.length; i < l; i++) {
                        this.scopes[i].resume();
                    }
                }
                for (i = 0, l = this.effects.length; i < l; i++) {
                    this.effects[i].resume();
                }
            }
        }
    }
    
    // 在作用域内运行函数
    run(fn) {
        if (this._active) {
            const currentEffectScope = activeEffectScope;
            try {
                activeEffectScope = this;
                return fn();
            } finally {
                activeEffectScope = currentEffectScope;
            }
        }
    }
    // 开启作用域
    on() {
        if (++this._on === 1) {
            this.prevScope = activeEffectScope;
            activeEffectScope = this;
        }
    }
    // 关闭作用域
    off() {
        if (this._on > 0 && --this._on === 0) {
            activeEffectScope = this.prevScope;
            this.prevScope = void 0;
        }
    }
    // 停止作用域
    stop(fromParent) {
        if (this._active) {
            this._active = false;
            let i, l;
            for (i = 0, l = this.effects.length; i < l; i++) {
                this.effects[i].stop();
            }
            this.effects.length = 0;
            for (i = 0, l = this.cleanups.length; i < l; i++) {
                this.cleanups[i]();
            }
            this.cleanups.length = 0;
            if (this.scopes) {
                for (i = 0, l = this.scopes.length; i < l; i++) {
                    this.scopes[i].stop(true);
                }
                this.scopes.length = 0;
            }
            if (!this.detached && this.parent && !fromParent) {
                const last = this.parent.scopes.pop();
                if (last && last !== this) {
                    this.parent.scopes[this.index] = last;
                    last.index = this.index;
                }
            }
            this.parent = void 0;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

# 核心方法

核心方法主要分为三类:作用域控制(pause/resume/stop)、作用域运行(run)和作用域激活(on/off)

# 作用域控制
  • pause

pause方法就是用于暂停作用域及其所有子作用域和副作用。先是判断作用域是否处于激活状态,若是激活状态,则修改作用域的暂停状态this._isPaused,改为true,然后遍历子作用域和副作用数组,调用其stop方法实现暂停。

  • resume

resume方法用于暂停状态的作用域及其所有子作用域和副作用。和pause方法是相反的操作,会修改this.isPaused为false,遍历调用所有子作用域和副作用的resume方法。

  • stop

stop方法用于停止作用域:清理所有副作用和子作用域,并且从父作用域中移除自身。

stop方法会停止所有effect,执行所有cleanups,并递归停止所有子作用域。如果该作用域不是独立的且由副作用域,并且不是由副作用域触发的停止,则从副作用域的scopes数组中移除自己,并调整数组。

# 作用域运行
  • run

run方法用于在该作用域内运行函数,这样函数内创建的effect会自动注册到该作用域中。主要就是将该作用域切换为激活作用域,然后执行fn,最后恢复激活作用域为之前的。

# 作用域激活
  • on

on方法用于激活作用域,就是增加计数器,若计数器增加后等于 1,则将该作用域作为激活作用域。

  • off

off方法和on方法相对,用于关闭作用域,减少计数器,若计数器大于 0,且减少后等于0,则恢复激活作用域为前值。

通过on/off方法,可以临时切换当前激活的作用域,这样可以实现在on和off切换之间创建effect会注册到该作用域中。

# 辅助方法

和EffectScope类相关的三个辅助方法:effectScope、getCurrentScope和onScopeDispose。

  • effectScope

effectScope方法就是返回EffectScope的实例对象。

function effectScope(detached) {
  return new EffectScope(detached);
}
1
2
3
  • getCurrentScope

getCurrentScope方法用于获取当前活跃(激活)的作用域

function getCurrentScope() {
  return activeEffectScope;
}
1
2
3
  • onScopeDispose

onScopeDispose方法会判断若当前作用域存在,则将函数fn注入到作用域的清理数组cleanups中。

function onScopeDispose(fn, failSilently = false) {
  if (activeEffectScope) {
    activeEffectScope.cleanups.push(fn);
  }
}
1
2
3
4
5
编辑 (opens new window)
上次更新: 2025/09/05, 09:36:06
依赖的收集与触发
ReactiveEffect类介绍

← 依赖的收集与触发 ReactiveEffect类介绍→

最近更新
01
patchProp属性补丁解析
09-16
02
createApp创建实例解析
09-15
03
渲染器renderer源码解析
09-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式