假设我有一个基本的递归函数:
function recur(data) {
data = data+1;
var nothing = function() {
recur(data);
}
nothing();
}
如果我有匿名函数,如...
,我怎么能这样做呢?(function(data){
data = data+1;
var nothing = function() {
//Something here that calls the function?
}
nothing();
})();
我想要一种方法来调用调用这个函数的函数...我已经看到某个地方的脚本(我记不清哪里)可以告诉你一个叫做函数的名字,但我不能现在回忆起任何这些信息。
答案 0 :(得分:132)
可以为函数命名,即使您将函数创建为值而不是“函数声明”语句。换句话说:
(function foo() { foo(); })();
是一个堆栈递归函数。现在,这就是说,你probably don't may not want to do this一般因为Javascript的各种实现存在一些奇怪的问题。 ( note - 这是一个相当古老的评论; Kangax博客文章中描述的一些/很多/所有问题可能会在更现代的浏览器中修复。)
当你给出这样的名字时,这个名字在功能之外是不可见的(好吧,它不应该是;这是奇怪之一)。这就像Lisp中的“letrec”。
对于arguments.callee
,这在“严格”模式下是不允许的,并且通常被认为是一件坏事,因为它会使一些优化变得困难。它也比人们预期的要慢得多。
编辑 - 如果你想拥有一个可以调用自身的“匿名”函数的效果,你可以做这样的事情(假设你把这个函数作为一个回调或类似的东西传递即):
asyncThingWithCallback(params, (function() {
function recursive() {
if (timeToStop())
return whatever();
recursive(moreWork);
}
return recursive;
})());
它的作用是定义一个具有漂亮,安全,未破坏的IE函数声明语句的函数,创建一个名称不会污染全局名称空间的本地函数。包装器(真正的匿名)函数只返回本地函数。
答案 1 :(得分:30)
人们在评论中谈到了Y组合者,但没有人把它作为答案。
Y组合器可以在javascript中定义如下:(感谢steamer25的链接)
var Y = function (gen) {
return (function(f) {
return f(f);
}(function(f) {
return gen(function() {
return f(f).apply(null, arguments);
});
}));
}
当你想传递你的匿名函数时:
(Y(function(recur) {
return function(data) {
data = data+1;
var nothing = function() {
recur(data);
}
nothing();
}
})());
关于这个解决方案最重要的一点是你不应该使用它。
答案 2 :(得分:14)
U组合
U组合器接受一个函数并将其应用于自身。所以你给它的函数至少应该有一个参数绑定到函数(本身)
在下面的例子中,我们没有退出条件,所以我们将无限循环直到堆栈溢出发生
const U = f => f (f)
U (f => (console.log ('stack overflow imminent!'), U (f)))

我们可以使用各种技术来阻止无限递归。在这里,我将编写我们的匿名函数来返回另一个等待输入的匿名函数;在这种情况下,一些数字。提供数字时,如果它大于0,我们将继续重复,否则返回0。
const log = x => (console.log (x), x)
const U = f => f (f)
// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function
// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0

这里不明显的是我们的函数,当首次使用U
组合器应用于自身时,它返回一个等待第一个输入的函数。如果我们为此命名,可以使用lambdas(匿名函数)
const log = x => (console.log (x), x)
const U = f => f (f)
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
countDown (5)
// 4 3 2 1 0
countDown (3)
// 2 1 0

只有这不是直接递归 - 一个使用自己的名称调用自身的函数。我们对countDown
的定义并未在其正文中引用,并且仍然可以递归
// direct recursion references itself by name
const loop = (params) => {
if (condition)
return someValue
else
// loop references itself to recur...
return loop (adjustedParams)
}
// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
如何使用U组合器从现有功能中删除自引用
在这里,我将向您展示如何使用递归函数,该函数使用对自身的引用并将其更改为使用U组合子代替自引用的函数
const factorial = x =>
x === 0 ? 1 : x * factorial (x - 1)
console.log (factorial (5)) // 120

