vscode中的基本概念(一)

Disposable

  • Disposable 是一个非常重要和基础的概念的,它贯穿了整个 vscode 项目中,90% 的对象都是继承 Disposable,还有大量的实现 IDisposable 接口的对象。 Disposable 本身并没有做太多事情: 它是一个抽象类,提供了两个方法 _register (protected) 和 dispose (public), 可以通过 dispose 方法把 _register 注册的 listener (IDispsable 对象) 给全部销毁。其核心的工作就是将继承Disposable的对象管理起来,再其销毁的时候销毁掉这个对象以及其依赖的对象,提高内存的利用率。
  • 为了保证插件的高效运行,VS Code使用了Dispose模式,大部分插件API都实现了IDisposable接口,生成的对象则会拥有一个dispose函数属性。
typescript 复制代码
interface IDisposable {
  dispose(): void;
}
  • Dispose模式主要用来资源管理,资源比如内存被对象占用,则会通过调用方法来释放,这些方法通常被命名为'close','dispose','free','release'。一个著名的例子便是C#,C#通过Dipose Pattern来释放不受CLR(Common Language Runtime)管理的非托管资源。
  • Javascript的内存分配是通过GC(garbage collector)进行管理,大部分情况下它都是自动执行且对用户不可见的。然而这种自动化的管理方式却存在一个潜在的问题,就是Javascript开发者会错误的认为他们不需要再关心内存管理了,从而再无意间书写一些不利于内存回收的代码。
  • 所以,最清楚被分配的内存在未来是否需要使用的还是开发者,但是每次使用完一个对象后就手动的将其销毁,这样的做法即不高效,也不可靠。正因为此,VS Code使用了Dispose Pattern来管理对象销毁。当扩展功能执行时,Extension Host会在正确的时机调用dispose方法,销毁Code生成的对象,减少内存使用。比如说,方法'setStatusBarMessage(value: string)'返回一个'Disposable'对象,当调用dispose方法的时候会移除掉信息对象。
typescript 复制代码
// 第一个重载参数是单个disposable类型
function dispose<T extends IDisposable>(disposable: T): T;
// 第二个重载参数是多个disposable类型传参数,参数可能为undefined。
function dispose<T extends IDisposable>(...disposables: Array<T | undefined>): T[];
// 第三个重载参数是一个disposable类型的数组。
function dispose<T extends IDisposable>(disposables: T[]): T[];
// 第三个重载参数为两种,第一个是disposable类型或disposable数组类型,剩余的为disposable类型。
function dispose<T extends IDisposable>(first: T | T[], ...rest: T[]): T | T[] | undefined {
  // 如果第一个参数是数组,则依次调用传参数的dispose方法
  if (Array.isArray(first)) {
    first.forEach(d => d && d.dispose());
    // 返回空的数组
    return [];
  } else if (rest.length === 0) {
    // 如果没有没有剩余参数
    if (first) {
      // 如果存在first
      // 调用第一个dispose
      first.dispose();
      // 返回first
      return first;
    }

    return undefined;
  } else {
    // first不是数组,且rest长度不为0
    dispose(first);
    dispose(rest);

    // 返回空数组
    return [];
  }
}

// implement IDisposable 的Disposable 抽象类
abstract class Disposable implements IDisposable {

  // Disposable类的静态对象,用于返回一个包含空的dispose方法的IDisposable对象。dispose被执行了,则表示该对象不再需要了。
  // 部分基础API使用了该对象,用于标志资源释放。
  static None = Object.freeze<IDisposable>({ dispose() { } });

  // protected属性toDispose返回protected对象_toDispose, 该对象初始值是一个空的数组。
  protected _toDispose: IDisposable[] = [];
  // 返回IDisposable数组。
  protected get toDispose(): IDisposable[] { return this._toDispose; }

  // 设置状态标志,表示该对象是否有被销毁。
  private _lifecycle_disposable_isDisposed = false;

  // 暴露公共方法dispose,执行完后将_lifecycle_disposable_isDisposed状态标志设为true,同时调用lifecycle内的dispose方法处理_toDispose数组,并重新赋值空数组。
  public dispose(): void {
    this._lifecycle_disposable_isDisposed = true;
    this._toDispose = dispose(this._toDispose);
  }

  // 内部方法注册实例,若_lifecycle_disposable_isDisposed为true,则表明该方法已经被dispose过,则不能再使用,需dispose掉,否则,推入_toDispose数组。
  protected _register<T extends IDisposable>(t: T): T {
    // 判断这个对象有没有被dispose过
    if (this._lifecycle_disposable_isDisposed) {
      console.warn('Registering disposable on object that has already been disposed.');
      t.dispose();
    } else {
      this._toDispose.push(t);
    }

    return t;
  }
}

Event和Emitter

  • 在vscode中事件模块是一个比较基础,而且比较核心的一块内容,可以说是vscode应用程序的一块基石。

Event事件

  • Event 接口规定了一个函数,当调用了这个函数,就表示监听了这个函数所对应的事件流。
    • listener 参数是事件派发时将会被调用的回调函数,参数 e 为单个事件,换句话说, listener 就是事件的消费者
    • thisArgs 参数是回调函数中 this 所指向的对象
    • disposables
