Javascript: рекурсивная анонимная функция?

Скажем, у меня есть базовая рекурсивная функция:

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();
})();

Мне бы хотелось вызвать функцию, которая вызвала эту функцию... Я где-то видел сценарии (я не могу вспомнить, где), которые могут назвать имя вызываемой функции, но я не могу вспомните любую из этих данных прямо сейчас.

Ответ 1

Вы можете присвоить функции имя, даже если вы создаете функцию как значение, а не инструкцию "объявление функции". Другими словами:

(function foo() { foo(); })();

- это рекурсивная функция с выталкиванием стека. Итак, вы сказали , вероятно, не, возможно, не захотите это делать в общем, потому что есть некоторые странные проблемы с различными реализациями Javascript. (обратите внимание, что довольно старый комментарий, некоторые/многие/все проблемы, описанные в блоге Kangax, могут быть исправлены в более современных браузерах.)

Когда вы даете такое имя, имя не видно вне функции (ну, это не должно быть, это одна из странностей). Это как "letrec" ​​в Lisp.

Что касается arguments.callee, это запрещается в "строгом" режиме и обычно считается плохим, потому что оно делает некоторые оптимизации жесткими. Это также намного медленнее, чем можно было бы ожидать.

edit — Если вы хотите иметь эффект "анонимной" функции, которая может вызывать себя, вы можете сделать что-то подобное (предполагая, что вы передаете функцию как обратный вызов или что-то в этом роде):

asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());

Что это значит, это определить функцию с помощью красивой, безопасной, не разбитой в IE функции выражение, создавая локальную функцию, имя которой не будет загрязнять глобальное пространство имен. Функция обертки (действительно анонимная) просто возвращает эту локальную функцию.

Ответ 2

Люди говорили о Y combinator в комментариях, но никто не написал это как ответ.

Y combinator может быть определен в javascript следующим образом: (спасибо пароходу25 для ссылки)

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();
  }
})());

Самое главное отметить это решение - вы не должны его использовать.

Ответ 3

U комбинатор

U-комбинатор берет функцию и применяет ее к себе. Таким образом, функция, которую вы ей даете, должна иметь по крайней мере один параметр, который будет привязан к самой функции

В приведенном ниже примере у нас нет условия выхода, поэтому мы будем бесконечно зацикливаться, пока не произойдет переполнение стека

const U = f => f (f)

U (f => (console.log ('stack overflow imminent!'), U (f)))

Ответ 4

Вместо этого может быть проще использовать "анонимный объект":

({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

Ваше глобальное пространство полностью незагрязнено. Это довольно просто. И вы можете легко воспользоваться неглобальным состоянием объекта.

Ответ 5

Я бы не сделал это как встроенную функцию. Это подталкивает к границам хорошего вкуса и на самом деле не дает вам ничего.

Если вам действительно нужно, есть arguments.callee, как в ответе Фабрицио. Однако это обычно считается нецелесообразным и запрещается в строгом режиме ECMAScript Fifth Edition. Хотя ECMA 3 и нестрогий режим не уходят, работают в строгом режиме promises более возможные языковые оптимизации.

Можно также использовать именованную встроенную функцию:

(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();

Однако наименования встроенных функциональных выражений также лучше избегать, так как IE JScript делает с ними некоторые плохие вещи. В приведенном выше примере foo неправильно загрязняет родительскую область в IE, а родительский foo представляет собой отдельный экземпляр для foo, видимый внутри foo.

Какую цель положить это в встроенную анонимную функцию? Если вы просто хотите избежать загрязнения родительской области, вы можете, конечно, скрыть свой первый пример внутри другой самозапуска-анонимной функции (пространство имен). Вам действительно нужно создавать новую копию nothing каждый раз вокруг рекурсии? Возможно, вам будет лучше с пространством имен, содержащим две простые взаимно-рекурсивные функции.

Ответ 6

(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();

Ответ 7

Вы можете сделать что-то вроде:

(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);

Ответ 8

Когда вы объявляете анонимную функцию следующим образом:

(function () {
    // Pass
}());

Считается выражением функции и имеет необязательное имя (которое вы можете использовать для вызова его изнутри самого себя. Но поскольку это выражение функции (а не выражение), оно остается анонимным (но имеет имя, которое вы можете вызвать). Таким образом, эта функция может называть себя:

(function foo () {
    foo();
}());
foo //-> undefined

Ответ 9

Почему бы не передать функцию самому функционалу?

    var functionCaller = function(thisCaller, data) {
        data = data + 1;
        var nothing = function() {
            thisCaller(thisCaller, data);
        };
        nothing();
    };
    functionCaller(functionCaller, data);

Ответ 10

В определенных ситуациях вам приходится полагаться на анонимные функции. Это рекурсивная функция 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

Ответ 11

Я не уверен, что ответ по-прежнему требуется, но это также можно сделать с помощью делегатов, созданных с помощью функции. function:

    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.

Ответ 12

Как и bobince написал, просто назовите свою функцию.

Но, я предполагаю, вы также захотите передать начальное значение и в конце концов прекратите свою работу!

var initialValue = ...

(function recurse(data){
    data++;
    var nothing = function() {
        recurse(data);
    }
    if ( ... stop condition ... )
        { ... display result, etc. ... }
    else
        nothing();
}(initialValue));

рабочий пример jsFiddle (использует данные + = данные для удовольствия)


Ответ 13

Мне понадобилась (или, скорее, хотелось) однолинейная анонимная функция, чтобы пройти вверх по объекту, создав строку, и обработала его следующим образом:

var cmTitle = 'Root' + (function cmCatRecurse(cmCat){return (cmCat == root) ? '' : cmCatRecurse(cmCat.parent) + ' : ' + cmCat.getDisplayName();})(cmCurrentCat);

который создает строку типа "Root: foo: bar: baz:..."

Ответ 14

С ES2015 мы можем немного поиграть с синтаксисом и злоупотреблять параметрами по умолчанию и thunks. Последние являются просто функциями без каких-либо аргументов:

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...

Ответ 15

Другой ответ, который не включает именованную функцию или 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

Ответ 16

Это переделка ответа 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

Не было необходимости разворачивать первую рекурсию. Функция, получающая себя в качестве эталона, возвращается к первозданному илу ООП.

Ответ 17

Это версия ответа @zem со стрелочными функциями.

Вы можете использовать комбинатор U или Y. Y combinator является самым простым в использовании.

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'))

Ответ 18

Еще одно решение Y-комбинатора, использующее ссылку rosetta-code (я думаю, что кто-то ранее упоминал ссылку где-то в stackOverflow.

Стрелки для анонимных функций более читабельны для меня:

var Y = f => (x => x(x))(y => f(x => y(y)(x)));

Ответ 19

Это может работать не везде, но вы можете использовать arguments.callee для обозначения текущей функции.

Итак, факториал можно было бы сделать так:

var fac = function(x) { 
    if (x == 1) return x;
    else return x * arguments.callee(x-1);
}