¿Cómo funciona esta sintaxis de JavaScript / jQuery: (function (window, undefined) {}) (window)?

153

¿Alguna vez has echado un vistazo al código fuente de jQuery 1.4 y has notado cómo está encapsulado de la siguiente manera:

(function( window, undefined ) {

  //All the JQuery code here 
  ...

})(window);

He leído un artículo sobre el espacio de nombres de JavaScript y otro llamado " Un par importante de parejas ", así que sé algo sobre lo que está sucediendo aquí.

Pero nunca antes había visto esta sintaxis en particular. ¿Qué está undefinedhaciendo eso allí? ¿Y por qué es windownecesario aprobarlo y luego volver a aparecer al final?

dkinzer
fuente
3
Quería agregar que Paul Irish habla de esto en este video: paulirish.com/2010/10-things-i-learned-from-the-jquery-source
Mottie
@ Bergi, marcó mi pregunta como un duplicado de otra, pero hice la pregunta casi un año antes del duplicado. El reparto debe ser al revés.
dkinzer
@dkinzer: No se trata de preguntar antes, se trata de una respuesta de la más alta calidad. Es cierto que en este caso es cuello y cuello, pero la respuesta de CMS me pareció la mejor. Ven a chat.stackoverflow.com/rooms/17 para discutir esto, sin embargo
Bergi

Respuestas:

161

Lo indefinido es una variable normal y se puede cambiar simplemente con undefined = "new value";. Entonces jQuery crea una variable local "indefinida" que REALMENTE no está definida.

La variable de ventana se convierte en local por razones de rendimiento. Porque cuando JavaScript busca una variable, primero revisa las variables locales hasta que encuentra el nombre de la variable. Cuando no se encuentra, JavaScript pasa por el siguiente alcance, etc. hasta que se filtra a través de las variables globales. Entonces, si la variable de ventana se convierte en local, JavaScript puede buscarla más rápido. Más información: Acelere su JavaScript - Nicholas C. Zakas

Vincent
fuente
1
¡Gracias! ese fue un video muy informativo. Creo que necesita editar su respuesta para decir "la variable de ventana se convierte en local". Creo que esta es la mejor respuesta. Conté y descubrí que el objeto de la ventana se llama al menos 17 veces en el código fuente de JQuery. Por lo tanto, debe haber un efecto significativo al mover la ventana al Alcance local.
dkinzer
@DKinzer Oh sí, estás en lo cierto, por supuesto que está hecho localmente. Me alegro de poder ayudarte =)
Vincent
1
De acuerdo con esta prueba actualizada, jsperf.com/short-scope/5 pasar 'ventana' es en realidad más lento, cuando se trata de obtener una ventana constante a través de jQuery, y particularmente malo para Safari. Entonces, aunque 'undefined' tiene un caso de uso, no estoy muy convencido de que 'window' sea particularmente útil en todos los casos.
hexalys
1
También tiene un efecto positivo para minificar el código. Por lo tanto, cada uso de "ventana" e "indefinido" puede ser reemplazado por un nombre más corto por el minifyer.
TLindig
2
@ lukas.pukenis Cuando crea una biblioteca que se utiliza en millones de sitios web, es aconsejable no hacer suposiciones sobre lo que harán los locos.
JLRishe
54

Indefinido

Al declararlo undefinedcomo argumento pero nunca pasarle un valor, se garantiza que siempre esté indefinido, ya que es simplemente una variable en el ámbito global que se puede sobrescribir. Esto hace a === undefineduna alternativa segura a typeof a == 'undefined', que ahorra algunos caracteres. También hace que el código sea más amigable con los minificadores, ya que undefinedse puede acortar, upor ejemplo, para guardar algunos caracteres más.

Ventana

Pasar windowcomo argumento mantiene una copia en el ámbito local, lo que afecta el rendimiento: http://jsperf.com/short-scope . Todos los accesos windowahora tendrán que viajar un nivel menos en la cadena de alcance. Al igual que con undefined, una copia local nuevamente permite una minificación más agresiva.


Nota al margen:

Aunque esta puede no haber sido la intención de los desarrolladores de jQuery, la transferencia windowpermite que la biblioteca se integre más fácilmente en entornos Javascript del lado del servidor, por ejemplo node.js , donde no hay un windowobjeto global . En tal situación, solo se necesita cambiar una línea para reemplazar el windowobjeto con otra. En el caso de jQuery, windowse puede crear y pasar un objeto simulado con el fin de raspar HTML (una biblioteca como jsdom puede hacer esto).

