"Descifrando" JavaScript...

19 views
Skip to first unread message

Iñaki Baz Castillo

unread,
Dec 26, 2012, 7:43:01 AM12/26/12
to jsmeetu...@googlegroups.com
Hola, estoy analizando código JS de ejempo que hace uso de API HTML5 y
me estoy dando cuenta de que, o bien ando limitado de conceptos, o
sencillamente JavaScript es feo a más no poder.

Concretamente el siguiente código, obtenido de [*], me "fascina":


// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {

// Only process image files.
if (!f.type.match('image.*')) {
continue;
}

var reader = new FileReader();

// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<img class="thumb" src="', e.target.result,
'" title="', escape(theFile.name), '"/>'].join('');
document.getElementById('list').insertBefore(span, null);
};
})(f);

// Read in the image file as a data URL.
reader.readAsDataURL(f);
}



En concreto mi mente no compila a partir del "reader.onload". Se está
asignando a ese atributo una función que será llamada, a modo de
callback, pasando como argumento un objeto theFile (hasta aquí OK),
pero luego resulta que esa función lo que hace es un "return
function(e)". Me pierdo con ese argumento "e", ¿qué es? ¿de dónde
sale?, ¿es lo mismo que theFile? ¿y por qué luego dentro de esa
funtion(e) resulta que se accede tanto a "theFile" como a "e"?

Y lo peor de todo, al final hay un curioso "})(f);", es decir, que la
asignación del atributo queda así:

reader.onload = ( function(theFile) { return function(e) { ... } } ) ( f );

Simplifico aún más:

reader.onload = ( function(theFile) { ... } ) ( f );

Y aún más:

reader.onload = ( ... ) ( f );


¿Qué sintaxis demoníaca es esa?

Gracias por cualquier iluminación al respecto.



[*] http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-reading-files


--
Iñaki Baz Castillo
<i...@aliax.net>

Txemanu unamexT

unread,
Dec 26, 2012, 7:50:21 AM12/26/12
to jsmeetu...@googlegroups.com
Realmente la función que se asigna al onload es la función que está en el return. La primera se ejecuta automáticamente, es una función anónima autoejecutable.

El parámetro e es el objeto event, que se crea con una serie de propiedades cuando salta el evento.

Pablo Garaizar Sagarminaga

unread,
Dec 26, 2012, 8:02:58 AM12/26/12
to jsmeetu...@googlegroups.com
Hola,

El Wed, 26 Dec 2012 13:50:21 +0100
Txemanu unamexT <txe...@gmail.com> comentaba:

> Realmente la función que se asigna al onload es la función que está
> en el return. La primera se ejecuta automáticamente, es una función
> anónima autoejecutable.

Fíjate que avisa con un comentario para los no-javascripters: ojo que
aquí viene una clausura (o cierre, o closure, o como demonios quiera
que se diga). Las clausuras son una de las frikadas que ha heredado
JavaScript de Scheme y trae por la calle de la amargura a todos los que
llegan de otros lenguajes pero causa admiración una vez se entiende su
potencial O:-D (yo estoy todavía en esa transición, lo confieso).

> El parámetro e es el objeto event, que se crea con una serie de
> propiedades cuando salta el evento.

Eso es, como esa función es un "manejador de eventos" o "callback" (o
como quiera que se llamen xD), cuando sea invocada, tendrá como
argumento el objeto de tipo evento asociado (que puede llamarse como
quieras, pero "e" no está del todo mal).

--
Pablo Garaizar Sagarminaga
Universidad de Deusto
Avda. de las Universidades 24
48007 Bilbao - Spain

Phone: +34-94-4139000 Ext 2512
Fax: +34-94-4139101

Iñaki Baz Castillo

unread,
Dec 26, 2012, 8:12:33 AM12/26/12
to jsmeetu...@googlegroups.com
El día 26 de diciembre de 2012 14:02, Pablo Garaizar Sagarminaga
<gara...@deusto.es> escribió:
> Hola,
>
> El Wed, 26 Dec 2012 13:50:21 +0100
> Txemanu unamexT <txe...@gmail.com> comentaba:
>
>> Realmente la función que se asigna al onload es la función que está
>> en el return. La primera se ejecuta automáticamente, es una función
>> anónima autoejecutable.

