让前端飞

【JS/TS】探索惰性函数

2023-10-18  本文已影响0人  来一斤BUG

前言

很多时候,我们的函数中充斥着大量的判断语句,其中不乏很多不怎么变化甚至不变化的判断条件,这些判断语句有时候会影响到程序的性能,本文就来讲讲如何更好地解决这个问题。

惰性函数

惰性函数是指在函数内部进行一次判断(这里说的一次判读是指一次性判断,并不一定只有一条判断语句)后,利用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 // 不建议用这个

运行一下测试用例:

打印日志
只打印了一次"判断是否为调试模式",说明替换成功了。

不过,究竟是替换实例对象中的函数还是替换原型链中的函数,需要按照实际情况来。例如单例模式的类,即可以选择替换实例对象中的函数,也可以替换原型链中的函数。

上一篇 下一篇

猜你喜欢

热点阅读