1

Tengo el siguiente código:

function creaSumador(x) {
  return function(y) {
    return x + y;
  };
}

var suma5 = creaSumador(5);
var suma10 = creaSumador(10);

console.log(suma5(2));  // muestra 7
console.log(suma10(2)); // muestra 12 

el mismo lo puedo hacer asi

function suma(x,y) {
 
    return x + y;
  
}



console.log(suma(5,2));  // muestra 7
console.log(suma(10,2)); // muestra 12 

No veo mucha importancia a las funciones closures, ya que también se puede hacer sin closures, me pueden explicar cuando usar closure, cuando en realidad es necesario?

Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
hubman
  • 2,624
  • 9
  • 35
  • 75
  • antiguamente JavaScript no tenia la palabra reservada Class, y como hacian para trabajar lo mas parecido a la Programacion Orientada a Objeto? era con [Clousures](https://es.stackoverflow.com/a/173859/28035) de hecho algunos puristas dicen que JavaScript no es un lenguaje Orientado a Objetos – JackNavaRow Jun 20 '19 at 14:49

3 Answers3

6

Yo lo encuentro útil a la hora de crear propiedades 'publicas' o 'privadas':

var functions = (function () {
    var private = 'this is private'; 

    return {
        publicAccess: function () {
            return private;
        }
    }
})(); 

Entonces de esta manera de la variable functions solamente tendrías acceso de afuera a publicAccess. No podrá modificarse el valor private directamente accediendo a ella.

Manuel Obregozo
  • 201
  • 1
  • 4
  • mmm me parece coherente tu respuesta pero alguna utilidad mas? – hubman Dec 23 '16 at 22:25
  • Llevado a tu ejemplo. Imagina que su función creaSumador, se trata de una función que calcula el valor de diferentes monedas. Entonces tu podrías instanciar esa función, de la misma manera que tu lo hiciste con suma5 y suma10. Y tendrías de esta manera la posibilidad de definir gracias a los closures, diferentes convertidores de monedas extranjeras. – Manuel Obregozo Dec 26 '16 at 00:04
0

Tu ejemplo peca de sencillo, pero el poder generar una función con valores predefinidos puede ser útil cuando se la tienes que pasar como argumento a otra:

Por ejemplo, Angular tiene unas funciones validadoras genéricas:

let form = [
  { name: 'campo', validators: [Validators.required, Validators.minLength(4)]}
];

Como deducirás, Validators.required es una función a la que le pasas el campo y comprueba si está vacío. Eso es relativamente sencillo de implementar. En cambio, cuando escribimos Validators.minLength(4) ¡estamos llamando a una función! Puesto que lo que necesitamos es una función validadora que compruebe que la longitud sea la que le hemos pasado, el código sería algo así:

Validators.minLength = function (le) {
  return function (control) {
    //si está vacío, no comprobamos, quizá no sea un campo requerido
    if (control.value === '') return true; 
    return control.value.length >= le;
  };
};

De todos modos,nos estamos limitando a clausuras donde el resultado es una función. También se considera clausura el acceso a variables externas desde funciones callbacks. Un ejemplo sería la clásica llamada AJAX:

function test(callback) {
  var req = new XMLHttpRequest();
  req.open('GET', 'https://swapi.co/api/planets/1/', true);
  req.onreadystatechange = function (aEvt) {
    if (req.readyState == 4) {
      if(req.status == 200) {
        // Estamos usando la variable req, es una clausura!
        console.log(req.response);
        //La función callback es un parámetro de la función que crea la clausura
        callback(req.response);
      }
    }
  };
  req.send(null); //aquí se ejecuta realmente la llamada, y la función test termina
}

La función test terminará su ejecución antes de que se obtenga la respuesta, pero puesto que la función callback se llamará entonces, las variables usadas deben ser mantenidas.

Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
0

Se ve que ya conoces el término y de que se trata, pero no podes conectarlo con un use-case, así que voy a intentar proveer uno.


Imaginate que nos piden hacer una aplicación para una biblioteca: El personal necesita saber que libros hay disponibles en todo momento, los usuarios pueden retirar libros o devolver libros. Además, necesitamos mostrar la cantidad de libros actual en otra parte de la aplicación.

Se vería algo así:

Una lista con libros, texto con la cantidad total de libros y un formulario para devolver y retirar libros

Cuando nosotros agregamos o retiramos un libro, tanto el total, como la lista deberían actualizarse:

introducir la descripción de la imagen aquí introducir la descripción de la imagen aquí

Como primer paso, vamos a hacer una función en la cual modifique la cantidad de libros en nuestra biblioteca y esto va a ocurrir con dos tipos de acciones, devolver y retirar libros. Nuestros libros tienen un título y un número único isbn, que esta información va a venir dentro de nuestra acción como payload

// En un principio nuestra biblioteca va a tener 3 libros
// y dependiendo de nuestra acción, es qué vamos a hacer (agregar o quitar libros).
// Identificamos que libro sacar o agregar por medio de nuestro payload

let books = [
  {
    title: "The Fellowship of the Ring",
    isbn: 1
  },
  {
    title: "The End of Eternity",
    isbn: 2
  },
  {
    title: "Guards! Guards!",
    isbn: 3
  }
]

const library = (action = {}) => {
  switch (action.type) {
    case 'ADD_BOOK':
      return books.push(action.payload);
    case 'REMOVE_BOOK':
      return books = books.filter((b) => b.isbn !== action.payload.isbn);
    default:
      return books;
  }
}

Si probamos nuestra función library, podemos ver que agrega y quita libros correctamente. Si no pasamos ninguna acción, va a devolver los libros actuales.

Pero de esta forma no podemos mantener los datos sincronizados, va a depender de cuando se lee el valor de books.

Para poder lograr compartir los datos y mantener sincronizada la aplicación, necesitamos almacenar nuestros libros en un almacenamiento general y este a su vez, necesita informar al exterior que han ocurrido cambios en los datos.

Vamos a crear primero la función que va a manipular los datos de nuestra biblioteca:

// nuestro store va a recibir como parámetro una función que es la que va a modificar los datos
const createStore = updater => {
  // inicializamos nuestro almacenamiento vacío
  let store;
  // y necesitamos saber quienes están esperando los cambios
  const listeners = [];

  // Creamos un método para obtener el estado en el momento actual
  const getState = () => store

  // La única forma en que se van a actualizar los datos, es mediante este método.
  // Se encarga de llamar al updater y guardar los valores actualizados en store.
  // Debemos pasar que acción queremos realizar, esta va a contener tipo y payload
  const dispatch = action => {
    // pasamos nuestro store actual al updater y la acción que se quiere aplicar
    store = updater(store, action);
    // llamamos a todos los listeners para que actualicen sus valores
    listeners.forEach((listener) => listener());
  }

  // necesitamos este método para poder saber y almacenar las funciones que hay que
  // llamar cuando se actualicen los datos
  const subscribe = listener => {
    listeners.push(listener);
  }

  // antes de exponer todos nuestros métodos, necesitamos popular información en
  // nuestro store, así que llamamos a dispatch con una acción vacía:
  dispatch({});

  // Hacemos uso de Closures
  return { getState, dispatch, subscribe };
}

Ahora deberíamos modificar nuestra función library, para que en vez de modificar una variable, devuelva un nuevo store, usando como referencia el que se le pasa como argumento (proveniente de createStore):

// el primer argumento viene de nuestro Store, sería el `state` actual de nuestros libros
// la primera vez que se llama, el argumento books va a estar vacío, por lo cual, 
// inicalizamos con los libros definidos en `books`, renombrado a `initialBooks`

const library = (books = initialBooks, action) => {
  switch (action.type) {
    case "RETURN_BOOK":
      return [...books, action.payload];
    case "BORROW_BOOK":
      return books.filter(b => b.isbn !== action.payload.isbn);
    default:
      return books;
  }
};

Es hora de poner todas las cosas a funcionar al mismo tiempo, vamos a crear nuestra biblioteca:

// MyLibrary contiene getState, para obtener los libros actuales
// dispatch: para enviar nuevas acciones y actualizar los datos
// subscribe: para poder escuchar estos cambios y mostrarlos en pantalla
const MyLibrary = createStore(library);

Vamos a crear los métodos para poder mostrar en pantalla los datos. Voy a crear renderBooks para mostrar la lista de libros y renderTotal para mostrar el total. Voy a remarcar lo importante con comentarios:

const renderBooks = () => {
  // Obtenemos los libros del estado de biblioteca
  const books = MyLibrary.getState();
  const bookList = document.querySelector(".book-list");
  bookList.innerHTML = "";

  // y usamos sus valores para mostrar una lista de libros
  books.forEach(book => {
    const bookItem = document.createElement("li");
    bookItem.className = "list-group-item";
    bookItem.innerHTML = `Titulo: ${book.title} <br/> ISBN: ${book.isbn}`;
    bookList.appendChild(bookItem);
  });
};

const renderTotal = () => {
  // obtengo los libros de la biblioteca
  const books = MyLibrary.getState();
  const total = document.querySelector(".total");
  // y muestro el total en pantalla
  total.innerHTML = books.length;
};

Subscribimos a los cambios a ambas funciones (las guardamos como listeners) y las llamamos para popular los valores iniciales en pantalla:

MyLibrary.subscribe(renderBooks);
MyLibrary.subscribe(renderTotal);

renderTotal();
renderBooks();

Lo único que queda ahora es obtener los datos del formulario y enviarlos utilizando a dispatch, junto con nuestras acciones:

const returnBookButton = document.querySelector(".return-book");
returnBookButton.onclick = e => {
  e.preventDefault();

  const title = document.querySelector(".title").value;
  const isbn = document.querySelector(".isbn").value;
  if (!title || !isbn) {
    return;
  }

  // envío la acción devolver libro cuando hago click en el botón correspondiente
  // paso a la acción el título e isbn ingresado para agregarlos a la lista
  MyLibrary.dispatch({ type: "RETURN_BOOK", payload: { title, isbn } });
  // Recordemos que una vez actualizados estos datos en el store,
  // cada listener se va a llamar para mostrar los datos nuevos

  title.value = "";
  isbn.value = "";
};

const borrowBookButton = document.querySelector(".borrow-book");
borrowBookButton.onclick = e => {
  e.preventDefault();

  const isbn = document.querySelector(".isbn").value;

  if (!isbn) {
    return;
  }

  // envío la acción retirar libro cuando hago click en el botón correspondiente
  // paso a la acción el isbn ingresado para sacarlo de la lista
  MyLibrary.dispatch({
    type: "BORROW_BOOK",
    payload: { isbn: parseInt(isbn, 10) }
  });

  isbn.value = "";
};

Ahora si queremos mostrar los libros en cualquier parte de la aplicación, solamente tenemos que suscribir un método a nuestro store y toda la aplicación se va a actualizar, porque hay un solo source of truth.

Acá está el Codesandbox de esta mini aplicación, hay un par de cositas que agregué extras.

Este patrón es el que usa trás bambalinas la librería Redux.

Espero que este use-case haya servido de algo!

Yakume
  • 411
  • 2
  • 6