2010-06-01 3 views
10

Je lis "Javascript: The Good Parts" et je suis totalement déconcerté par ce qui se passe vraiment ici. Une explication plus détaillée et/ou simplifiée serait grandement appréciée.Fermetures: explication ligne par ligne de l'exemple "Javascript: Good Parts"?

// BAD EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the wrong way. 
// When you click on a node, an alert box is supposed to display the ordinal of the node. 
// But it always displays the number of nodes instead. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (e) { 
      alert(i); 
     } 
    } 
}; 

// END BAD EXAMPLE 

La fonction add_the_handlers avait pour but de donner à chaque gestionnaire d'un numéro unique (i). Elle échoue parce que les fonctions de gestionnaire sont liés à la variable i, pas la valeur de la i variable au moment où la fonction a été faite:

// BETTER EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the right way. 
// When you click on a node, an alert box will display the ordinal of the node. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (i) { 
      return function (e) { 
       alert(i); 
      }; 
     }(i); 
    } 
}; 

Maintenant, au lieu d'attribuer une fonction à onclick, nous définissons une fonction et invoquez-le immédiatement, en passant à i. Cette fonction renvoie une fonction de gestionnaire d'événements qui est liée à la valeur i qui a été transmise, et non au i défini dans add_the_handlers. Cette fonction retournée est assignée à onclick.

+0

voir les questions marquées dans: http://stackoverflow.com/questions/tagged/javascript+closures+loops – CMS

+0

Vous peut également jouer avec une démo en direct http://jsbin.com/sezisalulede/1/edit?html,js,output –

Répondre

20

Je pense que c'est une source très commune de confusion pour les nouveaux venus dans JavaScript. D'abord, je vous suggère de vérifier l'article Mozilla Dev ci-dessous pour brève introduction sur le thème de la fermeture et portée lexicale:

Commençons par la mauvaise:

var add_the_handlers = function (nodes) { 
// Variable i is declared in the local scope of the add_the_handlers() 
// function. 
    var i; 

// Nothing special here. A normal for loop. 
    for (i = 0; i < nodes.length; i += 1) { 

// Now we are going to assign an anonymous function to the onclick property. 
     nodes[i].onclick = function (e) { 

// The problem here is that this anonymous function has become a closure. It 
// will be sharing the same local variable environment as the add_the_handlers() 
// function. Therefore when the callback is called, the i variable will contain 
// the last value it had when add_the_handlers() last returned. 
      alert(i); 
     } 
    } 

// The for loop ends, and i === nodes.length. The add_the_handlers() maintains 
// the value of i even after it returns. This is why when the callback 
// function is invoked, it will always alert the value of nodes.length. 
}; 

Nous pouvons aborder ce problème avec plus de fermetures, comme Crockford l'a suggéré dans le «bon exemple». Une fermeture est un type spécial d'objet qui combine deux choses: une fonction et l'environnement dans lequel cette fonction a été créée. la fonction de fermeture crée

// Now we are creating an anonymous closure that creates its own local 
// environment. I renamed the parameter variable x to make it more clear. 
nodes[i].onclick = function (x) { 

    // Variable x will be initialized when this function is called. 

    // Return the event callback function. 
    return function (e) { 
     // We use the local variable from the closure environment, and not the 
     // one held in the scope of the outer function add_the_handlers(). 
     alert(x); 
    }; 
}(i); // We invoke the function immediately to initialize its internal 
     // environment that will be captured in the closure, and to receive 
     // the callback function which we need to assign to the onclick. 

Plutôt que d'avoir les callbacks tous partageant un même environnement, a: En JavaScript, l'environnement de la fermeture se compose de toutes les variables locales qui étaient dans le champ au moment où la fermeture a été créé nouvel environnement pour chacun. Nous aurions aussi pu utiliser une usine de fonction pour créer une fermeture, comme dans l'exemple suivant:

function makeOnClickCallback (x) { 
    return function (e) { 
     alert(x); 
    }; 
} 

for (i = 0; i < nodes.length; i += 1) { 
    nodes[i].onclick = makeOnClickCallback(i); 
} 
+0

Une question quelque peu liée. Que signifie "e" dans la fonction (e), et pourrait-il être remplacé par un var? J'avais l'habitude de penser que cela signifiait événement, mais maintenant je suis confus. – Matrym

+0

@Matrym: Oui, devrait être un argument que le navigateur passe à la fonction de rappel lorsque l'événement onclick est déclenché. Découvrez [cet article de quirksmode] (http://www.quirksmode.org/js/events_access.html) sur la façon dont cela est géré dans différents navigateurs. –

+0

Que se passe-t-il si nous n'utilisons pas de variable? Passons-nous pour que nous puissions enchaîner les choses? – Matrym

3

Tout tourne autour des fermetures. Dans le premier exemple, "i" sera égal à "nodes.length" pour chaque gestionnaire d'événement click, car il utilise "i" de la boucle qui crée les gestionnaires d'événements. Au moment où le gestionnaire d'événements est appelé, la boucle sera terminée, donc "i" sera égal à "nodes.length".

Dans le deuxième exemple, "i" est un paramètre (donc une variable locale). Les gestionnaires d'événements utiliseront la valeur de la variable locale "i" (le paramètre).

0

Cela a à voir avec la fermeture.

Quand vous faites la chose dans le mauvais exemple,

lorsque vous cliquez sur chaque nœud, vous obtiendrez la dernière valeur i (à savoir que vous avez 3 noeuds, peu importe ce nœud que vous cliquez, vous obtiendrez 2). puisque votre alerte (i) est liée à une référence de la variable i et non à la valeur de i au moment où elle était liée dans le gestionnaire d'événements. Si vous cliquez sur le noeud 1, vous obtiendrez 0, le noeud 2 vous donnera 1 et le noeud 3 vous donnera 2 En gros, vous etes en train d'evaluer ce qui est immediatement quand il est appele a la ligne} (i) et il est passe au parametre e qui maintenant contient la valeur de ce que je suis a ce moment-la.

Btw ... Je pense qu'il y a une faute de frappe dans la meilleure partie d'exemple ... il devrait être alerte (e) au lieu d'alerte (i).

2

Dans les deux exemples, tout noeud transmis est associé à un gestionnaire d'événement onclick (comme <img src="..." onclick="myhandler()"/>, ce qui est une mauvaise pratique après tout). La différence est que dans le mauvais exemple, chaque fermeture (les fonctions du gestionnaire d'événements, c'est-à-dire) fait référence à la même variable i en raison de leur portée parent commune.

Le bon exemple utilise une fonction anonyme qui est exécutée immédiatement. Cette fonction anonyme fait référence exactement à la même variable i que dans le mauvais exemple MAIS puisqu'elle est exécutée et fournie avec i comme premier paramètre, la valeur i est affectée à une variable locale appelée ... eh? ... i, exactement - écrasant ainsi celui défini dans la portée du parent.

Réécrivons le bon exemple pour tout clair:

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (newvar) { 
      return function (e) { 
       alert(nevar); 
      }; 
     }(i); 
    } 
}; 

Ici, nous avons remplacé i dans la fonction de gestionnaire d'événements retourné avec newvar et il fonctionne encore, parce que newvar est exactement ce que vous attendez - un nouvelle variable héritée de la portée de la fonction anonyme.

Bonne chance pour le découvrir.

Questions connexes