¿Es autoejecutable por el hecho de estar entre paréntesis? ¿o no tiene
nada que ver? (lo digo porque me suena haberlo visto/leído, pero no
estoy seguro...).

Y sobre todo, ¿por qué hace falta esa función autoejecutable? ¿no se
puede setear directamente la function que viene dentro? ¿o acaso si se
asigna una function ésta se ejecutaría inmediatamente al interpretarse
el código al principio? Obviamente interesa que esa función se ejecute
más tarde (cuando se produzca el evento) y no antes, ¿es por ello ese
"hack" con una función que retorna otra?




> Fíjate que avisa con un comentario para los no-javascripters: ojo que
> aquí viene una clausura (o cierre, o closure, o como demonios quiera
> que se diga). Las clausuras son una de las frikadas que ha heredado
> JavaScript de Scheme y trae por la calle de la amargura a todos los que
> llegan de otros lenguajes pero causa admiración una vez se entiende su
> potencial O:-D (yo estoy todavía en esa transición, lo confieso).

Vaya, pues el caso es que yo de JS como véis poco, pero sí toco Ruby,
y en Ruby los blocks (que al final son precisamente un closure) se
usan para todo y no podría vivir sin ellos. Tendré que asimilar la
"sintaxis" de JS... :)


Muchas gracias a los dos.

Txemanu unamexT

unread,
Dec 26, 2012, 9:10:58 AM12/26/12
to jsmeetu...@googlegroups.com
Te contesto en azul:


El 26 de diciembre de 2012 14:12, Iñaki Baz Castillo <i...@aliax.net> escribió:
El día 26 de diciembre de 2012 14:02, Pablo Garaizar Sagarminaga
<gara...@deusto.es> escribió:
> Hola,
>
> El Wed, 26 Dec 2012 13:50:21 +0100
> Txemanu unamexT <txe...@gmail.com> comentaba:
>
>> Realmente la función que se asigna al onload es la función que está
>> en el return. La primera se ejecuta automáticamente, es una función
>> anónima autoejecutable.

¿Es autoejecutable por el hecho de estar entre paréntesis? ¿o no tiene
nada que ver? (lo digo porque me suena haberlo visto/leído, pero no
estoy seguro...).

Es autoejecutable por los paréntesis que hay a continuación de ella, que hace
que se declare y ejecute seguido. También podría tener este formato: f(){....}() aunque
se suele poner entre paréntesis para verlo más claro: (f(){...})()
 

Y sobre todo, ¿por qué hace falta esa función autoejecutable? ¿no se
puede setear directamente la function que viene dentro? ¿o acaso si se
asigna una function ésta se ejecutaría inmediatamente al interpretarse
el código al principio? Obviamente interesa que esa función se ejecute
más tarde (cuando se produzca el evento) y no antes, ¿es por ello ese
"hack" con una función que retorna otra?

La función autoejecutable lo que hace es crear un "closure" alrededor de la variable
theFile, para que ésta esté disponible cuando se ejecuta la función que se asigna en el return
al evento onload. Este closure es un "objeto de activación" que no se ha destruido por que
quedan referencias a él. Estos "objetos de activación" se crean y destruyen cada vez
que se ejecuta una función en JS, y contiene los objetos disponibles en el contexto de la función. 
Cuando estos "objetos de activación" no se destruyen por que quedan referencias a ellos,
hablamos que se ha creado una "closure".

Xabier de Zuazo

unread,
Dec 26, 2012, 9:34:56 AM12/26/12
to jsmeetu...@googlegroups.com
On Wed, 26 Dec 2012 15:10:58 +0100
Txemanu unamexT <txe...@gmail.com> wrote:

