IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

ES3 en détail

Les fermetures en JavaScript

Ce tutoriel fait partie de la collection ES3 dans le détail et en constitue la partie 6. Dans ce tutoriel nous allons parler d'un des sujets le plus souvent questionnés en JavaScript : les fermetures (« closures »).

15 commentaires Donner une note à l´article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Ce sujet n'est pas nouveau et a été abordé maintes fois. Nous allons cependant essayer de l'aborder d'un point de vue théorique dans un premier temps et voir ensuite comment le JavaScript s'en occupe techniquement.

Il serait intéressant d'avoir pris connaissance en amont des deux tutoriels précédents dédiés à la chaîne des portées et à l'objet des variables qui aideront à la compréhension du présent tutoriel sans pour autant être indispensables à la compréhension globale.

II. Théorie générale

Avant d'aborder la discussion sur les fermetures en JavaScript, il semble important de définir un certain nombre de concepts de la théorie générale de la programmation fonctionnelle.

Comme vous le savez peut-être, dans les langages de programmation fonctionnelle (et JavaScript en est un), les fonctions sont des données. C'est-à-dire qu'elles peuvent être affectées à des variables, passées en tant qu'arguments dans les paramètres des fonctions ou être retournées par les fonctions. Ces fonctions ont des noms et des structures spéciales.

II-A. Définitions

Un argument fonctionnel (« functionnal argument » ou « funarg ») est un paramètre de fonction dont la valeur est elle-même une fonction.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
// création et déclaration d'un paramètre fonctionnel `funarg`
function umbrella(funarg) {
    funarg();
}

// appel et définition d'un argument fonctionnel `functionnalArgument`
umbrella(function functionnalArgument() {
    console.log('corporation');
});

L'argument fonctionnel de l'exemple ci-dessus est une expression de fonction passée à la fonction umbrella.

La fonction qui reçoit comme paramètre l'argument fonctionnel est appelée une fonction d'ordre supérieur (« higher-order function »).

Un autre nom donné à une fonction d'ordre supérieur est fonction fonctionnelle ou, plus près des mathématiques, un opérateur. Dans l'exemple ci-dessus, umbrella est donc une fonction d'ordre supérieur.

Comme mentionné plus haut, un argument fonctionnel peut-être non seulement passé à travers les paramètres des fonctions appelées, mais également retourné comme valeur par une autre fonction.

Une fonction qui retourne une autre fonction est appelée une fonction avec valeur fonctionnelle (« function valued »).

 
Sélectionnez
1.
2.
3.
4.
5.
(function functionValued() {
    return function () {
        console.log("appel d'une fonction retournée");
    };
})()();

Une fonction pouvant être utilisée comme une donnée standard (c'est-à-dire être passée en tant qu'argument ou recevoir des arguments fonctionnels ou encore être retournée en tant que valeur fonctionnelle) est appelée une fonction de première classe. En JavaScript, toutes les fonctions sont des fonctions de première classe.

Une fonction se recevant elle-même via un paramètre est une fonction autoapplicative (« self-applicative ») :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
(function selfApplicative(funarg) {

    if (funarg && funarg === selfApplicative) {
        console.log('auto-applicative');
        return;
    }

    selfApplicative(selfApplicative);
})();

et une fonction qui se retourne elle-même est, quant à elle, appelée une fonction autoréplicative (« self-replicative ») :

 
Sélectionnez
1.
2.
3.
4.
(function selfReplicative() {
    console.log('auto-réplicative');
    return selfReplicative;
})();

L'un des motifs les plus intéressants des fonctions autoréplicatives est leur forme déclarative fonctionnant avec un seul argument de collection au lieu d'accepter la collection elle-même :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
// fonction impérative qui
// accepte une collection

function starsList(modes) {
     modes.forEach(function (mode) {
         console.log(mode);
     });
}

// et s'utilisant ainsi
starsList(['jill', 'chris', 'barry']);
// `jill`
// `chris`
// `barry`

// vs

// forme déclarative des
// fonction autoreplicative

function stars(mode) {
     console.log(mode);
     return stars; // on retourne la fonction elle-même
}

// s'utilisant en *déclarant* les S.T.A.R.S

stars
    ('jill')
    ('chris')
    ('barry')

