Je peux créer une fonction récursive dans une variable comme ceci:
/* Count down to 0 recursively.
*/
var functionHolder = function (counter) {
output(counter);
if (counter > 0) {
functionHolder(counter-1);
}
}
Avec ceci, functionHolder(3);
produirait 3
2
1
0
. Disons que j'ai fait ce qui suit:
var copyFunction = functionHolder;
copyFunction(3);
afficherait 3
2
1
0
comme ci-dessus. Si j'ai ensuite changé functionHolder
comme suit:
functionHolder = function(whatever) {
output("Stop counting!");
Alors, functionHolder(3);
donnerait Stop counting!
, Comme prévu.
copyFunction(3);
donne maintenant 3
Stop counting!
car il fait référence à functionHolder
, pas à la fonction (à laquelle il renvoie lui-même). Cela peut être souhaitable dans certaines circonstances, mais existe-t-il un moyen d'écrire la fonction pour qu'elle s'appelle elle-même plutôt que la variable qui la contient?
Autrement dit, est-il possible de changer niquement la ligne functionHolder(counter-1);
afin que toutes ces étapes donnent encore 3
2
1
0
Lorsque nous appelons copyFunction(3);
? J'ai essayé this(counter-1);
mais cela me donne l'erreur this is not a function
.
Vous pouvez attribuer à une expression de fonction un nom réellement privé et visible uniquement à l'intérieur de la fonction si
var factorial = function myself (n) {
if (n <= 1) {
return 1;
}
return n * myself(n-1);
}
typeof myself === 'undefined'
Ici, myself
est visible uniquement à l'intérieur de la fonction elle-même.
Vous pouvez utiliser ce nom privé pour appeler la fonction de manière récursive.
Voir 13. Function Definition
de la spécification ECMAScript 5:
L'identifiant dans une FunctionExpression peut être référencé depuis l'intérieur du FunctionBody de FunctionExpression pour permettre à la fonction de s'appeler de manière récursive. Cependant, contrairement à FunctionDeclaration, l'identificateur dans une FunctionExpression ne peut pas être référencé à partir de et n'affecte pas la portée englobant la FunctionExpression.
Veuillez noter qu’Internet Explorer jusqu’à la version 8 ne se comporte pas correctement car son nom est réellement visible dans l’environnement variable englobant et renvoie à un duplicata de la fonction réelle (voir patrick dw commentaires ci-dessous).
Sinon, vous pouvez utiliser arguments.callee
pour faire référence à la fonction actuelle:
var factorial = function (n) {
if (n <= 1) {
return 1;
}
return n * arguments.callee(n-1);
}
La 5ème édition de ECMAScript interdit l’utilisation de arguments.callee () dans mode strict , cependant:
(De MDN ): En code normal, arguments.callee fait référence à la fonction englobante. Ce cas d'utilisation est faible: nommez simplement la fonction englobante! De plus, arguments.callee entrave considérablement les optimisations telles que les fonctions en ligne, car il doit être possible de fournir une référence à la fonction non en ligne si l'on accède à arguments.callee. arguments.callee pour les fonctions en mode strict est une propriété non supprimable qui est renvoyée lorsqu'elle est définie ou extraite.
Vous pouvez accéder à la fonction elle-même en utilisant arguments.callee
[MDN] :
if (counter>0) {
arguments.callee(counter-1);
}
Cela va casser en mode strict, cependant.
Je sais que la question est ancienne, mais je pensais présenter une solution supplémentaire qui pourrait être utilisée si vous ne souhaitez pas utiliser les expressions de fonction nommée. (Ne pas dire que vous devriez ou ne pas les éviter, mais simplement présenter une autre solution)
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);
Vous pouvez utiliser le combineur 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)));
Et vous pouvez l'utiliser comme ceci:
// 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);
}
});
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);
Notez que le counter
compte "à rebours" en ce qui concerne la valeur de slug
. Ceci est dû à la position à laquelle nous enregistrons ces valeurs, car la fonction se répète avant d’enregistrer, nous continuons donc à imbriquer de plus en plus profondément dans la pile d'appels avant la journalisation a lieu.
Une fois que la récursivité rencontre le dernier élément de la pile d’appel, il trampoline "en", alors que le premier incrément de counter
se produit à l'intérieur du dernier appel imbriqué.
Je sais que ce n'est pas une "solution" au code du questionneur, mais vu le titre, je pensais que je donnerais un exemple générique Récursivité pour une meilleure compréhension de la récursivité , carrément.