深入剖析vscode工具函数(五)lazy模块

605 阅读6分钟

深入剖析vscode工具函数(五)lazy模块

什么是Lazy

Lazy 是一种计算机程序设计技术,用于实现延迟加载。它的核心思想是:只有在需要的时候才计算和加载数据,而不是在程序启动时或者数据传输时提前加载和计算数据。这样可以避免不必要的计算和内存占用,提高程序的效率和性能。

在计算机科学中,延迟加载是一种非常重要的技术,因为它可以帮助程序避免不必要的计算和内存占用,提高程序的效率和性能。在实际开发中,延迟加载被广泛应用于各种类型的软件和系统中,比如网络应用、游戏开发、数据处理和函数式编程等领域。

在 JavaScript 中,使用 Lazy 技术可以实现延迟加载,避免不必要的计算和内存占用,提高程序的效率和性能。Lodash 库中的 LazyWrapper 类就是基于这种惰性求值的思想而开发的,它可以用来实现链式调用的惰性执行,并提供了一些方便的方法和函数,可以帮助开发者实现延迟加载。

Lazy的主要作用

Lazy 主要的作用是实现延迟加载,即将某些操作推迟到必要的时候再执行。这个概念在计算机科学中非常重要,因为它可以帮助程序减少不必要的计算和内存占用,提高程序的效率和性能。以下是 Lazy 常见的应用场景和作用:

  1. 提高程序性能:当某些计算比较复杂或者需要大量的内存和计算资源时,使用 Lazy 可以将计算延迟到必要的时候再执行,避免不必要的计算和内存占用,提高程序的性能。
  2. 延迟加载数据:在网络应用中,当需要加载大量数据或者文件时,使用 Lazy 可以将数据或文件的加载推迟到必要的时候再执行,避免不必要的网络传输和带宽占用,提高用户体验。
  3. 惰性求值:在函数式编程中,使用 Lazy 可以实现惰性求值,即只有在需要时才计算和求值,避免不必要的计算和内存占用,提高程序的效率和性能。
  4. 链式调用:在一些场景中,需要进行多次操作,并将这些操作组成一个链式调用。使用 Lazy 可以实现链式调用的惰性执行,避免不必要的计算和内存占用,提高程序的效率和性能。

VSCode的Lazy实现

export class Lazy<T> {

	private _didRun: boolean = false;
	private _value?: T;
	private _error: Error | undefined;

	constructor(
		private readonly executor: () => T,
	) { }

	/**
	 * True if the lazy value has been resolved.
	 */
	get hasValue() { return this._didRun; }

	/**
	 * Get the wrapped value.
	 *
	 * This will force evaluation of the lazy value if it has not been resolved yet. Lazy values are only
	 * resolved once. `getValue` will re-throw exceptions that are hit while resolving the value
	 */
	get value(): T {
		if (!this._didRun) {
			try {
				this._value = this.executor();
			} catch (err) {
				this._error = err;
			} finally {
				this._didRun = true;
			}
		}
		if (this._error) {
			throw this._error;
		}
		return this._value!;
	}

	/**
	 * Get the wrapped value without forcing evaluation.
	 */
	get rawValue(): T | undefined { return this._value; }
}

这个实现并不复杂,主要说明一下:

  1. Lazy 类是一个泛型类,可以适用于各种类型的数据和对象。
  2. Lazy 类有一个私有变量 _didRun,用于记录是否已经执行过计算。
  3. Lazy 类有一个私有变量 _value,用于缓存计算结果。
  4. Lazy 类有一个私有变量 _error,用于记录计算过程中可能发生的异常和错误。
  5. Lazy 类有一个构造函数,接收一个参数 executor,它是一个函数类型,用于执行实际的计算操作。
  6. Lazy 类有一个只读属性 hasValue,用于判断是否已经执行过计算。
  7. Lazy 类有一个属性 value,用于获取计算结果,如果计算结果还没有被缓存,就调用 executor 函数进行计算,并将计算结果缓存到 _value 变量中。如果计算过程中发生了异常,就将异常信息缓存到 _error 变量中,并通过 throw 抛出异常。value 属性只能调用一次,后续调用将直接返回缓存的计算结果。
  8. Lazy 类有一个属性 rawValue,用于获取计算结果,但不会执行实际的计算操作。如果计算结果还没有被缓存,就返回 undefined,否则返回已经缓存的计算结果。

Lazy的使用方式

VSCode中存在大量对 Lazy 的使用,在这里给出一个通用的使用方式:

// 定义一个需要延迟加载的函数
function expensiveCalculation(): number {
  console.log('Executing expensiveCalculation');
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += i;
  }
  return result;
}

// 创建一个 Lazy 对象
const lazyValue = new Lazy(() => expensiveCalculation());

// 访问 Lazy 对象的值
console.log(lazyValue.hasValue); // false

const value1 = lazyValue.value; // 这里会触发 expensiveCalculation 的执行
console.log(value1); // 输出 expensiveCalculation 的返回值
console.log(lazyValue.hasValue); // true

const value2 = lazyValue.value; // 这里不会再次执行 expensiveCalculation
console.log(value2); // 输出 expensiveCalculation 的返回值

const rawValue = lazyValue.rawValue; // 可以直接访问缓存的计算结果
console.log(rawValue); // 输出 expensiveCalculation 的返回值

实际上 Lazy 的实现包含了 once 的能力,对于使用 Lazy 来执行的函数来说,它只会被执行一次,并且是访问 .value 的使用再执行。

这里有了 value 为什么还需要一个 rawValue 呢?仔细看实现就会发现,**value**属性会在需要时执行传入的函数并保存执行结果,而 **rawValue**属性则不会执行传入的函数,只是返回保存的结果。有些情况下,我们是不需要函数被执行的,比如一些判断的场景。

以下是VSCode中使用 rawValue 的场景:

this._register(this.model.onChangedHints(newParameterHints => {
			if (newParameterHints) {
				this.widget.value.show();
				this.widget.value.render(newParameterHints);
			} else {
				this.widget.rawValue?.hide();
			}
		}));

当我们调用 hide 的时候,并不知道 widget 是否已经执行过了,而我们并不想带来副作用,所以这个时候不是取 value ,而是 rawValue

小结

本文探讨了Lazy的概念,它是一种编程技术,用于实现延迟加载。延迟加载的核心思想是在需要时计算和加载数据,而不是在程序启动或数据传输期间提前加载和计算数据。这种方法可以帮助减少不必要的计算和内存使用,提高程序的效率和性能。

延迟加载是计算机科学中的一项关键技术,并广泛应用于各种类型的软件和系统,例如网络应用程序、游戏开发、数据处理和函数式编程。在JavaScript中,使用延迟加载可以避免不必要的计算和内存使用,提高程序的效率和性能。

在VSCode中,实现惰性的方式并不复杂,但应用非常多。在普通的Web开发中,可能我们并不太关注计算性能及内存带来的消耗,但是在VSCode这样大型桌面应用中,需要非常精细地控制计算性能和内存的开销,性能的保证提现在细节习惯中。在写代码时,一定要时刻问自己,是不是需要立即执行?是不是可以延迟加载?保持心中的准绳。