现在使用U组合器将内部引用替换为factorial
const U = f => f (f)
const factorial = U (f => x =>
x === 0 ? 1 : x * U (f) (x - 1))
console.log (factorial (5)) // 120

基本的替代模式是这样的。请记住,我们将在下一节中使用类似的策略
// self reference recursion
const foo = x => ... foo (nextX) ...
// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)
Y组合
在上一节中,我们看到了如何将自引用递归转换为不依赖于使用U组合子的命名函数的递归函数。因为必须记住总是将函数作为第一个参数传递给自己,所以有点烦恼。好吧,Y-combinator建立在U-combinator的基础上,并删除了那个乏味的位。这是一件好事,因为删除/降低复杂性是我们制作函数的主要原因
首先,让我们推导出我们自己的Y-combinator
// standard definition
const Y = f => f (Y (f))
// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))
// remove reference to self using U combinator
const Y = U (h => f => f (x => U (h) (f) (x)))
现在我们将看到它的用法与U-combinator相比如何。请注意,要重复,我们只需拨打U (f)
f ()
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
Y (f => (console.log ('stack overflow imminent!'), f ()))

现在,我将使用countDown
演示Y
程序 - 您会看到程序几乎完全相同,但Y组合器让事情更清晰
const log = x => (console.log (x), x)
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)
countDown (5)
// 4 3 2 1 0
countDown (3)
// 2 1 0

现在我们也会看到factorial
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const factorial = Y (f => x =>
x === 0 ? 1 : x * f (x - 1))
console.log (factorial (5)) // 120

U和Y组合子,参数超过1个
在上面的例子中,我们看到了如何循环和传递参数以跟踪"状态"我们的计算。但是,如果我们需要跟踪其他状态呢?
我们可以使用像数组一样的复合数据......
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (f => ([a, b, x]) =>
x === 0 ? a : f ([b, a + b, x - 1]))
// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7]))
// 0 1 1 2 3 5 8 13

但这很糟糕,因为它暴露了内部状态(计数器a
和b
)。如果我们可以致电fibonacci (7)
来获得我们想要的答案,那就太好了。
使用我们对curried函数(一元(1-paramter)函数序列)的了解,我们可以轻松实现我们的目标,而无需修改Y
的定义或依赖复合数据或高级语言功能。
请仔细阅读以下fibonacci
的定义。我们会立即应用分别与0
和1
绑定的a
和b
。现在斐波那契只是在等待提供的最后一个参数,它将绑定到x
。当我们递归时,我们必须调用f (a) (b) (x)
(而不是f (a,b,x)
),因为我们的函数是以咖喱形式。
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (f => a => b => x =>
x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
console.log (fibonacci (7))
// 0 1 1 2 3 5 8 13

这种模式可用于定义各种函数。下面我们将看到使用Y
组合子(range
和reduce
)以及reduce
,map
的衍生物定义的另外两个函数。
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const range = Y (f => acc => min => max =>
min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])
const reduce = Y (f => g => y => ([x,...xs]) =>
x === undefined ? y : f (g) (g (y) (x)) (xs))
const map = f =>
reduce (ys => x => [...ys, f (x)]) ([])
const add = x => y => x + y
const sq = x => x * x
console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]
console.log (reduce (add) (0) ([1,2,3,4]))
// 10
console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]