> El 26 de diciembre de 2012 14:12, Iñaki Baz Castillo
> <i...@aliax.net>escribió:
>
> > El día 26 de diciembre de 2012 14:02, Pablo Garaizar Sagarminaga
> > <gara...@deusto.es> escribió:
> > > Hola,
> > >
> > > El Wed, 26 Dec 2012 13:50:21 +0100
> > > Txemanu unamexT <txe...@gmail.com> comentaba:
> > >
> > >> Realmente la función que se asigna al onload es la función que
> > >> está en el return. La primera se ejecuta automáticamente, es una
> > >> función anónima autoejecutable.
> >
> > ¿Es autoejecutable por el hecho de estar entre paréntesis? ¿o no
> > tiene nada que ver? (lo digo porque me suena haberlo visto/leído,
> > pero no estoy seguro...).
> >
>
> Es autoejecutable por los paréntesis que hay a continuación de ella,
> que hace que se declare y ejecute seguido. También podría tener este
> formato: f(){....}() aunque se suele poner entre paréntesis para
> verlo más claro: (f(){...})()

Sí, eso es. Es una convención (usada entre otros por jQuery). En otros
sitios te puedes encontrar con lo mismo indicado de otras maneras:

( function ... {...}(f) ) // paréntesis afuera

!function ... {...} (f)

etc.

> > Y sobre todo, ¿por qué hace falta esa función autoejecutable? ¿no se
> > puede setear directamente la function que viene dentro? ¿o acaso si
> > se asigna una function ésta se ejecutaría inmediatamente al
> > interpretarse el código al principio? Obviamente interesa que esa
> > función se ejecute más tarde (cuando se produzca el evento) y no
> > antes, ¿es por ello ese "hack" con una función que retorna otra?
> >
> La función autoejecutable lo que hace es crear un "closure"
> alrededor de la variable theFile, para que ésta esté disponible
> cuando se ejecuta la función que se asigna en el return
> al evento onload. Este closure es un "objeto de activación" que no se
> ha destruido por que quedan referencias a él. Estos "objetos de
> activación" se crean y destruyen cada vez que se ejecuta una función
> en JS, y contiene los objetos disponibles en el contexto de la
> función. Cuando estos "objetos de activación" no se destruyen por que
> quedan referencias a ellos, hablamos que se ha creado una "closure".

Exacto. Crea un una función auto ejecutada para mantener la referencia
a "f".

Yo creo que el código podría quedar más claro quizá utilizando OOP y
el Proxy Pattern (que en cierto modo es lo que usa), por si quieres
investigarlo.

En realidad la variable "f" (aka "theFile") no se destruye, porque se
usa dentro del closure precisamente. Es decir, algo como esto podría
funcionar:

reader.onload = function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<img class="thumb" src="', e.target.result,
'" title="', escape(f.name), '"/>'].join('');
document.getElementById('list').insertBefore(span, null); };
};

Pero hay un problema, y es que el valor de "f" dentro del bucle va
cambiando y, además, al final acaba siendo "undefined". Cuando onload
se llame, "f" seguramente tendrá ese valor que se le ha asignado al
final.

for (var i = 0, f; f = files[i]; i++) {

En la última vuelta "files[i]" será "undefined", se asignará ese valor
a "f" y luego dará por terminado el bucle. Cuando se llame a "onload",
aunque a simple vista parezca raro, "f" seguramente valdrá undefined.

> > > Fíjate que avisa con un comentario para los no-javascripters: ojo
> > > que aquí viene una clausura (o cierre, o closure, o como demonios
> > > quiera que se diga). Las clausuras son una de las frikadas que ha
> > > heredado JavaScript de Scheme y trae por la calle de la amargura
> > > a todos los que llegan de otros lenguajes pero causa admiración
> > > una vez se entiende su potencial O:-D (yo estoy todavía en esa
> > > transición, lo confieso).
> >
> > Vaya, pues el caso es que yo de JS como véis poco, pero sí toco
> > Ruby, y en Ruby los blocks (que al final son precisamente un
> > closure) se usan para todo y no podría vivir sin ellos. Tendré que
> > asimilar la "sintaxis" de JS... :)

Son parecidos pero tienen sus diferencias. Digamos que, mientras ruby
tiene sus blocks, procs y lambads, cada uno para cada cosa, Javascript
sólo tiene sus function para todo.

Esto hace que ruby sea más fácil de leer, pero quizá también hace que
en javascript sea más fácil de aprender (una vez entendido bien cómo
funciona una function).

Un saludo,

--
Xabier de Zuazo
signature.asc

Iñaki Baz Castillo