David Tang
fuente
2
+1 por mencionar Minificación, desearía poder +2 para jsperf - La versión minificada es (function(a,b){})(window);- ay bson mucho más cortas que windowy undefined:)
gnarf
+1 Había oído hablar de la cadena de alcance antes, pero olvidé el término. ¡Gracias!
alex
También es una alternativa al uso de void (0) que fue diseñado por la misma razón: void (0) es una forma segura de obtener el valor indefinido.
glmxndr
"Lo que dices" hace referencia a esta otra pregunta: stackoverflow.com/questions/4945265/…
gnarf
@gnarf, gracias por la notificación de que las preguntas se fusionaron. Editaré mi respuesta para que tenga un poco más de sentido;)
David Tang
16

Otros lo han explicado undefined. undefinedes como una variable global que se puede redefinir a cualquier valor. Esta técnica es para evitar que se rompan todas las comprobaciones indefinidas si alguien escribió decir, en undefined = 10algún lugar. Se garantiza que un argumento que nunca se pasa será real, undefinedindependientemente del valor de la variable undefined .

La razón para pasar la ventana se puede ilustrar con el siguiente ejemplo.

(function() {
   console.log(window);
   ...
   ...
   ...
   var window = 10;
})();

¿Qué registra la consola? El valor del windowobjeto ¿verdad? ¡Incorrecto! 10? ¡Incorrecto! Se registra undefined. El intérprete de Javascript (o el compilador JIT) lo reescribe de esta manera:

(function() {
   var window; //and every other var in this function

   console.log(window);
   ...
   ...
   ...
   window = 10;

})();

Sin embargo, si obtiene la windowvariable como argumento, no hay var y, por lo tanto, no hay sorpresas.

No sé si jQuery lo está haciendo, pero si está redefiniendo windowla variable local en cualquier lugar de su función por cualquier razón, es una buena idea tomarla prestada del alcance global.

Chetan S
fuente
6

windowse pasa así en caso de que alguien decida redefinir el objeto de ventana en IE, supongo lo mismo undefined, en caso de que se reasigne de alguna manera más tarde.

La parte superior windowde ese script es simplemente nombrar el argumento "ventana", un argumento que es más local que la windowreferencia global y que es lo que usará el código dentro de este cierre. Al windowfinal, en realidad se especifica qué pasar para el primer argumento, en este caso, el significado actual de window... la esperanza es que no se haya equivocado windowantes de que eso suceda.

Puede ser más fácil pensar en esto mostrando el caso más típico utilizado en jQuery, .noConflict()manejo de complementos , por lo que para la mayoría del código aún puede usar $, incluso si significa algo más que jQueryfuera de este alcance:

(function($) {
  //inside here, $ == jQuery, it was passed as the first argument
})(jQuery);
Nick Craver
fuente
Gracias. Esto tiene mucho sentido. Sin embargo, creo que la respuesta es una combinación de esto y lo que dice @ C.Zakas.
dkinzer
@DKinzer Me temo que no soy C. Zakas;)
Vincent
@Vincent, perdón por eso :-)
dkinzer
5

Probado con 1000000 iteraciones. Este tipo de localización no tuvo efecto en el rendimiento. Ni siquiera un milisegundo en 1000000 iteraciones. Esto es simplemente inútil.

Semra
fuente
2
Sin mencionar que el cierre lleva todas las variables junto con la instancia de la función, por lo que si tiene 100 bytes en el cierre y 1000 instancias, eso es 100 kb de memoria más.
Akash Kava
@AkashKava y @Semra, no creo que esta prueba realmente explique por qué pasarías por una ventana en una situación del mundo real. La ganancia de rendimiento solo se verá cuando haya cierres profundamente anidados que accedan a esa variable a lo largo de la cadena de alcance. Obviamente, si su función está a solo un paso de la variable de la cadena de alcance, no verá mucha ganancia. pero si fuera muuuuy allá abajo y fuera necesario window, podría ver algo de ganancia por una copia local.
gabereal
@gabereal nuevos motores de JavaScript resuelven los cierres en el momento de la compilación en sí, no hay aumento de rendimiento en el cierre en tiempo de ejecución. Dudo que haya una ganancia de rendimiento medible, si tiene tanta confianza, puede crear un ejemplo de jsperf para demostrar el rendimiento. El único rendimiento que obtenemos es aislando los cierres, lo que da como resultado una mejor minificación que reduce el tamaño y el rendimiento se logra mediante una descarga y análisis más rápidos del script.
Akash Kava
@AkashKava, ¿qué te hace pensar que tengo tanta confianza? Solo dije "podría ver algo de ganancia" y "no creo". Podría crear una prueba, pero prefiero no hacerlo ya que nunca uso este patrón de todos modos. ¿Puedes explicar qué quieres decir con "resuelve los cierres en el momento de la compilación misma"? a diferencia de qué alternativa? ¿Cómo los motores Javascript hacían las cosas de manera diferente antes?
gabereal
Busque un código donde la ventana se curse al final pero no se ingrese en los parámetros de la función, ¿qué significa? se asegura de que los parámetros sigan el objeto de alcance global original, incluso si no hay ningún parámetro de ventana, supongo.
Webwoman