Mogę utworzyć funkcję rekurencyjną w zmiennej takiej jak ta:
/* Count down to 0 recursively.
*/
var functionHolder = function (counter) {
output(counter);
if (counter > 0) {
functionHolder(counter-1);
}
}
Dzięki temu functionHolder(3);
wyjdzie 3
2
1
0
. Powiedzmy, że wykonałem następujące czynności:
var copyFunction = functionHolder;
copyFunction(3);
wyświetli się 3
2
1
0
jak powyżej. Jeśli potem zmieniłem functionHolder
w następujący sposób:
functionHolder = function(whatever) {
output("Stop counting!");
Wtedy functionHolder(3);
dał Stop counting!
, zgodnie z oczekiwaniami.
copyFunction(3);
teraz podaje 3
Stop counting!
to, do czego się odnosi functionHolder
, a nie funkcję (na którą sama wskazuje). Może to być pożądane w pewnych okolicznościach, ale czy istnieje sposób na napisanie funkcji tak, aby wywoływała samą siebie, a nie zmienną, która ją przechowuje?
To znaczy, czy można zmienić tylko linię functionHolder(counter-1);
, aby przejście przez wszystkie te kroki nadal dawało, 3
2
1
0
gdy dzwonimy copyFunction(3);
? Próbowałem, this(counter-1);
ale to daje mi błąd this is not a function
.
źródło
Odpowiedzi:
Używanie nazwanych wyrażeń funkcyjnych:
Możesz nadać wyrażeniu funkcyjnemu nazwę, która jest faktycznie prywatna i jest widoczna tylko z wnętrza funkcji, jeśli siebie:
var factorial = function myself (n) { if (n <= 1) { return 1; } return n * myself(n-1); } typeof myself === 'undefined'
Tutaj
myself
jest widoczny tylko wewnątrz samej funkcji .Możesz użyć tej prywatnej nazwy do rekurencyjnego wywołania funkcji.
Zobacz
13. Function Definition
specyfikację ECMAScript 5:Należy zauważyć, że Internet Explorer do wersji 8 nie zachowuje się poprawnie, ponieważ nazwa jest faktycznie widoczna w otaczającym środowisku zmiennych i odwołuje się do duplikatu rzeczywistej funkcji (patrz komentarz patryka dw poniżej).
Korzystanie arguments.callee:
Alternatywnie możesz użyć,
arguments.callee
aby odnieść się do bieżącej funkcji:var factorial = function (n) { if (n <= 1) { return 1; } return n * arguments.callee(n-1); }
Piąta edycja ECMAScript zabrania używania arguments.callee () w trybie ścisłym , jednakże:
źródło
myself
jest faktycznie widoczny w otaczającym środowisku zmiennych i odwołuje się do duplikatu rzeczywistejmyself
funkcji. Powinieneś być w stanie ustawić jednak odniesienie zewnętrznenull
.return n * myself(n-1);
?Możesz uzyskać dostęp do samej funkcji za pomocą
arguments.callee
[MDN] :if (counter>0) { arguments.callee(counter-1); }
Spowoduje to jednak przerwanie w trybie ścisłym.
źródło
TypeError
, ale nie znalazłem niczego, co oficjalnie stwierdza, żearguments.callee
(lub jakiekolwiek naruszenie trybu ścisłego) jest przestarzałe poza „trybem ścisłym”.Możesz użyć kombinatora Y: ( 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)));
Możesz go użyć w ten sposób:
// 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); } });
źródło
Wiem, że to stare pytanie, ale pomyślałem, że przedstawię jeszcze jedno rozwiązanie, którego można by użyć, gdybyś chciał uniknąć używania nazwanych wyrażeń funkcyjnych. (Nie mówię, że powinieneś lub nie powinieneś ich unikać, po prostu przedstawiając inne rozwiązanie)
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);
źródło
Oto jeden bardzo prosty przykład:
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);
Zwróć uwagę, że
counter
liczy się „wstecz” w odniesieniu do tegoslug
, jaka jest wartość. Dzieje się tak z powodu pozycji, w której rejestrujemy te wartości, ponieważ funkcja powtarza się przed rejestrowaniem - więc zasadniczo zagnieżdżamy się głębiej i głębiej w stosie wywołań, zanim nastąpi rejestracja.Gdy rekursja dotrze do ostatniego elementu stosu wywołań, trampoliny „wychodzą” z wywołań funkcji, podczas gdy pierwsza inkrementacja
counter
występuje w ostatnim zagnieżdżonym wywołaniu.Wiem, że to nie jest "poprawka" w kodzie Pytającego, ale biorąc pod uwagę tytuł, pomyślałem, że generalnie zilustruję Rekursję, aby lepiej zrozumieć rekurencję.
źródło