unread,
Dec 26, 2012, 11:48:59 AM12/26/12
to jsmeetu...@googlegroups.com
El día 26 de diciembre de 2012 15:10, Txemanu unamexT
<txe...@gmail.com> escribió:
>> ¿Es autoejecutable por el hecho de estar entre paréntesis? ¿o no tiene
>> nada que ver? (lo digo porque me suena haberlo visto/leído, pero no
>> estoy seguro...).
>
>
> Es autoejecutable por los paréntesis que hay a continuación de ella, que
> hace
> que se declare y ejecute seguido. También podría tener este formato:
> f(){....}() aunque
> se suele poner entre paréntesis para verlo más claro: (f(){...})()

Ajá, aclarado. Qué raro es JavaScript, aunque supongo que es acostumbrarse... :)



>> Y sobre todo, ¿por qué hace falta esa función autoejecutable? ¿no se
>> puede setear directamente la function que viene dentro? ¿o acaso si se
>> asigna una function ésta se ejecutaría inmediatamente al interpretarse
>> el código al principio? Obviamente interesa que esa función se ejecute
>> más tarde (cuando se produzca el evento) y no antes, ¿es por ello ese
>> "hack" con una función que retorna otra?
>>
> La función autoejecutable lo que hace es crear un "closure" alrededor de la
> variable
> theFile, para que ésta esté disponible cuando se ejecuta la función que se
> asigna en el return
> al evento onload. Este closure es un "objeto de activación" que no se ha
> destruido por que
> quedan referencias a él. Estos "objetos de activación" se crean y destruyen
> cada vez
> que se ejecuta una función en JS, y contiene los objetos disponibles en el
> contexto de la función.
> Cuando estos "objetos de activación" no se destruyen por que quedan
> referencias a ellos,
> hablamos que se ha creado una "closure".

Cristalino.

Muchas gracias.

Iñaki Baz Castillo