typescript 复制代码
export interface Event<T> {
	(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable;
}
  • 返回的 IDisposable 对象用于解除这个监听的(通过调用它的 dispose 方法)。
  • 另外一种解除监听的方式就是 disposable 了,Event 函数在执行的过程中会将 IDisposable 插入 disposables,方便调用方决定在什么时候解除监听。
  • 在VSCode源码的实现中,实现了一个Event的库。里面包含了很多其他的有关Event事件的方法。

Emitter事件发射器

  • Emitter 类型暴露了两个重要方法:
    • fire,从这个方法的函数签名就能看出它就是用来派发一个事件的,该方法的主要逻辑就是将 this._listeners 当中的保存的 listener 全部调用一遍(省略了部分分支逻辑和性能监控相关代码)
csharp 复制代码
	fire(event: T): void {
		if (this._listeners) {
			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);
				}
			}
		}
	}
    • get event(),这个方法会在 Emitter 中创建一个 Event,其主要逻辑就是将 listener 添加到 this._listeners 当中
arduino 复制代码
const remove = this._listeners.push(!thisArgs ? listener : [listener, thisArgs]);
  • Emitter 类型还提供了一些特殊的回调接口:
typescript 复制代码
export interface EmitterOptions {
	onFirstListenerAdd?: Function;
	onFirstListenerDidAdd?: Function;
	onListenerDidAdd?: Function;
	onLastListenerRemove?: Function;
}
  • 这使得 Emitter 在注册消费者的时候执行一些额外的逻辑。

  • 事件的使用方式主要包括:

    • 注册事件发射器
    • 对外提供定义的事件
    • 在特定时机向订阅者触发事件
  • 在Emitter的实现过程中,还实现了其他的一些场景,比如防抖、once等等。具体的解析可以参考:github.com/wzhudev/blo...

简单的Event-Emitter使用

  • 事件的定义:(生产一个事件)
csharp 复制代码
export class EditorService extends Disposable implements EditorServiceImpl {
 declare readonly _serviceBrand: undefined;
 //#region events
 private readonly _onDidActiveEditorChange = this._register(new Emitter<void>());
 readonly onDidActiveEditorChange = this._onDidActiveEditorChange.event;
 private readonly _onDidVisibleEditorsChange = this._register(new Emitter<void>());
 readonly onDidVisibleEditorsChange = this._onDidVisibleEditorsChange.event;
 private readonly _onDidEditorsChange = this._register(new Emitter<IEditorsChangeEvent>());
 readonly onDidEditorsChange = this._onDidEditorsChange.event;
 private readonly _onDidCloseEditor = this._register(new Emitter<IEditorCloseEvent>());
 readonly onDidCloseEditor = this._onDidCloseEditor.event;
 //#endregion
}
  • 事件的消费:(注册监听函数)
kotlin 复制代码
import { Event } from 'vs/base/common/event';
class MainThreadDocumentAndEditorStateComputer {
  constructor(
  @IEditorService private readonly _editorService: IEditorService,
 ) {
  
  this._editorService.onDidActiveEditorChange(_ => this._updateState(), this, this._toDispose);
  Event.filter(this._paneCompositeService.onDidPaneCompositeOpen, event => event.viewContainerLocation === ViewContainerLocation.Panel)(_ => this._activeEditorOrder = ActiveEditorOrder.Panel, undefined, this._toDispose);
  Event.filter(this._paneCompositeService.onDidPaneCompositeClose, event => event.viewContainerLocation === ViewContainerLocation.Panel)(_ => this._activeEditorOrder = ActiveEditorOrder.Editor, undefined, this._toDispose);
  this._editorService.onDidVisibleEditorsChange(_ => this._activeEditorOrder = ActiveEditorOrder.Editor, undefined, this._toDispose);
 }
}
相关推荐
晨米酱11 小时前
Umi Mock 如何从文件声明变成 HTTP 响应
前端·javascript·设计
星期一研究室1 天前
为什么有些文档你永远不想打开第二次
微服务·产品·设计
用户6919026813391 天前
Agent 上下文记忆原理
设计模式·架构·设计
星期一研究室3 天前
长文里的观点太容易混在一起?用引用块把重点分清楚
微服务·产品·设计
星栈3 天前
为什么独立开发者都应该试试 Open Design?
设计·视觉设计·交互设计
KECCO74 天前
IP与文旅品牌如何打造专属字体?定制字体在文化表达与商业延展中的应用分析
人工智能·字体·设计·字体设计·定制字体
codeGoogle5 天前
Token生产之后,合肥开始研究怎么“运营”
设计
星期一研究室6 天前
AI 生成一堆素材后,怎么整理成好找又好用的视觉资产库?
微服务·产品·设计
星期一研究室6 天前
一张原图衍生整套系列,精简工序高效出稿
微服务·产品·设计
星期一研究室9 天前
用公式让表格自动完成90%重复工作!
微服务·产品·设计