美文网首页让前端飞
【JS/TS】探索惰性函数

【JS/TS】探索惰性函数

作者: 来一斤BUG | 来源:发表于2023-10-18 19:27 被阅读0次

    前言

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

    惰性函数

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

    运行一下测试用例:

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

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

    相关文章

      网友评论

        本文标题:【JS/TS】探索惰性函数

        本文链接:https://www.haomeiwen.com/subject/xmdcidtx.html