Cependant, dans la pratique, travailler avec des collections sera plus efficient et intuitif.

Les variables locales qui sont définies dans les arguments fonctionnels passés sont bien sûr accessibles lors de l'activation (appel) de la fonction. Et cela grâce à un objet des variables qui stocke les données du contexte et qui est créé à chaque fois en entrant dans le contexte :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
function umbrella(funarg) {

    // activation locale de funarg
    // la variable `localVar` est disponible
    // à l'intérieur de `funarg`

    funarg(10); // `20`
    funarg(20); // `30`

}

umbrella(function (localParam) {

    var localVar = 10;
    console.log(localParam + localVar);

});

Cependant, comme nous l'avons vu dans le tutoriel portant sur la chaîne des portées, les fonctions en JavaScript peuvent être encapsulées dans des fonctions parentes et utiliser des variables depuis des contextes parents. Quand une variable utilisée dans une fonction provient d'une fonction parente, cela conduit à une particularité connue sous le nom de problème de l'argument fonctionnel (« Funarg problem »).

II-B. Problème de l'argument fonctionnel

Dans un langage de programmation avec pile d'exécution, les variables locales des fonctions sont stockées dans la pile (« stack »). Cette pile est augmentée chaque fois qu'une fonction est appelée et ses variables et arguments sont stockés dans le niveau ajouté.

Après retour d'une fonction, les variables et arguments de cette fonction sont supprimés de la pile en même temps que le niveau associé. Ce modèle est une grosse restriction pour l'utilisation de fonctions en tant que valeurs fonctionnelles puisque la fonction qui stockait la valeur retournée n'est plus dans la pile.

Ce problème apparaît le plus souvent quand une fonction utilise des variables libres.

Une variable libre est une variable qui est utilisée par une fonction, mais qui n'est ni un paramètre de la fonction ni une variable locale de la fonction.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
function biohazard() {

    var freeVar = 10;

    function stars(localParam) {
        console.log(localParam + freeVar);
    }

    return stars;
}

var residentEvil = biohazard();
residentEvil(20); // `30`

Dans cet exemple, la variable freeVar est libre pour la fonction stars.

Si ce système utilisait un modèle avec pile d'exécution pour stocker les variables locales, cela signifierait qu'au retour de la fonction biohazard toutes les variables auraient été supprimées de la pile. Et cela aurait causé une erreur lors de l'activation de stars depuis l'extérieur.

Cependant pour ce cas particulier, dans une implémentation orientée pile, retourner la fonction stars n'aurait pas été possible du tout, puisque stars est aussi locale à biohazard et aurait donc également été supprimée au retour de la fonction biohazard.

Un autre problème avec les objets fonctionnels est lié au passage de fonctions en tant qu'arguments dans un système avec une implémentation de portée dynamique.

Pseudo-code
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
var z = 10

function zombi() {
    console.log(z)
}

zombi() // `10` avec une portée statique ou une portée dynamique

(function () {

    var z = 20
    zombi() // `10` avec une portée statique, `20` avec une portée dynamique

})()

// et la même chose en passant `zombi`
// en tant qu'argument du premier paramètre `funarg`

(function (funarg) {

    var z = 30
    funarg() // `10` avec une portée statique, `30` avec une portée dynamique

})(zombi)

Nous voyons que dans un système avec une portée dynamique, la résolution des variables est gérée grâce à une pile dynamique de variables. Donc les variables libres sont cherchées dans la chaîne dynamique de l'activation courante, mise en place lors de la phase d'appel de la fonction, et non dans une portée (lexicale) statique qui est créée lors de la phase de création de la fonction.

Donc même si z existe (contrairement à l'exemple précédent où la variable locale aurait été supprimée de la pile), une question se pose : quelle valeur de z (c'est-à-dire z depuis quel contexte, depuis quelle portée) devrait être utilisée dans les différents appels de la fonction zombi ?

Ces deux cas de figure lèvent deux types de problèmes : comment faire fonctionner les valeurs fonctionnelles retournées depuis des fonctions (« upward funarg ») et comment faire fonctionner les arguments fonctionnels passés à des fonctions (« downward funarg ») ?

Pour résoudre ces problèmes (et leurs variantes), le concept de fermeture a été proposé.

II-C. Une fermeture