unread,
Dec 26, 2012, 11:56:25 AM12/26/12
to jsmeetu...@googlegroups.com
El día 26 de diciembre de 2012 15:34, Xabier de Zuazo
<xab...@zuazo.org> escribió:
> En realidad la variable "f" (aka "theFile") no se destruye, porque se
> usa dentro del closure precisamente. Es decir, algo como esto podría
> funcionar:
>
> reader.onload = function(e) {
> // Render thumbnail.
> var span = document.createElement('span');
> span.innerHTML = ['<img class="thumb" src="', e.target.result,
> '" title="', escape(f.name), '"/>'].join('');
> document.getElementById('list').insertBefore(span, null); };
> };
>
> Pero hay un problema, y es que el valor de "f" dentro del bucle va
> cambiando y, además, al final acaba siendo "undefined". Cuando onload
> se llame, "f" seguramente tendrá ese valor que se le ha asignado al
> final.
>
> for (var i = 0, f; f = files[i]; i++) {
>
> En la última vuelta "files[i]" será "undefined", se asignará ese valor
> a "f" y luego dará por terminado el bucle. Cuando se llame a "onload",
> aunque a simple vista parezca raro, "f" seguramente valdrá undefined.

Entendido. En Ruby yo haría algo así:

--------------------------------
files.each do |f|

reader = FileReader.new;

reader.onload do
# Hacer aquí algo con "f" puesto que dentro del block
# se mantendrá con el valor que tenía al generar este block.
end

end
--------------------------------



> Digamos que, mientras ruby
> tiene sus blocks, procs y lambads, cada uno para cada cosa, Javascript
> sólo tiene sus function para todo.
>
> Esto hace que ruby sea más fácil de leer, pero quizá también hace que
> en javascript sea más fácil de aprender (una vez entendido bien cómo
> funciona una function).

Supongo que para gustos, pero a mí personalmente la sintaxis de Ruby
que indico arriba me parece increiblemente lógica y fácil de entender.

Muchas gracias a todos, qué nivelazo.

Xabier de Zuazo

unread,
Dec 26, 2012, 12:28:20 PM12/26/12
to jsmeetu...@googlegroups.com
Eso es. El ejemplo que pones en js se podría hacer algo así:

for (var i in files) {
(function (f) {

reader = FileReader.new

reader.onload = function() {
/* Hacer aquí algo con "f" puesto que dentro del closure
se mantendrá con el valor que tenía al generar el scope
anterior. */
};
})(files[i]);
}

Igual con esta estructura queda más claro que con el ejemplo original.
En este caso se podría hacer de las dos manearas entiendo yo.

Saludos,

--
Xabier de Zuazo
signature.asc

Iñaki Baz Castillo

unread,
Dec 26, 2012, 12:33:18 PM12/26/12
to jsmeetu...@googlegroups.com
>> En Ruby yo haría algo así:
>>
>> --------------------------------
>> files.each do |f|
>>
>> reader = FileReader.new;
>>
>> reader.onload do
>> # Hacer aquí algo con "f" puesto que dentro del block
>> # se mantendrá con el valor que tenía al generar este block.
>> end
>>
>> end
>> --------------------------------



El día 26 de diciembre de 2012 18:28, Xabier de Zuazo
<xab...@zuazo.org> escribió:

> Eso es. El ejemplo que pones en js se podría hacer algo así:
>
> for (var i in files) {
> (function (f) {
>
> reader = FileReader.new
>
> reader.onload = function() {
> /* Hacer aquí algo con "f" puesto que dentro del closure
> se mantendrá con el valor que tenía al generar el scope
> anterior. */
> };
> })(files[i]);
> }
>
> Igual con esta estructura queda más claro que con el ejemplo original.
> En este caso se podría hacer de las dos manearas entiendo yo.

Pues sí, yo al menos lo veo mucho más claro.

Gracias.

hyperandroid

unread,
Dec 26, 2012, 1:44:23 PM12/26/12
to jsmeetu...@googlegroups.com
Bienvenido al mundo de closures y los scopes de JavaScript.
Este asunto, junto con hoisting, y especificar "this" de forma apropiada van mas o menos de la mano.

Todo es por como JavaScript asigna alcances a las funciones. 
En ese bucle, donde declaras el reader.onload, asignas una function.
Esa funcion, cuando vaya a evaluarse, buscara sus parametros y referencias en su scope.

Si lo dejas asi:

for( i=0; i< ... ) {
  reader.onload= function() { var x= files[i] ....

acabara en desastre, porque todos los onload referencian a files[i].
files esta definido fuera del bucle, como parametro, y accesible para todos los onload definidos.
pero el iterator i, es el mismo para todas las funciones, y acaba con el valor files.length. Para todas las funciones.

para arreglarlo/corregirlo/hacerlo bien, tienes que hacer una closure, cuyo unico objecto es crear un nuevo alcance, para cada funcion onload. 
la sintaxis del demonio (function() { })() o (function() {}()), es la declaracion de una funcion implicita y anonima (no tiene nombre).
una construccion de tipo (3+3), evalua a 6.
y (function() { }), evalua a function() {}
asi que (function() {})() o (function() {}()) evaluara a llamar a una funcion.
estamos de suerte, porque las funciones crean alcances.
asi que todo lo que llames aqui:

(function() {})( ponga aqui su lista de argumentos )

llegara a la funcion implicita como argumentos de llamada, pero en su propio alcance por lo que los argumentos quedan capturados.

este no es el unico uso de las closures. normalmente se usan para ocultar funciones/variables, etc:

var namespace= {};

(function() {

  var var_privada= 9;
  function fn {
  }

  namespace.MyTipo= function() {
    return fn(var_privada);
  };

})();  // a mi me gusta dejar los parentesis fuera, aunque el viejuno de crockford dice que son "dog balls" ;)


var_privada, y fn, son elementos que no existen fuera de la funcion implicita, y que solo los simbolos dentro de la misma pueden ver.
de paso, se exporta un simbolo namespace.MyTipo.
tambien puedes construir objetos mediante closure en lugar de constructor, etc. etc.

closures son muy versatiles.
sin mas, que una vez visto, ya no tiene sorpresa.

- ibon

Iñaki Baz Castillo

unread,
Dec 27, 2012, 4:18:20 AM12/27/12
to jsmeetu...@googlegroups.com
Este mail va directo a mi nuevo directorio "JavaScript-Doc/".

Gracias.


El día 26 de diciembre de 2012 19:44, hyperandroid
<hypera...@gmail.com> escribió:
Reply all
Reply to author
Forward
0 new messages