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

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 权威指南里指出:

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

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

这意味着,你要改函数名,就不用改 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版

js 函数递归优化,arguments.callee 优化的更多相关文章

  1. 递归与arguments.callee;

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. JavaScript (JS) 函数补充 (含arguments、eval()、四种调用模式)

    1. 程序异常 ① try-catch语法    测试异常 try-catch语法代码如下: try { 异常代码;     try中可以承重异常代码, console.log(“try”)  出现异 ...

  3. 引用类型--Function类型(函数声明与函数表达式、arguments.callee、caller、apply、call、bind)

    在ECMAScript中函数实际上是对象.每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法.由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定 ...

  4. js的隐含参数(arguments,callee,caller)使用方法

    在提到上述的概念之前,首先想说说javascript中函数的隐含参数: arguments arguments 该对象代表正在执行的函数和调用它的函数的参数.[function.]arguments[ ...

  5. arguments.callee 调用函数自身用法----JSON.parse()和JSON.stringify()前端js数据转换json格式

    arguments.callee 调用函数自身用法 arguments.callee 在哪一个函数中运行,它就代表哪个函数. 一般用在匿名函数中. 在匿名函数中有时会需要自己调用自己,但是由于是匿名函 ...

  6. js arguments.callee & caller的用法及区别

    在函数内部,arguments.callee该属性是一个指针,指向拥有这个arguments对象的函数; 而函数对象的另一个属性:caller,这个属性保存着调用当前函数的函数的引用,如果是在全局作用 ...

  7. javascript 中的 arguments,callee.caller,apply,call 区别

    记录一下: 1.arguments是一个对象, 是函数的一个特性,只有在函数内才具有这个特性,在函数外部不用使用. 举例: function test(){   alert(typeof argume ...

  8. 前端总结·基础篇·JS(三)arguments、callee、call、apply、bind及函数封装和构造函数

    前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(三)补充 前端总结·基础篇·JS(一)原型.原型链.构造函数和字符串(String) 前 ...

  9. JavaScript函数之实际参数对象(arguments) / callee属性 / caller属性 / 递归调用 / 获取函数名称的方法

    函数的作用域:调用对象 JavaScript中函数的主体是在局部作用域中执行的,该作用域不同于全局作用域.这个新的作用域是通过将调用对象添加到作用域链的头部而创建的(没怎么理解这句话,有理解的亲可以留 ...

随机推荐

  1. nginx Beginner’s Guide

    这个引导给nginx做了一个基本的介绍,并描述了nginx可以做的一些基本事情. 假设nginx已经安装在了读者的电脑上,如果没有请查看官网安装页. 这个引导描述了怎么去开始和结束nginx,从新加载 ...

  2. CakePHP不支持path/to路径,前后台无法方法

    本来想把前后台分离,可是阅读了cakephp的说明,才发现.cakephp根本就不支持path/to路径. cakephp官网给出的 管理员分离方式:http://book.cakephp.org/2 ...

  3. git配置文件读取顺序

    作者:zhanhailiang 日期:2014-11-03 git包括三个配置文件: /etc/gitconfig 文件:系统中对全部用户都普遍适用的配置. 若使用git config 时用' –sy ...

  4. ubuntu中rc.local无效

    在ubuntu中写了一点iptables规则,但是,竟然iptables竟然无效,经过多方查找问题...眼泪... 终于发现是rc.local竟然没有运行,我晕.仔细检查iptables脚本n遍,没有 ...

  5. Java中path,-classpath,-Djava.library.path的功能和区别

    1. path path是个系统环境变量,声明命令的搜索路径,让操作系统找到指定的工具程序. D:\Program Files\Java\jdk1.8.0_111\bin指定JDK工具路径,例如jav ...

  6. java:常用的两种设计模式(单例模式和工厂模式)

    一.单例模式:即一个类由始至终只有一个实例.有两种实现方式(1)定义一个类,它的构造方法是私有的,有一个私有的静态的该类的变量在初始化的时候就实例化,通过一个公有的静态的方法获取该对象.Java代码  ...

  7. 下面哪些属于JSTL中的表达式操作标签。(选择1项)

    A.<c:out> B.<c:if> C.<c:url> D.<c:catch> 解答:A

  8. Bufferread有readline()使得字符输入更加方便

    原则:保证编解码方式的统一,才能不至于出现错误. Io包的InputStreamread称为从字节流到字符流的桥转换类.这个类可以设定字符转换方式. OutputStreamred:字符到字节 Buf ...

  9. [转载]Jenkins持续集成项目搭建与实践——基于Python Selenium自动化测试 -2

    自己的代码 import unittest # import HTMLTestRunner_cn as HTMLTestRunner import xmlrunner import sys sys.p ...

  10. 借用smtp.qq.com发邮件

    至于sentmail和postfix的配置有多么麻烦学生在这里就不多废话了...反正是配置了N个小时,最终弄的头晕眼花也没弄好... 下面的方法可以让你完全摆脱这两个工具...当然,你要是想做邮件服务 ...