Une fermeture (« closure ») est la combinaison d'un pend de code et des données du contexte dans lequel ce pend de code est créé. Imageons cela avec le code suivant :

Pseudo-code
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
var t = 20;

function mansion() {
    console.log(t) // variable libre `t` valant `20`
}

// Fermeture pour le pend de code `mansion`
mansionClosure = {
    call: mansion, // référence à la fonction
    lexicalEnvironment: { t: 20 } // contexte pour la recherche de variables libres
}

Même si le mot « lexical » est souvent omis quand on parle de portée lexicale, le fait est qu'une fermeture sauve les variables parentes dans un champ lexical dédié au pend de code, là où il est défini. Lors des prochaines activations de ce code, les variables libres seront cherchées à travers ce contexte lexical fermé (et ainsi comme nous l'avons vu dans l'exemple plus haut, la variable z sera toujours résolue à 10 pour une portée statique).

Dans la définition nous avons utilisé le terme générique de « pend de code », mais celui-ci peut être plus précis en fonction du langage. En JavaScript par exemple, le terme est complètement remplaçable par « fonction ». Mais cela n'est pas nécessairement le cas de tous les langages, par exemple en Ruby, où les fermetures peuvent être appliquées à autre chose que des fonctions.

Comme type d'implémentation pour le stockage de variables après la destruction d'un contexte, l'implémentation basée sur une pile ne convient pas du tout (car cela contredit la définition d'une structure basée sur une pile). Dans ce cas, les données gérées par la fermeture d'un contexte parent peuvent être sauvées dynamiquement en mémoire comme dans un tas (« heap »), c'est-à-dire dans une implémentation basée sur un tas. Une telle implémentation utilise un ramasse-miettes (« garbage collector ») et des références par comptage. Ces systèmes sont moins rapides que les systèmes basés sur une pile. Cependant, les implémentations peuvent toujours optimiser cela en vérifiant lors de la phase d'analyse d'une fonction quelles variables libres sont utilisées et décider en fonction de cela de les placer dans une pile ou dans un tas.

III. Les fermetures en JavaScript

Maintenant que nous avons discuté de la théorie, nous allons parler des fermetures (« closures ») spécifiquement dans le contexte du JavaScript. Il est nécessaire de noter ici que le JavaScript utilise uniquement une portée (lexicale) statique (alors que dans certains langages, comme en Perl, les variables peuvent être déclarées en utilisant des portées dynamiques ou statiques).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
var g = 10;

function virus() {
    console.log(g);
}

(function (funarg) {

    var g = 20;

    // la variable `g` pour le `funarg` est sauvée de manière statique
    // dans le contexte lexical dans lequel elle est créée

  funarg(); // `10`, mais pas `20`

})(virus);

Techniquement, les variables d'un contexte parent sont sauvées dans la propriété interne [[Scope]] de la fonction. Aussi si vous souhaitez complètement comprendre [[Scope]] et la chaîne des portées, cela a été discuté en détail dans ce tutoriel. Ainsi les problématiques de fermetures devraient disparaître d'elles-mêmes.

En s'appuyant sur l'algorithme de création des fonctions, nous voyons que toutes les fonctions en JavaScript sont des fermetures, puisque toutes créent une chaîne des portées du contexte parent. Ce qu'il faut retenir étant qu'au moment où une fonction est activée (appelée), la portée parente est déjà sauvée depuis le moment de sa création.

Pseudo-code
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
var g = 10;

function virus() {
    console.log(g);
}

