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 权威指南里指出:
函数名称标识符,对于函数定义表达式来说,这个名称是可选的;如果存在,该名字只存在于函数体中,并指代该函数对象本身。
后面半句话很关键哦,函数名称标识符,如果存在,该名字只存在于函数体中,并指代该函数对象本身。这意思是,函数名称标识符作为函数体中的一个局部变量存在,指代函数对象本身,它和被函数赋值的变量名并不在同一个执行环境,被函数赋值的变量名在上一级环境;例如:
这意味着,你要改函数名,就不用改 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 属性包含当前正在执行的函数。
js 函数递归优化,arguments.callee 优化的更多相关文章
- 递归与arguments.callee;
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- JavaScript (JS) 函数补充 (含arguments、eval()、四种调用模式)
1. 程序异常 ① try-catch语法 测试异常 try-catch语法代码如下: try { 异常代码; try中可以承重异常代码, console.log(“try”) 出现异 ...
- 引用类型--Function类型(函数声明与函数表达式、arguments.callee、caller、apply、call、bind)
在ECMAScript中函数实际上是对象.每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法.由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定 ...
- js的隐含参数(arguments,callee,caller)使用方法
在提到上述的概念之前,首先想说说javascript中函数的隐含参数: arguments arguments 该对象代表正在执行的函数和调用它的函数的参数.[function.]arguments[ ...
- arguments.callee 调用函数自身用法----JSON.parse()和JSON.stringify()前端js数据转换json格式
arguments.callee 调用函数自身用法 arguments.callee 在哪一个函数中运行,它就代表哪个函数. 一般用在匿名函数中. 在匿名函数中有时会需要自己调用自己,但是由于是匿名函 ...
- js arguments.callee & caller的用法及区别
在函数内部,arguments.callee该属性是一个指针,指向拥有这个arguments对象的函数; 而函数对象的另一个属性:caller,这个属性保存着调用当前函数的函数的引用,如果是在全局作用 ...
- javascript 中的 arguments,callee.caller,apply,call 区别
记录一下: 1.arguments是一个对象, 是函数的一个特性,只有在函数内才具有这个特性,在函数外部不用使用. 举例: function test(){ alert(typeof argume ...
- 前端总结·基础篇·JS(三)arguments、callee、call、apply、bind及函数封装和构造函数
前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(三)补充 前端总结·基础篇·JS(一)原型.原型链.构造函数和字符串(String) 前 ...
- JavaScript函数之实际参数对象(arguments) / callee属性 / caller属性 / 递归调用 / 获取函数名称的方法
函数的作用域:调用对象 JavaScript中函数的主体是在局部作用域中执行的,该作用域不同于全局作用域.这个新的作用域是通过将调用对象添加到作用域链的头部而创建的(没怎么理解这句话,有理解的亲可以留 ...
随机推荐
- MSSQL如何访问ORACLE里的表
环境: MSSQL:2008 ORACLE DB:10.2.0.1 之前一直没有接触MSSQL,最近因需要MSSQL访问ORACLE里的表,下面是MSSQL连接ORACLE的配置过程. 1 ...
- 【Android】15.4 例15-2--Notification基本用法
分类:C#.Android.VS2015: 创建日期:2016-02-29 一.简介 上一节介绍了通知(Notification)相关的基本内容.这一节先用一个简单示例演示创建和发布本地通知的基本用法 ...
- 每日英语:Do Bicycle Helmet Laws Really Make Riders Safer?
Typically in transportation — and most social arenas, for that matter — laws promoting safety precau ...
- 绕过IE10直接安装VS2013
参考资料:http://blog.163.com/qimo601%40126/blog/static/1582209320143354446462/ 这SB设定我就懒得说了,安个IE10要安装N多WI ...
- 文件模式设置用户ID/设置组ID/sticky bit_转
S_ISUID (04000) set-user-ID (set process effective user ID on execve(2))S_ISGID (02000) set-grou ...
- Linux解决删除文件后空间没有释放问题_端口占用问题
使用命令 (1) losf | grep deleted 查询所有已经删除但是还未释放空间的进程,找到容量最大的线程,kill -9 pid 然后等待容量释放 (2) netstat -ano | g ...
- 面向对象设计原则三:里氏替换原则(LSP)
里氏替换原则(LSP)定义:在任何父类出现的地方都可以用它的子类类替换,且不影响功能.解释说明:其实LSP是对开闭原则的一个扩展,在OO思想中,我们知道对象是由一系列的状态和行为组成的,里氏替换原则说 ...
- Oracle 错误:ORA-06413: Connection not open 解决办法
http://blog.csdn.net/neso520/article/details/6037411 ——————————————————————————————————————————————— ...
- Abstract可以将子类的共性最大限度的抽取出来,放在父类中,以提高程序的简洁性
Abstract可以将子类的共性最大限度的抽取出来,放在父类中,以提高程序的简洁性. Abstract虽然不能生成对象,但是可以声明,作为编译时类型,但不能作为运行时类型. Final和abstrac ...
- POST GET 请求示例
HTTP GET 请求代码: HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.baidu.com ...