CAPITULO 3: JAVASCRIPT AVANZADO 2
JAVASCRIPT
Closures: Veamos paso a paso lo que va a ocurrir cuando ejecutemos este tipo de código. Primero se creará el contexto
de ejecución global, en esta etapa el intérprete guardará espacio para la declaración de la función saludar. Luego,
cuando se encuentra con la invocación a la función saludar, va a crear un nuevo contexto, y como vemos dentro de ese
contexto la variable saludo va a tomar el valor que le pasamos por parámetro:'Hola'.
Luego de terminar de ejecutar y retornar una función, ese contexto es destruido. Pero ¿Qué pasa con la variable saludo?
Bueno, el intérprete saca el contexto del stack, pero deja en algún lugar de memoria las variables que se usaron
adentro, hay un proceso dentro de JavaScript que se llama garbage collection que eventualmente las va limpiando si no
las usamos. Por lo tanto, esa variable todavía va a estar en memoria. Por último, ejecutamos la función saludarHola y
pasamos como parámetro el string 'Hola'. Por lo tanto, se crea un nuevo contexto de ejecución, con la variable
mencionada. Ahora, cómo adentro de la función saludarHola hacemos referencia a la variable saludo, el intérprete
intenta buscarla en su scope; cómo saludo no está definida en ese contexto, el intérprete sale a buscarla al siguiente
scope chain y a pesar que el contexto ya no existe, la referencia al ambiente exterior y a sus variables todavía existe, a
este fenómeno es que le llamamos CLOSURE. En el ejemplo, el closure está definido por el rectángulo punteado de rojo.
Las closures no son algo que se escriban, o qué se le indique al interprete, simplemente son una feature del lenguaje,
simplemente ocurren. Nosotros no tenemos que pensar ni ocuparnos de mantener variables en memoria según el
contexto de ejecución en el que estemos, el intérprete se encargará de esto siempre. Los Closures nos van a permitir
armar algunos patrones interesantes.
Ejemplo Closures:
¿Por qué el [Link] da todos 3? Para entenderlo veamos cómo se van creando los contextos de ejecución y donde
van quedando los objetos que creamos. En un primer momento se creará el contexto global, donde van estar definida la
función creaFuncion y también el arreglo arr. En un segundo momento, se va a crear el contexto de la función
creaFuncion que fue ejecutada. Dentro de ella, se reserva espacio para un arreglo llamado arreglo, y para la variable i.
Cuando el intérprete llega a la línea del return, se destruye el contexto de ejecución de creaFuncion y volvemos al
contexto global. La siguiente ejecución que se produce es la de arr[0](). Cabe notar que la variable arr apunta o hace
referencia al arreglo que vive en el contexto de creaFuncion, esto sucede porque los arreglos son objetos y estos se
pasan por referencia y no por valor. Como vemos, se crea el contexto de ejecución para esa función (arr[0]). Dentro de
esta hay una referencia a la variable i, que también vivía en el contexto de creaFuncion, ya destruido. Como el
intérprete no encuentra otra variable i dentro del nuevo contexto, sale a buscarla en sus referencias y, como sabemos,
la va a encontrar en el closure que envuelve estas variables. Luego, ejecuta las siguientes funciones arr[1]() y arr[2](), y
en ambos casos sucede lo mismo. Justamente por eso, en cada console log, se imprime el valor que tiene la variable i,
que es 3, el valor que quedó cuando se terminó el lazo dentro de creaFuncion. Si quisiéramos que cada función
guardase el valor de i, deberíamos crear un execution content donde se cree una variable nueva en cada iteración. Para
eso vamos a usar una IIFE a la cual le vamos a pasar como parámetro i. Como estamos ejecutando la función, se va a
crear un contexto nuevo por cada ejecución, y por ende van a existir tres variables j y cada una en un contexto distinto
que contendrán los valores recibidos por parámetro (1, 2 y 3).
Function Factory: Vamos a ver un patrón para crear funciones, muy usado en el desarrollo de frameworks, y que existe
gracias a los closures. Veamos el siguiente código, primero definimos una función que va retornar otra función, esta
sería nuestra fábrica de funciones, esta recibe como parámetro el lenguaje del saludo, y retorna una función que salude
en el idioma recibido.
Si pensamos que ocurre cuando ejecutamos esas líneas, vamos a ver que se crearon dos closures. Uno para cada
ejecución de la función hacerSaludo, en un closure la variable lenguaje contiene es y en el otro contiene en. Entonces,
cuando invoquemos las funciones saludoIngles o saludoEspaniol, el intérprete va a salir a buscar la referencia a esa
variable fuera del contexto de ejecución y la va a encontrar en el closure correspondiente. O sea, que estamos usando el
concepto de closure para setear un parámetro para que viva sólo dentro de una función, además nadie puede ingresar
al valor de lenguaje, esto agrega un poco de seguridad a nuestro código.
Cada vez que invocamos una función se genera un execution context para esa ejecución. Si invocamos muchas veces la
misma función ocurre lo mismo.
Closures and Callbacks: Ahora que sabemos lo que son los closures, si pensamos en todo lo que hicimos alguna vez con
JavaScript, es muy probable que nos demos cuenta que ya lo veníamos usando, tal vez sin saberlo. Por ejemplo:
En el ejemplo de arriba, cuando inocamos a saludarMasTarde estamos creando un execution context, en el que
invocamos a la función setTimeout y donde definimos la variable saludo. Ese execution context es destruido, pero
setTimeout contiene una referencia a saludo. Lo que realmente ocurre es que cuando pasan los tres segundos, se lanza
un evento diciendo que hay que ejecutar el callback, que es justamente una function expression. Entonces se crea un
execution context para esa función, y dentro de ella se usa saludo, pero no está en ese contexto, entonces el intérprete
sale a buscarla afuera, y la encuentra en el closure. O sea que se hicieron algo parecido a esto, tal vez usando eventos,
entonces ya usaron functions expressions y muy probablemente closures también.
Call, Apply and Bind: Cuando vimos el keyword this, dijimos que el intérprete era el que manejaba el valor de este.
Bueno, esto no es del todo cierto, hay una serie de variables que nos van a permitir poder setear nosotros el keyword
this. Como en JavaScript las funciones son un tipo objeto especial, estas también contienen métodos propios. Todas las
funciones tienen acceso a los métodos:
• call()
• bind()
• apply()
Justamente invocando estos métodos vamos a poder tener control sobre el keyword this. Veamos algunos ejemplos:
En este ejemplo, vamos a usar el keyword this para invocar el método del objeto persona. Como verán, el código de
arriba produce un error, ya que cuando ejecutamos logNombre(), el this que está adentro hace referencia al objeto
global, y ese objeto no tiene un método getNombre.
La función bind() devuelve una copia de la función, la cual tiene internamente asociado el keyword this al objeto que le
pasemos por parámtro. Si la llamamos sobre logNombre y le pasamos persona como argumento, vamos a ver que al
ejecutar la nueva función logNombrePersona() se va a loguear correctamente el nombre de persona. Si usamos bind()
entonces la nueva función queda siempre ligada al objeto que pasamos cómo argumento. O sea que si quisiéramos
usarla para otro objeto, tendríamos que crear una nueva copia de la función y bindiarle un nuevo objeto. Si ese es el
caso, podríamos usar call(), veamos cómo funciona:
En este caso, estamos invocando la función original logNombre, pero con call le estamos indicando a qué objeto tiene
que hacer referencia this dentro de esa función. El primer argumento de call es el objeto a usar cómo this. Después de
este puedo pasar otros argumentos, que serán pasados a la función que estamos invocando. Por ejemplo, si nuestra
función recibiera argumentos, usaríamos call de la siguiente manera:
De hecho, la función apply es casi igual a call, excepto que recibe los argumentos de distinta manera. apply necesita dos
argumentos, el primero es el objeto a bindear con this (igual que call) y el segundo parámetro es un arreglo, en este
arreglo pasamos los argumentos que va a usar la función que invocamos. Por ejemplo, para obtener el mismo
comportamiento que arriba, pero con apply:
Un arreglo puede ser más fácil de pasar cuando no sabemos a priori cuantos argumentos le voy a pasar. Vamos a usar
call o apply según nos convenga para resolver el problema que necesitemos. Vamos a usar estos métodos muchas veces
cuando programemos, tal vez ahora no se imaginen un caso puntual, pero los habrá se los aseguro. Veamos un simple
ejemplo donde podríamos usarlos, esto se conoce cómo function borrowing (tomando prestadas funciones). Vamos a
crear una segunda persona, pero que no tenga el método getNombre como la primera:
Ahora, vamos a pedirle prestado el método getNombre a la primera persona y la vamos a usar con la nueva.
Con esto pudimos invocar un método de un objeto, pero usándolo con otro. Veamos otro mini patron: function
currying, este involucra bind. Como bind crea una nueva función, si le pasamos parámetros, estos quedan fijos en la
nueva función. En el ejemplo no vamos a bindiar this con nada, pero si unos parámetros. Digamos que tenemos una
función que multiplica dos números recibidos por parámetro. Y nos gustaría construir una función que multiplique un
número recibido por argumento por dos. Para esto podríamos usar bind y le pasamos cómo primer parámetro this (en
este caso this hace referencia al contexto global), y como segundo parámetro un 2. Guardamos el resultado en una
nueva variable:
De esta forma, tenemos una nueva función donde el parámetro a es siempre 2, gracias a bind. Nótese, que dentro de
multiplicaPorDos, this sigue haciendo referencia al objeto global, porque cuando lo bindeamos le pasamos ese
parámetro. Function Currying se refiere a crear una copia de una función, pero con algunos argumentos preseteados.
En JavaScript lo hacemos con bind.
SLIDES DE LA CLASE: [Link]
1vTJVPf9trnEj1dF0Glv3He13vgxa2eoxMFbwOSghoqXl32AE0yBz0NHlOmZpJHDFRxWhBQ2WIOq2CAU/pub?start=false&l
oop=false&delayms=3000&slide=id.g1d6548f8739_0_1