// `virus` est une fermeture
virus = virusFunctionObject = {
    [[Call]]: <pend de code de `virus`>,
    [[Scope]]: [{
        g: 10
    } // === `GO`],
    <...> // autres propriétés
};

Pour des questions d'optimisations, quand une fonction n'utilise pas de variables libres, l'implémentation peut ne pas sauver de chaîne des portées. Cependant rien n'est mentionné à ce propos dans la spécification ECMA-262-3. C'est donc une liberté prise au niveau de l'implémentation (et par l'algorithme technique) car « toutes les fonctions sauvent une chaîne des portées dans leur propriété interne [[Scope]] lors de la création ».

Plusieurs implémentations permettent un accès direct à cette portée fermée. Par exemple dans Rhino, une propriété non standard __parent__ correspond à la propriété interne [[Scope]][0] dont nous avons discuté dans le chapitre sur l'objet des variables :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
var GO = this;
var t = 10;

var virus = (function () {

    var g = 20;

    return function () {
        console.log(g);
    };

})();

virus(); // `20`
console.log(virus.__parent__.g); // `20`

virus.__parent__.g = 30;
virus(); // `30`

// Nous pouvons nous déplacer à travers la chaîne des portées jusqu'au bout
console.log(virus.__parent__.__parent__ === GO); // `true`
console.log(virus.__parent__.__parent__.t); // `10`

III-A. Une valeur [[Scope]] pour tous

Il est nécessaire de noter que la propriété fermée [[Scope]] en JavaScript est le même objet pour plusieurs des fonctions internes créées dans le contexte parent. Cela signifie que la modification d'une variable dans une fermeture va se refléter lors de la lecture de cette variable depuis une autre fermeture.

En clair : toutes les fonctions internes partagent la même chaîne des portées parente.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
var tyran;
var nemesis;

function weapons() {

    var x = 1;

    tyran = function () { return ++x; };
    nemesis = function () { return --x; };

    x = 2; // affectation de AO(<weapons>)[`x`], qui est dans la propriété `[[Scope]]` de chaque fermeture

    console.log(tyran()); // `3`, via tyran.[[Scope]]
}

weapons();

console.log(tyran()); // `4`, via tyran.[[Scope]]
console.log(nemesis()); // `3`, via nemesis.[[Scope]]

Il y a une erreur très répandue liée à cette fonctionnalité. Souvent les développeurs obtiennent un résultat qu'ils n'attendaient pas quand ils créent des fonctions dans des boucles, essayant d'associer à chaque fonction de la boucle une variable de comptage, s'attendant à ce que chaque fonction garde sa « propre » valeur.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
var data = [];

for (var k = 0; k < 3; k++) {
    data[k] = function () {
        var x = true;
        console.log(k);
    };
}

data[0](); // `3`, et pas `0`
data[1](); // `3`, et pas `1`
data[2](); // `3`, et pas `2`

Ce comportement est expliqué par l'exemple précédent. Une même portée liée au contexte est créée pour ces trois fonctions. Chaque fonction la référence dans sa propriété interne [[Scope]], et la variable k depuis cette portée parente peut-être facilement changée.

Regardez plutôt ce qu'il se passe étape par étape pour la chaîne des portées :

Phase d'entrée dans le contexte global :

Pseudo-code
Sélectionnez
1.
2.
3.
4.
// activeGlobalContext.Scope = activeGlobalContext.AO
activeGlobalContext.Scope = [
    { data: <référence à `data`>, k: undefined }
]

Phase d'exécution du contexte global :

Pseudo-code
Sélectionnez
1.
2.
3.
4.
// activeGlobalContext.Scope = activeGlobalContext.AO
activeGlobalContext.Scope = [
    { data: [<...>], k: 3 }
]

Phase d'entrée de la fonction data[1] (par exemple) :

Pseudo-code
Sélectionnez
1.
2.
3.
4.
5.
// activeFunctionContext.Scope = activeFunctionContext.AO + data[1].[[Scope]]
activeFunctionContext.Scope = [
    { x: undefined }, // objet d'activation courant `AO`
    { data: [<...>], k: 3 } // l'objet global (`activeFunctionContext.AO`)
]

Phase d'exécution de la fonction data[1] (par exemple) :

Pseudo-code
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
// activeFunctionContext.Scope = activeFunctionContext.AO + data[1].[[Scope]]
activeFunctionContext.Scope = [
    { x: true },
    { data: [<...>], k: 3 }
]

// c.-à-d

data[1].[[Scope]].k === globalContext.Scope.k === `3`

Au moment de l'activation de la fonction, c'est la dernière valeur de k affectée qui s'affiche, c'est-à-dire 3.

Cela est dû au fait que toutes les variables sont créées avant l'exécution du code, c'est-à-dire lors de la phase d'entrée dans le contexte. Ce comportement est aussi connu sous le nom de hissage (« hoisting ») de variable.

La création d'un contexte fermé additionnel peut résoudre ce problème :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
var data = [];

for (var k = 0; k < 3; k++) {
    data[k] = (function helper(x) {
        return function () {
            console.log(x);
        };
    })(k); // passage de la valeur `k` pour chaque objet des variables
}

// maintenant c'est le résultat souhaité
data[0](); // `0`
data[1](); // `1`
data[2](); // `2`

Regardons ce qu'il se passe dans ce cas.

En premier lieu, la fonction helper est créée et immédiatement activée avec comme premier argument k.

Puis, la valeur retournée par la fonction helper est aussi une fonction, contenant les éléments du tableau data original.

Cette technique produit les effets suivants : à l'activation, helper crée à chaque fois un nouvel objet d'activation qui contient le paramètre x, et la valeur de ce paramètre est la valeur de l'argument k passé.

Donc, les propriétés [[Scope]] de chaque fonction retournée sont les suivantes lors de la phase d'exécution :

Pseudo-code
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
data[0].[[Scope]] === [
    { x: 0 },
    { data: [<...>], k: 3 }
]

data[1].[[Scope]] === [
    { x: 1 },
    { data: [...], k: 3 }
]

data[2].[[Scope]] === [
    { x: 2 },
    { data: [...], k: 3 }
]

Nous voyons maintenant que la propriété [[Scope]] des fonctions a une référence sur la valeur souhaitée via la variable x qui est capturée par la portée additionnelle créée.

Notons que les fonctions retournées gardent toujours leur référence à la variable k. La variable k garde toujours à travers toutes les fonctions la valeur 3.

Parfois les fermetures JavaScript incomplètes sont réduites au motif montré plus haut, avec la création de fonctions additionnelles de capture des valeurs désirées. D'un point de vue pratique, ce motif se doit d'exister, mais d'un point de vue théorique comme mentionné : toutes les fonctions en JavaScript sont des fermetures, et pas seulement dans ce cas de figure.

Le motif décrit plus haut n'est pas le seul utilisable. Pour conserver la valeur souhaitée de la variable k il est aussi possible, par exemple, d'utiliser cette approche :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
var data = [];

for (var k = 0; k < 3; k++) {
    (data[k] = function () {
        console.log(arguments.callee.x);
    }).x = k; // sauver `k` en tant que propriété de la fonction
}

// et tout est encore correct
data[0](); // `0`
data[1](); // `1`
data[2](); // `2`

Notons que ES6 standardise la portée de structure, qui peut être mise en place en utilisant les mots clés let ou const en tant que déclaration de variable. L'exemple ci-dessus peut alors simplement être réécrit ainsi :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
let data = [];

for (let k = 0; k < 3; k++) {
    data[k] = function () {
        console.log(k);
    };
}

// toujours des sorties correctes
data[0](); // `0`
data[1](); // `1`
data[2](); // `2`

III-B. Argument fonctionnel et return

Une autre fonctionnalité est le retour des fermetures. En JavaScript, une instruction avec le mot-clé return dans une fermeture rend le contrôle de flux depuis un contexte appelant.

Voici un exemple pour comprendre le comportement standard de return en JavaScript :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
function getElement() {

    [1, 2, 3].forEach(function (element) {

        if (element % 2 == 0) {
            // retour de la fonction « forEach »,
            //, mais pas retour de la fonction `getElement`
            console.log('trouvé: ' + element); // trouvé: 2
            return element;
        }

    });

    return null;
}

console.log(getElement()); // `null`, et non `2`

Ainsi en JavaScript, lancer et attraper certaines exceptions peut aider :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
function getElement() {

    var $break = {};

    try {

        [1, 2, 3].forEach(function (element) {

            if (element % 2 === 0) {
                // « return » pour getElement depuis cette fermeture
                console.log('trouvé: ' + element); // trouvé: 2
                $break.data = element;
                throw $break;
            }

        });

    } catch (e) {
        if (e === $break) {
            return $break.data;
        }
    }

    return null;
}

console.log(getElement()); // `2`

III-C. Théorie et exception

Comme nous l'avons noté, les développeurs réduisent souvent les fermetures à de simples fonctions internes retournées par leurs contextes parents. Ceci réduit les fermetures à être exploitables uniquement dans les fonctions anonymes.

Laissez-moi vous le dire à nouveau : toutes les fonctions, indépendamment de leur type (expressions nommées et anonymes ou déclarations), parce qu'elles possèdent une chaîne des portées, sont des fermetures.

Une exception à cette règle subsiste pour les fonctions créées via le constructeur Function dont la propriété [[Scope]] ne contient que l'objet global.

IV. Usage pratique des fermetures

Dans la pratique les fermetures permettent la création de structures élégantes, favorisant la personnalisation de différents calculs définis par un argument fonctionnel. En voici des exemples non exhaustifs.

IV-A. Argument fonctionnel

Voici un exemple avec la méthode sort des tableaux qui accepte en tant que premier paramètre un argument fonctionnel de « condition de tri » :

 
Sélectionnez
1.
2.
3.
[1, 2, 3].sort(function (a, b) {
    // ... conditions de tri de votre choix
});

Voici un autre exemple avec la méthode find. Il est parfois intéressant d'utiliser des fonctions de recherche en utilisant des arguments fonctionnels définissant les conditions de recherche :

 
Sélectionnez
1.
2.
3.
someCollection.find(function (element) {
    return element.someProperty === 'condition de recherche';
});

IV-B. Association fonctionnelle

Voici un exemple de ce que l'on appelle l'association fonctionnelle (« mapping functionnals ») avec la méthode map d'un tableau. Celle-ci va associer à un nouveau tableau une valeur calculée à chaque élément :

 
Sélectionnez
1.
2.
3.
[1, 2, 3].map(function (element) {
    return element * 2;
}); // `[2, 4, 6]`

IV-C. Boucle de fonction

Il est aussi intéressant d'autres fois d'appliquer les fonctions fonctionnelles, par exemple dans une méthode forEach qui applique des instructions pour chaque élément d'un tableau :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
[1, 2, 3].forEach(function (element) {
    if (element % 2 !== 0) {
        console.log(element);
    }
});
// affiche `1`
// affiche `3`

IV-D. apply et call

Au passage, les méthodes de fonction apply et call utilisent également des arguments fonctionnels. Nous avons déjà discuté de ces méthodes dans une note à propos de la valeur de this mais ici, nous allons voir leurs rôles avec les arguments fonctionnels ou fonctions appliquées en tant qu'argument (à une liste d'arguments avec apply et des positions d'argument avec call) :

 
Sélectionnez
1.
2.
3.
4.
(function () {
    console.log([].join.call(arguments, ';'));
}).apply(this, [1, 2, 3]);
// affiche `1;2;3`

IV-E. Appels différés

Un autre point important des fermetures est la possibilité des appels différés (« deferred calls ») :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
var a = 10;
setTimeout(function () {
    console.log(a);
}, 1000);
// une seconde d'attente...
// ...puis affichage de `10`

IV-F. Fonction de rappel

Plus simplement, un cas d'usage répandu des fermetures est celui des fonctions de rappel (« callback functions ») :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
// ...
var x = 10;

xmlHttpRequestObject.onreadystatechange = function () {
  // la fonction de rappel est appelée en différé,
  // quand les données sont prêtes.
  // La variable `x` est ici disponible indépendamment
  // du fait que lorsque le contexte interne existe,
  // l'exécution du code externe est déjà finie.
  console.log(x); // `10`
};
// ...

IV-G. Encapsulations privées

Les fermetures servent également à « masquer » des variables dans une portée encapsulante lors de l'exécution d'instructions :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
var residentEvil = {};

// initialisation
(function (object) {

    var veronica = 10;

    object.getVirus = function _getVirus() {
        return veronica;
    };

})(residentEvil);

console.log(residentEvil.getVirus()); // retourne la valeur `veronica` enfermée : `10`
console.log(veronica); // « erreur : `veronica` n'est pas défini(e) »

V. Conclusion

Ce tutoriel vous en a dit plus à propos de la théorie générale des fermetures afin de mieux aborder son application en JavaScript même si le fait d'avoir étudié la chaîne des portées en amont nous a bien facilité la tâche. Nous entrerons prochainement dans le détail de la programmation orientée objet dans le domaine du JavaScript !

VI. Remerciements

Nous remercions Bruno Lesieur qui nous a autorisés à publier ce tutoriel.

Nous remercions également Laethy pour la mise au gabarit et Genthial pour la correction orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2018 Nom Auteur. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.