Fermetures en JavaScript


Que sont les fermetures ?

Je considère les fermetures en JavaScript comme un sujet avancé. C’est l’un des sujets qui sont le plus souvent abordés dans les interviews.
La compréhension des fermetures serait plus aisée si vous aviez lu mes précédents articles de blog ou si vous connaissiez la portée en JavaScript.

La portée de la fonction en JavaScript signifie qu’une variable déclarée à l’intérieur d’une fonction n’est accessible que dans cette fonction. Elle est cependant accessible à la fonction et à toutes ses fonctions enfantines. Les fermetures vont plus loin. La clôture permet de s’assurer que la portée de la fonction parent est accessible à la fonction enfant même après que la fonction parent ait terminé son exécution.

Exemple

function outer() {
    const outerVariable = "outer";
    function inner() {
        const innerVariable = "inner";
        console.log(`${outerVariable} ${innerVariable}`); // outer inner
    }
    inner();
}

outer();

J’ai créé et exécuté le outer fonction ci-dessus. Cette action crée et invoque inner fonction. inner enregistre avec succès la variable qu’elle a déclarée et la variable de la fonction parente. Il est attendu puisque la fonction enfant a accès à la portée de la fonction parent.

N’appelons pas le inner et le rendre à la place.

function outer() {
    const outerVariable = "outer";
    function inner() {
        const innerVariable = "inner";
        return (`${outerVariable} ${innerVariable}`);
    }
    return inner;
}

const returnFunction = outer();
console.log(returnFunction); // Function returnFunction

Variable returnFunction est une fonction, car c’est ce qui a été rendu par outer. Pas de surprises.

🚨À ce stade, l’exécution de outer est terminée, et la valeur de retour a été attribuée à une nouvelle variable.

C’est la clé. La collecte des déchets en JavaScript devrait avoir éliminé toute trace de outerVariable car c’est ce qui se passe lorsqu’une fonction est retirée de la pile et que son exécution est terminée. Exécutons returnFunction.

function outer() {
    const outerVariable = "outer";
    function inner() {
        const innerVariable = "inner";
        return (`${outerVariable} ${innerVariable}`); // outer inner
    }
    return inner;
}

const returnFunction = outer();
console.log(returnFunction); // Function returnFunction
console.log(returnFunction()); // outer inner

Surprise ! Il peut toujours enregistrer la valeur d’une variable dans sa fonction mère (qui a fini de s’exécuter).

La collecte des déchets JavaScript ne supprime pas les variables d’une fonction si cette fonction a des fonctions enfant retournées. Ces fonctions enfant peuvent être exécutées plus tard et sont pleinement éligibles pour accéder au champ d’application du parent par le principe du scoping lexical.

Ce comportement de ramassage des ordures n’est pas seulement limité aux fonctions des enfants. Une variable n’est pas une poubelle collectée tant que tout maintient une référence à celui-ci.

Exemple du monde réel

Disons que je programme sur une voiture. Cette voiture peut accélérer comme une voiture du monde réel, et chaque fois qu’elle accélère, la vitesse de la voiture augmente.

function carMonitor() {
    var speed = 0;

    return {
        accelerate: function () {
            return speed++;
        }
    }
}

var car = new carMonitor();
console.log(car.accelerate()); // 0
console.log(car.accelerate()); // 1
console.log(car.accelerate()); // 2
console.log(car.accelerate()); // 3
console.log(car.accelerate()); // 4

Vous pouvez voir comment la vitesse de la voiture est fournie par carMonitor, et il est accessible par accelerate fonction. Chaque fois que j’appelle accelerateLe système peut non seulement accéder à ladite variable, mais aussi l’incrémenter à partir de la dernière valeur et la renvoyer.

Création de variables privées à l’aide de fermetures

Prenons l’exemple de carMonitor.

function carMonitor() {
    var speed = 0;

    return {
        accelerate: function () {
            return speed++;
        }
    }
}

var car = new carMonitor();
console.log(car.accelerate()); // 0
console.log(car.accelerate()); // 1
console.log(car.accelerate()); // 2
console.log(car.accelerate()); // 3
console.log(car.accelerate()); // 4
console.log(speed); // speed is not defined

Vous pouvez voir que la variable est une variable privée pour la fonction carMonitoret il n’est accessible que par la fonction enfant accelerate. Personne à l’extérieur ne peut y accéder. On pourrait dire que l’on s’y attend en raison de l’étendue de la fonction. Il est privé à carMonitoret il est privé à chaque nouvelle instance de carMonitor.

Chaque instance conserve sa copie et l’incrémente !

Cela devrait vous aider à réaliser le pouvoir des fermetures !

function carMonitor() {
    var speed = 0;

    return {
        accelerate: function () {
            return speed++;
        }
    }
}

var car = new carMonitor();
var redCar = new carMonitor()
console.log(car.accelerate()); // 0
console.log(car.accelerate()); // 1
console.log(redCar.accelerate()); // 0
console.log(redCar.accelerate()); // 1
console.log(car.accelerate()); // 2
console.log(redCar.accelerate()); // 2
console.log(speed); // speed is not defined

car et redCar maintenir leur propre speed les variables, et speed n’est pas accessible à l’extérieur.

Nous obligeons le consommateur à utiliser les méthodes définies sur la fonction ou la classe plutôt que d’accéder directement aux propriétés (ce qu’il ne devrait pas faire). C’est ainsi que vous encapsulerez votre code.

J’espère que l’article a dissipé les doutes sur les fermetures en JavaScript !

Question d’entretien commune

Voici une question d’entretien sur les fermetures qui est posée assez fréquemment.

À votre avis, quel sera le résultat du code suivant :

for (var i = 0; i <= 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

Si vous avez deviné des chiffres de 0 à 5 avec un écart d’une seconde, vous allez avoir une surprise. Au bout d’une seconde, le temps imparti est écoulé pour le setTimeout, i a une valeur de 6 au moment de l’invocation ! Nous aimerions utiliser la valeur de i dès la création et les fermetures IIFE + vous y aideront.

for (var i = 0; i <= 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i);
        }, 1000);
    })(i);
}

Il existe une autre façon de résoudre ce problème. En utilisant le let mot-clé.

var dans notre boucle utilisée pour déclarer i crée un champ de fonctions. Il en résulte une liaison partagée pour toutes les itérations de la boucle. Lorsque les six minuteries sont terminées, elles utilisent toutes la même variable avec une valeur finale de 6.

let a une portée de bloc et lorsqu’il est utilisé avec un for La boucle crée une nouvelle liaison pour chaque itération de la boucle. Chaque temporisateur de la boucle reçoit une variable différente avec une valeur différente de 0 à 5.

for (let i = 0; i <= 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

Si vous visez l’ES5, utilisez la méthode de fermeture IIFE plus. Si vous avez accès à ES6, utilisez la méthode let méthode des mots-clés.

C’est ce que fait Babel sous le capot lorsqu’il transpose le code ES6 en ES5. Elle transpose les codes ci-dessus let à une combinaison de fermeture + IIFE !

Soyez le premier à commenter

Poster un Commentaire

Votre adresse de messagerie ne sera pas publiée.


*