Closures y ejemplos de la vida real

July 31, 2019

...

Un closure es la combinación de una función y el alcance léxico que tiene esta. Si buscas en internet esa es la definición que aparece de un closure, y la verdad es que a mi en un comienzo no me dijo mucho, lo único que entendí fue función. Con el paso del tiempo, mientras más los utilizaba más los entendía, pero espero que ustedes no tengan que pasar por eso y logre explicarlo de una manera sencilla, vamos a ver una función a modo de ejemplo:

const f = () => {
  const x = 'lala'
  console.log(x)
}

f() // imprime lala

Declaramos una función f que lo que hace es imprimir el valor de x, que en este caso es lala

Si nosotros intentamos acceder a x fuera de la función esto nos arrojará un error:

const f = () => {
  const x = 'lala'
  console.log(x)
}

f() // imprime lala

console.log(x) // error, x no está definida

Eso es porque x no se encuentra definida en el mismo nivel donde se ejecuta nuestro segundo console.log, esto es el alcance léxico:

const f = () => { // acá empieza el alcance de la función
  const x = 'lala'
  console.log(x)
} // acá termina

sin embargo la función también puede acceder a variables que se encuentran definidas fuera de esta:

const y = 'lele'
const f = () => {
  const x = 'lala'
  console.log(y, x)
}

f() // imprime lele lala 

Por lo que el alcance de la función es todo lo que se encuentre fuera de ella, ahora que pasa cuando definimos otra función en el alcance de más arriba?

const f = () => {
  const x = 'lala'
  console.log(x)
}

const g = () => {
  const y = 'lele'
  console.log(x, y)
}

f() // imprime lala 

g() // arroja error porque no puede acceder a x

Acá f funciona correctamente pero g arroja un error, eso es debido a que las funciones declaradas en JavaScript solo pueden acceder a variables dentro de la misma función en su mismo alcance, y una función declara fuera de esta función corresponde a otro alcance.

f tiene un alcance local, que es todo lo que se encuentra dentro de los paréntesis de llaves de f, pero también tiene un alcance global, que es todo lo que se encuentra fuera de f, excluyendo lo que se encuentre dentro de otras funciones!, el conjunto de esto es el alcance léxico.

Recapitulando, una función puede acceder solo a su alcance y al alcance global, pero no al alcance creado por otras funciones.

Ahora veamos otro ejemplo donde en lugar de solo imprimir el valor retornamos una función que se encarga de imprimir el valor:

const f = () => {
  const x = 'lala'
  
  return () => console.log(x)
}

const g = f()
g() // imprime 'lala'

En algunos lenguajes de programación en valor de x es eliminado, este no es el caso de javascript ya que el código funciona. Cuando ejecutamos la función, esta va a asignarle el valor de lala a x, y persistirá siempre para la nueva función creada, enes caso g. Por lo que g podremos ejecutarla cuantas veces queramos y su valor siempre será lala.

Acabamos de crear un closure!

En este caso, el closure es el contexto en el que vive lala dentro de la función g. Lo hermoso de esto es que podemos crear funciones que nos devuelvan funciones las cuales pueden cambiar su comportamiento dependiendo de los valores que se encuentren dentro del closure.

Vamos ahora con otro ejemplo con algo un poco más avanzado:

const f = (valor) => {
  const x = `${valor}!`
  
  return () => console.log(`rayos ${x}`)
}

const g = f('Nicolas')
const h = f('Fluffy')
g() // imprime 'Rayos Nicolas!'
h() // imprime 'Rayos Fluffy!'

Esto ya empieza a volverse más interesante, el valor que le entregamos a la función f persiste y nos permite crear nuevas funciones con comportamiento distinto. g ahora es una función que imprime Rayos Nicolas!, y h ahora es una función que imprime Rayos Fluffy!. Veamos si podemos hacer algo más interesante con esto:

const auditProps = {
  createdAt: { default: new Date },
  updatedAt: { default: new Date },
  createdBy: { type: Schema.Types.ObjectId, ref: 'User' },
  updatedBy: { type: Schema.Types.ObjectId, ref: 'User' },
}

const Model = (defaultProps) => {
  return (name, props) => {
    const schema = mongoose.schema({
      ...defaultProps,
      ...props,
    })
    
    return mongoose.model('name', schema)
  }
}

export const withAudit = Model(auditProps)

// y en otro achivo Product.js

import { withAudit } from './models'

export default withAudit('Product', {
  name: String,
  desc: String,
})
// este modelo ya tiene las propiedades de auditProps
// también esta función nos sirve para recrear modelos que 
// compartan propiedades, nombre, user_id, descripción, son 
// algunas que muchas colecciones o tablas en base de datos 
// tienen.

Ya tenemos un uso practico para crear modelos en mongoose, aunque también se puede aplicar este principio para otras abstracciones de bases de datos.

Con esto también podemos compartir comportamiento!

const crudder = (dominio) => (recurso) => {
  const url = `${dominio}/${recurso}`
  
  return () => ({
    create: data => fetch(url', {
      method: 'POST',
      body: JSON.stringify(data),
    }),
    // lo mismo para delete, listar, etc...
  })
}

const withDomain = crudder('https://www.dominio.com')
const users = withDomain('users')

users.create({ name: 'chanchito feliz' })
  .then(x => x.json())
  .then(x => console.log(x)) // aca respuesta de la api

Recapitulamos! Vimos que son los closures y como estos se crean cuando nuestras funciones devuelven funciones, estas viven dentro del alcance de una función y del alcance global pero no dentro del alcance de otra función, vimos como pasando argumentos a nuestras funciones podemos generar abstracciones altamente reutilizares y vimos también dos ejemplos, uno para crear modelos en base de datos y otro para reutilizar lógica cuando queremos conectarnos a una API.

Espero que les haya gustado este post, no olviden seguirme en twitter y youtube! Links más abajo.


Suscríbete

Suscríbete a la lista para más cursos, posts y videos tutoriales. Prometo no enviarte más de un correo semanal 🙏

Creado por Nicolás Schürmann ingeniero e instructor de software. Cuando no está programando, esta frente a una cámara dictando cursos, creyéndose youtuber o apoyando a sus alumnos. Puedes seguirlo en twitter o también suscribirte a su canal de youtube. Considera comprar sus cursos por este medio y así apoyas al instructor.