Qu'est ce qu'une closure ?

Quelle est la différence entre une closure et une fonction anonyme ?

Cet article, qui n'est pas spécifique à un langage particulier, a pour but d'expliquer ce qu'est une closure et la différence avec une fonction anonyme. Avant tout, sachez que tout le monde n'est pas d'accord sur le sujet et que, par exemple, mon avis est différent de ce que vous pourriez lire dans la documentation officielle PHP. Je vous invite fortement à partager votre vision des choses dans les commentaires.

Pour PHP, une fonction anonyme est une closure

PHP considère qu'une fonction anonyme est une closure. Sur la documentation des fonctions anonymes on peut ainsi lire : "les fonctions anonymes, aussi appelées fermetures ou closures permettent la création de fonctions sans préciser leur nom". En PHP, toutes les fonctions anonymes sont des instances de la classe Closure.

Une fonction anonyme en PHP

$anonymous = function(){}; var_dump($anonymous); if ($anonymous instanceOf Closure) { echo 'Une fonction anonyme est une instance de la classe Closure'; } /* Sortie : * object(Closure)#1 (0) { * } * Une fonction anonyme est une instance de la classe Closure. */

Une closure et une fonction anonyme sont deux concepts différents

John Resig, le créateur de JQuery, défini une closure dans son livre "Secrets of the javascript ninja" (chapitre 5, page 94) :

"a closure is the scope created when a function is declared that allows the function to access and manipulate variables that are external to that function."

Traduction : "une closure est le scope créé lors de la déclaration d'une fonction qui autorise la fonction à accéder et à manipuler les variables qui sont externes à cette fonction".

Une closure ne désigne pas la fonction elle-même mais la sauvegarde du contexte (environnement lexical) auquel cette fonction avait accès lors de sa création. Cette sauvegarde empêche que les variables ne soient désallouées même si leur scope est terminé et qu'elles ne sont plus directement visibles.

Le premier exemple montre une closure qui n'utilise pas de fonction anonyme et ne retourne pas de fonction avec le mot clé "return".

Une closure en javascript sans fonction anonyme

var variableDansLeScopeGlobal = 'tuto2dev'; var variableQuonUtiliseraPlusTard; function fonctionExterne() { var variableDansLeScopeInterne = 'nico'; function fonctionInterne() { /* La fonction interne a accès aux variables déclarées dans les scopes parents : */ console.log('fonction interne :', variableDansLeScopeGlobal, variableDansLeScopeInterne); } variableQuonUtiliseraPlusTard = fonctionInterne; } /* La fonction externe est lancée, la fonction interne est créée et avec elle une closure */ fonctionExterne(); /* La variable interne ne devrait pas être visible, pourtant elle n'est pas désallouée et la fonction interne peut l'utiliser */ variableQuonUtiliseraPlusTard(); /* Preuve que la variable du scope interne n'est pas visible depuis le scope global : */ console.log('scope global :', variableDansLeScopeGlobal, variableDansLeScopeInterne); /* Sortie : * fonction interne : tuto2dev nico * Uncaught ReferenceError: variableDansLeScopeInterne is not defined */

Pour information, en javascript l'utilisation du mot clé "function" déclenche automatiquement la création d'une closure. En revanche, le constructeur de l'objet "Function" ne crée pas de closure, la fonction ne peut pas avoir accès au scope externe.

Le second exemple utilise une fonction anonyme et pourtant ne crée pas de closure :

Une fonction anonyme qui ne crée pas de closure

var externe='tuto2dev'; var fonctionAnonyme = new Function("var interne='nico';console.log(interne);console.log(externe)"); fonctionAnonyme(); /* Sortie : * nico * Uncaught ReferenceError: externe is not defined */

En javascript, il n'y a pas de notion de visibilité des variables, les closures permettent de simuler les attributs privés.

Des attributs privés en javascript à l'aide des closures

function Tuto2Dev() { var auteur = 'nico'; this.getAuteur = function(){ return auteur; }; this.setAuteur = function(nouvel_auteur){ auteur = nouvel_auteur; }; } var tuto2dev = new Tuto2Dev(); console.log(tuto2dev.getAuteur()); /* Sortie : * nico */

Ce dernier exemple nous apprend quelque chose de crucial sur les closures, notamment pourquoi ça se nomme closure ! Le scope est fermé, deux appels de la fonction crées deux scopes bien distincts. Ainsi, si je crée deux instances de Tuto2Dev et que je modifie l'auteur d'un des objets, l'auteur de la seconde instance reste inchangé. Un second exemple qui prouve ce phénomène :

Chaque fonction correspond à une closure différente

function incrementerFactory() { var i = 0; return function() { return i++; } } var incrementer = incrementerFactory(); console.log(incrementer()); // 0 console.log(incrementer()); // 1 var incrementer2 = incrementerFactory(); console.log(incrementer2()); // 0

Vous noterez que même si chaque fonction possède sa propre closure, les variables des scopes parents sont mutualisées, ce qui explique comment fonctionne setAuteur dans l'exemple précédent.

Avec la console Google Chrome, vous pourrez observer les variables externes intégrées dans une closure. Allez dans l'onglet "Sources" et exécutez le code ci-dessous. Dans la colonne de droite, dans "Scope Variables" vous pourrez observer le résultat.

Voir une closure avec Google Chrome

function fonctionExterne() { var variableDansLeScopeInterne = 'nico'; return function fonctionInterne(param) { debugger; console.log(variableDansLeScopeInterne); }; } var fInterne = fonctionExterne(); fInterne('tuto2dev'); /* Résultat : * Local * param: "tuto2dev" * this: Window * Closure * variableDansLeScopeInterne: "nico" * Global * ... */

Si l'on suit la définition de la closure donnée précédemment, en PHP ça ressemble à ceci :

Une closure en PHP

function multiplierFactory($multiplier) { return function($number) use($multiplier) { return $number * $multiplier; }; } $f = multiplierFactory(2); echo $f(5).PHP_EOL; // 10 echo $f(6).PHP_EOL; // 12 $f = multiplierFactory(3); echo $f(5).PHP_EOL; // 15 echo $f(6).PHP_EOL; // 18