Вызов функции javascript рекурсивно - PullRequest
82 голосов
/ 15 августа 2011

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

/* 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, а не к функции (на которую она сама указывает).Это может быть желательно в некоторых обстоятельствах, но есть ли способ написать функцию так, чтобы она вызывала себя, а не переменную, в которой она хранится?

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

Ответы [ 5 ]

139 голосов
/ 15 августа 2011

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

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

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

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

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

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

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

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

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

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

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

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

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

8 голосов
/ 15 августа 2011

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

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

Это прервется в строгом режимеОднако.

5 голосов
/ 30 мая 2015

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

  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 голосов
/ 29 сентября 2015

Вы можете использовать Y-комбинатор: ( Википедия )

// 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);
  }
});
3 голосов
/ 18 октября 2014

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

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.Это происходит из-за позиции, в которой мы записываем эти значения, так как функция рекурсивно перед регистрацией - таким образом, мы по существу продолжаем вкладывать все глубже и глубже в стек вызовов до ведение журнала.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...