本文从 VSCode 的日志服务之一了解 VSCode 的事件分发和处理。阅读版本为官方当前最新代码的 master 分支。
AbstractLogService
我们已经知道,VSCode 启动后,会从 src/main.js
异步加载并运行 CodeMain
实例的 .main()
方法并开始启动第一个页面。但是,在 startup(args)
一开始执行时,会先创建一个 bufferLogService
,该服务用于避免在 Windows 下出现的并发访问 LOG 文件的问题 ( issue 41218 ) ,解决方式是先把产生的 LOG 信息缓冲,直到确认自己是唯一运行的实例再产生日志。
BufferLogService
继承了抽象类 AbstractLogService
,该类主要用于设置日志级别,同时,当日志级别发生变化时,发出 onDidChangeLogLevel
事件,通知监听该事件的对象。具体代码如下:
1 | export abstract class AbstractLogService extends Disposable { |
这里使用了 VSCode 中典型的事件处理模式:
1 | class AbstractLogService { |
这里 _register
主要用于资源回收步骤,不多说明。VSCode 中事件处理主要分为两部分:
- 定义一个分发器
Emitter
。 - 调用
Emitter
的 getterevent
得到向该Emitter
添加监听器的方法 (Event
类型 )。
添加监听器的方法类似下面这样:
1 | // 监听器的参数 level 的类型和分发器的泛型一致 |
触发事件使用分发器的 fire
方法:
1 | // 参数的类型和分发器的泛型一致 |
Emitter
和前面的过程相对应,一个 Emitter
有两个功能:
-
getter
event
:得到一个向Emitter
添加listener
的函数。
该getter
在第一次调用时会生成函数_event
,该函数是Event
类型,其会将传入的监听器加入到监听器列表_listeners
,相关代码如下:1
2
3
4
5
6
7
8
9
10
11get event(): Event<T> {
if (!this._event) {
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => {
// ...
const remove = this._listeners.push(!thisArgs ? listener : [listener, thisArgs]);
// ...
};
}
return this._event;
} -
fire
:触发挂载该Emitter
上的监听器们。
fire
则直接循环触发_listeners
中的所有监听器: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
26fire(event: T): void {
if (this._listeners) {
if (!this._deliveryQueue) {
this._deliveryQueue = new LinkedList();
}
// 先将监听器和参数打包
for (let listener of this._listeners) {
this._deliveryQueue.push([listener, event]);
}
// 逐一触发所有监听器
while (this._deliveryQueue.size > 0) {
const [listener, event] = this._deliveryQueue.shift()!;
try {
if (typeof listener === 'function') {
// 直接是一个函数
listener.call(undefined, event);
} else {
// 包含参数
listener[0].call(listener[1], event);
}
} catch (e) {
onUnexpectedError(e);
}
}
}
}
BufferLogService
BufferLogService
是具备缓冲的 LOG 服务,其首先提供了产生不同级别 LOG 的函数 trace
、debug
、info
、warn
、error
、critical
。他们都是对私有函数 _log
的封装,该函数不会立即打印 LOG 信息,直到设置了内部对象 _logger
,这也是这个 logger
的特点:
1 | private _log(level: LogLevel, ...args: any[]): void { |
除此之外,该服务提供一个 setter
,用于设置用于输出 LOG 的 logger, 设置后,也会立即进行 LOG 操作,把之前缓冲的部分输出:
1 | set logger(logger: ILogService) { |
小结
本文介绍了 VSCode 启动中的一个服务 BufferLogService
的相关功能,并以此为线索总结了 VSCode 中的事件机制的大致模式之一。总的来说,VSCode 中会以下面方式进行一些事件处理:
- 定义一个分发器
Emitter
。 - 调用
Emitter
的 getterevent
得到向该Emitter
添加监听器的方法 (Event
类型 )。 - 调用
Emitter
的fire
触发相关监听器。