IT全部匿名OMG
因为我们在这里使用纯函数,所以我们可以用任何命名函数替换它的定义。观察当我们采用斐波纳契并用其表达式替换命名函数时会发生什么
/* const U = f => f (f)
*
* const Y = U (h => f => f (x => U (h) (f) (x)))
*
* const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
*
*/
/*
* given fibonacci (7)
*
* replace fibonacci with its definition
* Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
*
* replace Y with its definition
* U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
* replace U with its definition
* (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
*/
let result =
(f => f (f)) (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
console.log (result) // 13

你有它 - fibonacci (7)
递归计算只使用匿名函数
答案 3 :(得分:13)
我不会这样做作为内联函数。它正在推动良好品味的界限,并没有真正让你得到任何东西。
如果你真的必须,那就像Fabrizio的回答一样arguments.callee
。然而,这通常被认为是不可取的,并且在ECMAScript第五版的“严格模式”中是不允许的。虽然ECMA 3和非严格模式不会消失,但在严格模式下工作可以提供更多可能的语言优化。
还可以使用命名内联函数:
(function foo(data){
data++;
var nothing = function() {
foo(data);
}
nothing();
})();
然而,最好避免使用命名的内联函数表达式,因为IE的JScript会给它们带来一些不好的东西。在上面的示例中,foo
错误地污染了IE中的父作用域,而父foo
是foo
内foo
所见的单独实例。
将其置于内联匿名函数中的目的是什么?如果您只是想避免污染父作用域,您当然可以将您的第一个示例隐藏在另一个自调用匿名函数(命名空间)中。你是否真的需要每次在递归时创建nothing
的新副本?使用包含两个简单的相互递归函数的命名空间可能会更好。
答案 4 :(得分:12)
最简单的方法是使用“匿名对象”:
({
do: function() {
console.log("don't run this ...");
this.do();
}
}).do();
您的全球空间完全没有受到污染。这很简单。您可以轻松利用对象的非全局状态。
答案 5 :(得分:11)
(function(data){
var recursive = arguments.callee;
data = data+1;
var nothing = function() {
recursive(data)
}
nothing();
})();
答案 6 :(得分:6)
您可以执行以下操作:
(foo = function() { foo(); })()
或在你的情况下:
(recur = function(data){
data = data+1;
var nothing = function() {
if (data > 100) return; // put recursion limit
recur(data);
}
nothing();
})(/* put data init value here */ 0);
答案 7 :(得分:3)
当您声明这样的匿名函数时:
(function () {
// Pass
}());
它被认为是一个函数表达式,它有一个可选的名称(可以用来从内部调用它。但是因为它是一个函数表达式(而不是一个语句)它保持匿名(但是有一个你可以调用的名字)因此,这个函数可以调用自己:
(function foo () {
foo();
}());
foo //-> undefined
答案 8 :(得分:3)
为什么不将函数传递给函数本身?
var functionCaller = function(thisCaller, data) {
data = data + 1;
var nothing = function() {
thisCaller(thisCaller, data);
};
nothing();
};
functionCaller(functionCaller, data);
答案 9 :(得分:3)
在某些情况下,您必须依赖匿名函数。给定是一个递归map
函数:
const map = f => acc => ([head, ...tail]) => head === undefined
? acc
: map (f) ([...acc, f(head)]) (tail);
const sqr = x => x * x;
const xs = [1,2,3,4,5];
console.log(map(sqr) ([0]) (xs)); // [0] modifies the structure of the array

请注意,map
不得修改数组的结构。所以累加器acc
不需要暴露。我们可以将map
包装到另一个函数中,例如:
const map = f => xs => {
let next = acc => ([head, ...tail]) => head === undefined
? acc
: map ([...acc, f(head)]) (tail);
return next([])(xs);
}
但这个解决方案非常冗长。让我们使用低估的U
组合子:
const U = f => f(f);
const map = f => U(h => acc => ([head, ...tail]) => head === undefined
? acc
: h(h)([...acc, f(head)])(tail))([]);
const sqr = x => x * x;
const xs = [1,2,3,4,5];
console.log(map(sqr) (xs));

简明扼要,不是吗? U
很简单,但缺点是递归调用有点混淆:sum(...)
变为h(h)(...)
- 这就是全部。
答案 10 :(得分:2)
我不确定答案是否仍然需要,但这也可以使用function.bind创建的委托来完成:
var x = ((function () {
return this.bind(this, arguments[0])();
}).bind(function (n) {
if (n != 1) {
return n * this.bind(this, (n - 1))();
}
else {
return 1;
}
}))(5);
console.log(x);
这不涉及命名函数或arguments.callee。
答案 11 :(得分:1)
像bobince写的那样,只需命名你的功能。
但是,我猜你也想传递一个初始值并最终停止你的功能!
var initialValue = ...
(function recurse(data){
data++;
var nothing = function() {
recurse(data);
}
if ( ... stop condition ... )
{ ... display result, etc. ... }
else
nothing();
}(initialValue));
答案 12 :(得分:1)
我需要(或者更确切地说,想要)一个单行匿名函数来向上移动一个构建字符串的对象,并像这样处理它:
var cmTitle = 'Root' + (function cmCatRecurse(cmCat){return (cmCat == root) ? '' : cmCatRecurse(cmCat.parent) + ' : ' + cmCat.getDisplayName();})(cmCurrentCat);
产生一个像'Root:foo:bar:baz:...'
这样的字符串答案 13 :(得分:1)
使用ES2015,我们可以使用语法和滥用默认参数和thunk来玩一下。后者只是没有任何参数的函数:
const applyT = thunk => thunk();
const fib = n => applyT(
(f = (x, y, n) => n === 0 ? x : f(y, x + y, n - 1)) => f(0, 1, n)
);
console.log(fib(10)); // 55
// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...
请注意,f
是一个匿名函数(x, y, n) => n === 0 ? x : f(y, x + y, n - 1)
作为默认值的参数。当f
调用applyT
时,必须在没有参数的情况下进行此调用,以便使用默认值。默认值是一个函数,因此f
是一个命名函数,可以递归调用自身。
答案 14 :(得分:0)
我不建议在任何实际用例中执行此操作,但作为一个有趣的练习,您实际上可以使用第二个匿名函数来执行此操作!
(f => f(f))(f => {
data = data+1;
var nothing = function() {
f();
}
nothing(f);
});
这种工作方式是我们将匿名函数作为参数传递给自身,因此我们可以从自身调用它。
答案 15 :(得分:0)
另一个使用rosetta-code链接的Y组合器解决方案(我认为以前有人在stackOverflow的某处提到了该链接。
箭头是我更容易理解的匿名函数:
var Y = f => (x => x(x))(y => f(x => y(y)(x)));
答案 16 :(得分:0)
这是带有箭头功能的@ zem答案版本。
您可以使用U
或Y
组合子。 Y组合器是最简单的。
U
组合器,你必须继续传递函数:
const U = f => f(f)
U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))
Y
组合器,你不必继续传递函数:
const Y = gen => U(f => gen((...args) => f(f)(...args)))
Y(selfFn => arg => selfFn('to infinity and beyond'))
答案 17 :(得分:0)
这是jforjs答案的返工,具有不同的名称和略微修改的条目。
// function takes two argument: first is recursive function and second is input
var sum = (function(capturedRecurser,n){
return capturedRecurser(capturedRecurser, n);
})(function(thisFunction,n){
if(n>1){
return n + thisFunction(thisFunction,n-1)
}else{
return n;
}
},5);
console.log(sum) //output : 15
无需展开第一次递归。接收自身作为参考的函数可以追溯到OOP的原始渗出。
答案 18 :(得分:0)
另一个不涉及命名函数或arguments.callee
的答案var sum = (function(foo,n){
return n + foo(foo,n-1);
})(function(foo,n){
if(n>1){
return n + foo(foo,n-1)
}else{
return n;
}
},5); //function takes two argument one is function and another is 5
console.log(sum) //output : 15
答案 19 :(得分:-1)
这可能无处不在,但您可以使用arguments.callee
来引用当前函数。
因此,可以这样做因子:
var fac = function(x) {
if (x == 1) return x;
else return x * arguments.callee(x-1);
}