前言
很多时候,我们的函数中充斥着大量的判断语句,其中不乏很多不怎么变化甚至不变化的判断条件,这些判断语句有时候会影响到程序的性能,本文就来讲讲如何更好地解决这个问题。
惰性函数
惰性函数是指在函数内部进行一次判断(这里说的一次判读是指一次性判断,并不一定只有一条判断语句)后,利用js/ts的机制替换掉原函数,从而优化程序的性能。
举个例子,很多时候我们的程序需要根据调试环境或生成环境执行不同的代码,两个模式通常会用一个机制区分,或是变量,或是主机名。比如下面的示例代码:
/**
* 判断是否为调试模式
* @return {boolean}
*/
function isDebugMode() {
// 判断window.DEBUG是否为true,不为true则进行下一步判断
if (window["DEBUG"]) {
return true;
}
// 判断url是否为本地
return window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost";
}
/**
* 示例函数
*/
function example() {
if (isDebugMode()) {
console.log("当前为调试环境");
// 调试环境下的逻辑
} else {
console.log("当前为生产环境");
// 生产环境下的逻辑
}
}
假设example()
示例函数是一个经常需要调用的函数,它会使用isDebugMode()
函数判断当前是否为调试环境,然后根据不同的环境执行不同的操作。由于example()
可能会多次调用,导致每次都要判断一遍是否为调试环境,影响了性能。一个方法是用变量保存isDebugMode()
函数返回的结果,然后每次判断这个变量,但是这样还是得每次都判断。接下来我们看看用惰性函数怎么解决这个问题:
/**
* 示例函数
*/
function example() {
if (isDebugMode()) {
// 替换成调试环境下的函数
example = () => {
console.log("当前为调试环境");
// 调试环境下的逻辑
}
} else {
// 替换成生产环境下的函数
example = () => {
console.log("当前为生产环境");
// 生产环境下的逻辑
}
}
// 第一次替换后执行一次
example();
}
以上是修改过的example()
示例函数,我们可以看到当第一次执行example()
函数的时候判断了一次调试环境,接着将example()
函数在内部替换成了新的函数,之后再调用example()
的时候就变成了调用新的函数。这样一来就只判断了一次调试环境。
当然由于调试模式不会改变,我们也可以改成立即执行函数:
/**
* 示例函数
*/
const example = (() => {
if (isDebugMode()) {
// 返回调试环境下的函数
return () => {
console.log("当前为调试环境");
// 调试环境下的逻辑
};
} else {
// 返回生产环境下的函数
return () => {
console.log("当前为生产环境");
// 生产环境下的逻辑
};
}
})();
有时候我们的函数是成员函数,比如:
class Example {
/**
* 判断是否为调试模式
* @return {boolean}
*/
isDebugMode() {
console.log("判断是否为调试模式");
// 判断window.DEBUG是否为true,不为true则进行下一步判断
if (window["DEBUG"]) {
return true;
}
// 判断url是否为本地
return window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost";
}
/**
* 示例函数,已经改成了惰性函数
*/
example() {
if (this.isDebugMode()) {
// 替换成调试环境下的函数
this.example = () => {
console.log("当前为调试环境");
// 调试环境下的逻辑
}
} else {
// 替换成生产环境下的函数
this.example = () => {
console.log("当前为生产环境");
// 生产环境下的逻辑
}
}
// 第一次替换后执行一次
this.example();
}
}
我们使用一下的代码进行测试:
const example = new Example();
example.example();
example.example();
new Example().example();
打印日志
可以看到打印了两次"判断是否为调试模式",这说明直接将新函数赋值给
this.example
并没有影响到不同对象。这时候我们需要将原型链上的example()
函数替换掉:
// 省略其他代码
example() {
if (this.isDebugMode()) {
// 替换成调试环境下的函数
this.constructor.prototype.example = () => {
console.log("当前为调试环境");
// 调试环境下的逻辑
}
} else {
// 替换成生产环境下的函数
this.constructor.prototype.example = () => {
console.log("当前为生产环境");
// 生产环境下的逻辑
}
}
// 第一次替换后执行一次
this.example();
}
当然this.constructor.prototype.example
改成以下几种形式也是可以的,都可以获得原型链上的example()
函数:
Object.getPrototypeOf(this).example
Example.prototype.example
this.__proto__.example // 不建议用这个
运行一下测试用例:
只打印了一次"判断是否为调试模式",说明替换成功了。
不过,究竟是替换实例对象中的函数还是替换原型链中的函数,需要按照实际情况来。例如单例模式的类,即可以选择替换实例对象中的函数,也可以替换原型链中的函数。
网友评论