Вызов функции javascript рекурсивно

Я могу создать рекурсивную функцию в переменной следующим образом:

/* Count down to 0 recursively.
 */
var functionHolder = function (counter) {
    output(counter);
    if (counter > 0) {
        functionHolder(counter-1);
    }
}

При этом functionHolder(3); выводит 3 2 1 0. Скажем, я сделал следующее:

var copyFunction = functionHolder;

copyFunction(3); выводит 3 2 1 0, как указано выше. Если бы я затем изменил functionHolder следующим образом:

functionHolder = function(whatever) {
    output("Stop counting!");

Тогда functionHolder(3); даст Stop counting!, как и ожидалось.

copyFunction(3); теперь дает 3 Stop counting!, поскольку он относится к functionHolder, а не к функции (на которую она указывает). Это может быть желательно в некоторых случаях, но есть ли способ написать функцию так, чтобы она вызывала себя, а не переменную, которая ее удерживает?

То есть, возможно ли изменить только строку functionHolder(counter-1);, так что, проходя все эти шаги, все равно дает 3 2 1 0, когда мы вызываем copyFunction(3);? Я пробовал this(counter-1);, но это дает мне ошибку this is not a function.

Ответ 1

Использование названных выражений функции:

Вы можете дать выражение функции имя, которое на самом деле private, и видно только изнутри функции ifself:

var factorial = function myself (n) {
    if (n <= 1) {
        return 1;
    }
    return n * myself(n-1);
}
typeof myself === 'undefined'

Здесь myself отображается только внутри самой функции.

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

См. 13. Function Definition спецификации ECMAScript 5:

Идентификатор в выражении Function можно ссылаться изнутри FunctionExpression FunctionBody, чтобы позволить функции вызывать себя рекурсивно. Однако, в отличие от FunctionDeclaration, идентификатор в выражении Function не может ссылаться и не влияет на область применения Expression.

Обратите внимание, что Internet Explorer до версии 8 не ведет себя корректно, так как имя действительно отображается в окружении, в котором находится переменная, и ссылается на дубликат фактической функции (см. комментарий patrick dw ниже).

Использование arguments.callee:

В качестве альтернативы вы можете использовать arguments.callee для ссылки на текущую функцию:

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

Пятое издание ECMAScript запрещает использование arguments.callee() в строгом режиме, однако:

(Из MDN): В обычных аргументах code.callee относится к закрывающей функции. Этот случай использования слабый: просто назовите функцию включения! Кроме того, arguments.callee существенно затрудняет оптимизацию, например, встроенные функции, поскольку необходимо предоставить ссылку на не-встроенную функцию, если access.callee обращается к ней. arguments.callee для функций строгого режима - это свойство, не подлежащее удалению, которое генерируется при установке или восстановлении.

Ответ 2

Вы можете получить доступ к самой функции, используя arguments.callee [MDN]:

if (counter>0) {
    arguments.callee(counter-1);
}

Однако это будет нарушено в строгом режиме.

Ответ 3

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

  var fn = (function() {
    var innerFn = function(counter) {
      console.log(counter);

      if(counter > 0) {
        innerFn(counter-1);
      }
    };

    return innerFn;
  })();

  console.log("running fn");
  fn(3);

  var copyFn = fn;

  console.log("running copyFn");
  copyFn(3);

  fn = function() { console.log("done"); };

  console.log("fn after reassignment");
  fn(3);

  console.log("copyFn after reassignment of fn");
  copyFn(3);

Ответ 4

Вот один очень простой пример:

var counter = 0;

function getSlug(tokens) {
    var slug = '';

    if (!!tokens.length) {
        slug = tokens.shift();
        slug = slug.toLowerCase();
        slug += getSlug(tokens);

        counter += 1;
        console.log('THE SLUG ELEMENT IS: %s, counter is: %s', slug, counter);
    }

    return slug;
}

var mySlug = getSlug(['This', 'Is', 'My', 'Slug']);
console.log('THE SLUG IS: %s', mySlug);

Обратите внимание, что counter считает "назад" в отношении значения slug. Это происходит из-за позиции, в которой мы регистрируем эти значения, поскольку функция повторяется перед протоколированием - поэтому мы по существу сохраняем вложенность глубже и глубже в запись до. p >

Как только рекурсия встречается с последним элементом стека вызовов, он трамплирует "выход" из вызовов функций, тогда как первый приращение counter происходит внутри последнего вложенного вызова.

Я знаю, что это не "исправление" в вопросительном коде, но, учитывая заголовок, я думал, что в целом я хотел бы представить Рекурсию для лучшего понимания рекурсии, прямо.

Ответ 5

Вы можете использовать Y-combinator: (Wikipedia)

// ES5 syntax
var Y = function Y(a) {
  return (function (a) {
    return a(a);
  })(function (b) {
    return a(function (a) {
      return b(b)(a);
    });
  });
};

// ES6 syntax
const Y = a=>(a=>a(a))(b=>a(a=>b(b)(a)));

// If the function accepts more than one parameter:
const Y = a=>(a=>a(a))(b=>a((...a)=>b(b)(...a)));

И вы можете использовать его как это:

// ES5
var fn = Y(function(fn) {
  return function(counter) {
    console.log(counter);
    if (counter > 0) {
      fn(counter - 1);
    }
  }
});

// ES6
const fn = Y(fn => counter => {
  console.log(counter);
  if (counter > 0) {
    fn(counter - 1);
  }
});