zoukankan      html  css  js  c++  java
  • js 函数递归优化,arguments.callee 优化

    函数递归是个经典的问题,平常用的时候,小练习可以通过函数名来反复调用,比如:

    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * factorial(num - 1);
        }
    }

    js高程认为:

    这个函数的执行与函数名factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用 arguments.callee (我理解就是,如果想改个函数名 factorial ,那我要改两次或者更多次,麻烦且容易漏掉)

    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * arguments.callee(num - 1);
        }
    }

    但是MDN认为:

    这实际上是一个非常糟糕的解决方案,因为这 (以及其它的 arguments, callee, 和 caller 问题) 使得在通常的情况(你可以通过调试一些个别例子去实现它,但即使最好的代码是最理想的,你也没必要去检查调试它)不可能实现内联和尾递归。另外一个主要原因是递归调用会获取到一个不同的 this 值,例如:

    var global = this;
    
    var sillyFunction = function (recursed) {
        if (!recursed) { return arguments.callee(true); }
        if (this !== global) {
            alert("This is: " + this);
        } else {
            alert("This is the global");
        }
    }
    
    sillyFunction();

    例子说明了问题,第二次递归的时候,this 变成了 arguments 对象,MDN 讲的有道理,因为 arguments.callee 是作为 arguments 对象的方法调用的,任何函数只要作为方法调用实际上都会传入一个隐式的实参-方法调用的母体对象,所以默认的 this 就是 arguments 对象。

    但是如果想保存 this 值,我觉得也是可以实现的,比如用 .call() 和 .apply()

    var global = this;
    
    var sillyFunction = function (recursed) {
        if (!recursed) { return arguments.callee.call(global,true); }
        if (this !== global) {
            alert("This is: " + this);
        } else {
            alert("This is the global");
        }
    };
    
    sillyFunction();

    但是还有个缺点就是,

    因为这 (以及其它的 arguments, callee, 和 caller 问题) 使得在通常的情况(你可以通过调试一些个别例子去实现它,但即使最好的代码是最理想的,你也没必要去检查调试它)不可能实现内联和尾递归,

    内联和尾递归是什么我不知道了,等遇到了我再看看能不能优化吧,不过 MDN 倒是赞同通过命名函数表达式解决这些问题;

    什么叫命名函数表达式呢?

    通常我们定义函数有两种方式,一般都是:

    //函数声明语句
    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * /*怎么填???*/(num - 1);
        }
    }
    //函数定义表达式
    var factorial=function (num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * /*怎么填???*/(num - 1);
        }
    };

    但是这样就面临递归的问题,除了上面两种情况,我们也可以这样定义:

    var factorial=function f(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * f(num - 1);
        }
    };

    js 权威指南里指出:

    函数名称标识符,对于函数定义表达式来说,这个名称是可选的;如果存在,该名字只存在于函数体中,并指代该函数对象本身。

    后面半句话很关键哦,函数名称标识符,如果存在,该名字只存在于函数体中,并指代该函数对象本身。这意思是,函数名称标识符作为函数体中的一个局部变量存在,指代函数对象本身,它和被函数赋值的变量名并不在同一个执行环境,被函数赋值的变量名在上一级环境;例如:

    image

    这意味着,你要改函数名,就不用改 f 了,只要改变量名就行了,解决了 js高程 的代码耦合的问题,而且避免了可能出现的 MDN 提出的问题;

    除了在函数定义的时候可以用,平时也可以用,把 匿名函数 换成 命名函数表达式 就可以了,如:

    [1, 2, 3, 4, 5].map(function(n) {
        return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
    });
    
    //优化的代码
    [1, 2, 3, 4, 5].map(function f(n) {
        return !(n > 1) ? 1 : f(n - 1) * n;
    });

    参考资料:

    1、MDN: arguments.callee 属性包含当前正在执行的函数。

    2、JavaScript高级程序设计-第3版

    3、JavaScript权威指南-第6版

  • 相关阅读:
    oracle—数据泵及常用参数
    NTP服务及时间同步
    kudu安装
    ogg12c 配置
    ogg12-ERROR OGG-01031 file D:OGGdirdated000000 is not in any allowed output directories
    ogg12c_静默安装
    git的基本使用
    redis数据库
    linux之网络
    flask框架基础
  • 原文地址:https://www.cnblogs.com/xianshenglu/p/8086042.html
Copyright © 2011-2022 走看看