2010-04-02 12 views
112

Aquí es una versión simplificada de algo que estoy tratando de correr:¿Cómo paso el valor (no la referencia) de una variable JS a una función?

for (var i = 0; i < results.length; i++) { 
    marker = results[i]; 
    google.maps.event.addListener(marker, 'click', function() { 
     change_selection(i); 
    }); 
} 

pero estoy encontrando que cada oyente utiliza el valor de results.length (el valor cuando el bucle termina). ¿Cómo puedo agregar oyentes de modo que cada uno use el valor de i en el momento en que lo agregue, en lugar de la referencia a i?

Respuesta

158

Es necesario crear un ámbito separado que guarda la variable en su estado actual pasándolo como un parámetro de función:

for (var i = 0; i < results.length; i++) { 
    (function (i) { 
    marker = results[i]; 
    google.maps.event.addListener(marker, 'click', function() { 
     change_selection(i); 
    }); 
    })(i); 
} 

Al crear una función anónima y llamándola con la variable como primer argumento, está pasando de valor a la función y creando un cierre.

+3

Querrá agregar 'var' antes de' marker' para no contaminar el espacio de nombres global. – ThiefMaster

+2

@ThiefMaster: curiosamente, pensé lo mismo después de ver esta respuesta por primera vez en mucho tiempo. Sin embargo, mirando el código de OP, no podemos estar completamente seguros de que 'marker' ya no es una variable global. –

+0

habiendo utilizado la API del mapa de Google, podemos apostar que el alcance de ese marcador está fuera del bucle for. Buena captura Andy. –

2

Estás terminando con un cierre. Here's an article on closures y cómo trabajar con ellos. Vea el Ejemplo 5 en la página; ese es el escenario con el que estás lidiando.

EDITAR: Cuatro años después, ese enlace está muerto. La raíz del problema anterior es que el bucle for forma cierres (específicamente en marker = results[i]). Como se pasa al addEventListener, verá el efecto secundario del cierre: el "entorno" compartido se actualiza con cada iteración del ciclo, antes de que finalmente se "guarde" a través del cierre después de la iteración final. MDN explains this very well.

12

cierres:

for (var i = 0, l= results.length; i < l; i++) { 
    marker = results[i]; 
    (function(index){ 
     google.maps.event.addListener(marker, 'click', function() { 
      change_selection(index); 
     }); 
    })(i); 
} 

Edición, 2013: Estos ahora se conoce comúnmente como un IIFE

+0

Nada * mal * aquí, pero -1 solo porque Andy E llegó primero con más explicación; esta respuesta no agrega nada a la página tal como está. –

+3

No estoy seguro de que comprenda los motivos de la baja. Y esta respuesta agrega información además de la (excelente) respuesta de Andy: IIFE. –

35

Además de los cierres, puede utilizar function.bind:

google.maps.event.addListener(marker, 'click', change_selection.bind(null, i)); 

pasa el valor de i i n como un argumento para la función cuando se llama. (null es para la unión this, lo que no es necesario en este caso.)

function.bind se introdujo en el marco del prototipo y ha sido estandarizado en ECMAScript Quinta edición. Hasta navegadores todo lo soportan de forma nativa, se puede añadir sus propias function.bind admite el uso de cierres:

if (!('bind' in Function.prototype)) { 
    Function.prototype.bind= function(owner) { 
     var that= this; 
     var args= Array.prototype.slice.call(arguments, 1); 
     return function() { 
      return that.apply(owner, 
       args.length===0? arguments : arguments.length===0? args : 
       args.concat(Array.prototype.slice.call(arguments, 0)) 
      ); 
     }; 
    }; 
} 
+2

Acabo de notar esto, +1. Soy bastante fanático de 'bind' y no puedo esperar a que se desplieguen las implementaciones nativas. –

+0

¿Qué navegadores son compatibles con esto? ¿Cualquier navegador móvil? – NoBugs

+2

@NoBugs: actualmente: IE9 +. Fx4 +, versiones recientes de Chrome y Opera. No Safari, no iPhone, el navegador Android lo tiene desde Ice Cream Sandwich. – bobince

-4

Creo que podemos definir una variable temporal para almacenar el valor de i.

for (var i = 0; i < results.length; i++) { 
var marker = results[i]; 
var j = i; 
google.maps.event.addListener(marker, 'click', function() { 
    change_selection(j); 
}); 
} 

No lo he probado.

+4

no, no funciona – newacct

+5

La razón por la que esto no funcionará es que JavaScript carece de ámbito de nivel de bloque. Todo el alcance es función-nivel. Solo puede crear un nuevo alcance llamando a una función, que es lo que vemos en las otras respuestas. Sin llamar a una función para cada iteración del ciclo, no hay forma de proporcionar un cierre diferente a cada devolución de llamada del evento del mapa. Este es un problema que se resuelve de forma transparente para usted cada vez que utiliza un iterador-helper como '$ .each()' o '_.each()'. – Keen

-2
for (var i = 0; i < results.length; i++) { 
    marker = results[i]; 
    google.maps.event.addListener(marker, 'click', (function(i) { 
     return function(){ 
      change_selection(i); 
     } 
    })(i)); 
} 
+7

esto sería una mejor respuesta si explicaras por qué funciona. –

Cuestiones relacionadas