Expresiones de funciones invocadas inmediatamente de JavaScript

Las Expresiones de Función Invocadas Inmediatamente (IFFE) son un patrón común de JavaScript que ejecuta una función instantáneamente después de la definición, en lugar de llamarla después.

Introducción

Las funciones de definición y llamada son prácticas clave para dominar JavaScript y la mayoría de los demás lenguajes de programación. Por lo general, una función se define antes de llamarla en su código.

Las expresiones de función invocadas inmediatamente (IIFE), pronunciadas "dudosas", son un patrón común de JavaScript que ejecuta una función instantáneamente después de que se define. Los desarrolladores utilizan principalmente este patrón para garantizar que solo se pueda acceder a las variables dentro del ámbito de la función definida.

En este artículo, primero aprenderá acerca de las expresiones de función. Después, profundizaremos más en los IIFE: cómo escribirlos y cuándo usarlos. Finalmente, discutiremos cómo la palabra clave let introducida en ECMAScript 6 proporciona una alternativa más limpia para algunos casos de uso de IIFE.

¿Qué son las expresiones de funciones? {#cuáles son las expresiones de función}

En JavaScript, puede definir una función de 2 maneras diferentes:

  1. Una declaración
  2. Una expresión

Las declaraciones de función comienzan con la palabra clave función, seguida del nombre de la función y los argumentos que pueda tomar. Por ejemplo, podemos crear una función logName usando una declaración como esta:

1
2
3
4
5
function logName(userName) {
    console.log(`${userName}, you are awesome`);
};

logName("Jane");

A partir de la definición de la función, registramos cualquier valor dado en el parámetro message en la consola. Luego llamamos a la función con "¡Jane, eres increíble!", que imprimirá ese texto en la consola.

Cuando se define una función usando declaraciones de función, la función se iza. Una función o variable elevada se coloca en la parte superior de su alcance funcional cuando JavaScript está ejecutando código.

Nota: El ámbito funcional se refiere a dónde se definió una función. Por ejemplo, si una función foo() contiene una función bar() dentro de ella, diríamos que el alcance funcional de bar() es foo(). Si foo() no se definió dentro de una función, entonces foo() pertenece al ámbito global. Las funciones de JavaScript en el ámbito global son accesibles para todo el código que se ejecuta con él.

En la práctica, este comportamiento le permite usar una función antes de definirla. Por ejemplo, el código anterior se puede reescribir así y se comportaría de la misma manera:

1
2
3
4
5
logName();

function logName(name) {
    console.log(`${name}, you are awesome!`);
};

Expresiones de funciones son definiciones de funciones que se asignan a una variable. Por lo tanto, nuestra declaración de función logName() puede convertirse en una expresión de función si la creamos así:

1
2
3
4
5
const logUserName = function logName(name) {
    console.log(`${name}, you are awesome!`);
};

logUserName("Jane");

En este ejemplo, para llamar a la función necesitamos usar el nombre de la variable que se proporcionó: logUserName. Esto no cambia el comportamiento de la función, aún registra "Eres genial" en la consola.

A diferencia de las declaraciones de función, las expresiones de función no se elevan. Estas funciones solo están disponibles cuando el intérprete de JavaScript procesa esa línea de código.

Por ejemplo, si intentamos llamar a logUserName() antes de crearlo como una expresión de función:

1
2
3
4
logUserName("Jane");
const logUserName = function logName(name) {
    console.log(`${name}, you are awesome!`);
};

Obtenemos el siguiente error:

1
Uncaught ReferenceError: Cannot access 'logUserName' before initialization

Otra diferencia entre las expresiones de funciones y las declaraciones de funciones es que las expresiones de funciones pueden definir funciones sin un nombre.

Las funciones sin nombre se llaman funciones anónimas. Por ejemplo, logUserName() también podría definirse con una función anónima como esta:

1
2
3
const logUserName = function (name) {
    console.log(`${name}, you are awesome!`);
};

Funciones de flecha {#funciones de flecha}

Las funciones de flecha proporcionan azúcar sintáctico para las expresiones de función. Una reimplementación de nuestra función logUserName usando una función de flecha se vería así:

1
2
3
const logUserName = (name) => {
    console.log(`${name}, you are awesome!`);
}

Lea Funciones de flecha en JavaScript para obtener más información sobre esta sintaxis y cómo afecta el alcance de la función.

Ahora que sabemos cómo crear varias expresiones de funciones, aprendamos cómo invocarlas inmediatamente.

¿Qué son las expresiones de función invocadas inmediatamente? {#cuáles son las expresiones de función inmediatamente invocadas}

Los IIFE son funciones que se ejecutan inmediatamente después de ser definidas.

Podemos convertir cualquier expresión de función en un IIFE envolviéndola entre paréntesis y agregando el siguiente par de paréntesis al final:

1
2
3
(function() {
    // Code that runs in your function
})()

Alternativamente, puede usar la sintaxis de flecha para crear un IIFE de la siguiente manera:

1
2
3
(() => {
    // Code that runs in your function
})()

Los paréntesis que rodean la definición de la función le permiten a JavaScript saber que procesará una expresión de función. El último par de paréntesis invoca la función.

Variaciones de sintaxis

Puede crear IIFE sin el primer conjunto de paréntesis si usa un operador unario: caracteres especiales que le indican a JavaScript que evalúe la siguiente expresión.

Podemos crear expresiones de función con operadores unarios como este:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
+function () {
    // Code that runs in your function
}();

-function () {
    // Code that runs in your function
}();

!function () {
    // Code that runs in your function
}();

~function () {
    // Code that runs in your function
}();

void function () {
    // Code that runs in your function
}();

Es importante tener en cuenta que estos operadores podrían afectar cualquier dato devuelto por su función. Por ejemplo, el siguiente código parece que debería devolver 10, pero en realidad devuelve -10:

1
2
3
4
$ node
> -function () {return 10;}();
-10
>

Dado que esta sintaxis unaria es menos común y puede resultar confusa para los desarrolladores, generalmente se desaconseja.

Los IIFE también pueden tomar argumentos funcionales. Podemos pasar variables al alcance como se muestra a continuación:

1
2
3
(function(arg1, arg2) {
    // Code that runs in your function
})("hello", "world");

Ahora que hemos visto cómo crear IIFE, veamos situaciones comunes en las que se usan.

¿Cuándo usar un IIFE?

Los casos de uso más comunes para IIFE son:

  • Aliasing de variables globales
  • Creación de variables y funciones privadas.
  • Funciones asíncronas en bucles

Aliasing de variables globales

Si tiene 2 bibliotecas que exportan un objeto con el mismo nombre, puede usar IIFE para asegurarse de que no entren en conflicto en su código. Por ejemplo, las bibliotecas de JavaScript jQuery y Dinero exportan $ como su objeto principal.

Puede envolver su código dentro de un IIFE que pasa una de las variables globales como argumento. Digamos que queremos asegurarnos de que $ se refiera al objeto jQuery y no a la alternativa cash. Puede asegurarse de que jQuery se use con el siguiente IIFE:

1
2
3
(function($) {
    // Code that runs in your function
})(jQuery);

Creación de variables y funciones privadas {#creación de variables y funciones privadas}

Podemos usar IIFE para crear variables y funciones privadas dentro del alcance global, o cualquier otro alcance de función.

Las funciones y variables agregadas al ámbito global están disponibles para todos los scripts que se cargan en una página. Digamos que tenemos una función generateMagicNumber(), que devuelve un número aleatorio entre 900 y 1000 inclusive, y una variable favoriteNumber en nuestro archivo JavaScript.

Los podemos escribir así:

1
2
3
4
5
6
7
8
function generateMagicNumber() {
    return Math.floor(Math.random() * 100) + 900;
}

console.log("This is your magic number: " + generateMagicNumber());

var favoriteNumber = 5;
console.log("Twice your favorite number is " + favoriteNumber * 2);

Si cargamos otros archivos JavaScript en nuestro navegador, también obtienen acceso a generateMagicNumber() y favoriteNumber. Para evitar que los usen o editen, encerramos nuestro código en un IIFE:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(function () {
    function generateMagicNumber() {
        return Math.floor(Math.random() * 100) + 900;
    }

    console.log("This is your magic number: " + generateMagicNumber());

    var favoriteNumber = 5;
    console.log("Twice your favorite number is " + favoriteNumber * 2);
})();

Se ejecuta igual, pero ahora solo se puede acceder a generateMagicNumber() y favoriteNumber en nuestro script.

Funciones asíncronas en bucles

El comportamiento de JavaScript sorprende a muchos cuando las devoluciones de llamada se ejecutan en bucles. Por ejemplo, contemos del 1 al 5 en JavaScript, poniendo un espacio de 1 segundo entre cada vez que registramos un mensaje. Una implementación ingenua sería:

1
2
3
4
5
for (var i = 1; i <= 5; i++) {
    setTimeout(function () {
        console.log('I reached step ' + i);
    }, 1000 * i);
}

Si ejecuta este código, obtendrá el siguiente resultado:

1
2
3
4
5
6
$ node naiveCallbackInLoop.js
I reached step 6
I reached step 6
I reached step 6
I reached step 6
I reached step 6

Si bien la salida se imprimiría 1 segundo después de la otra, cada línea imprime que llegaron al paso 6. ¿Por qué?

Cuando JavaScript encuentra código asíncrono, difiere la ejecución de la devolución de llamada hasta que se completa la tarea asíncrona. Así es como permanece sin bloqueo. En este ejemplo, la instrucción console.log() se ejecutará solo después de que haya transcurrido el tiempo de espera.

JavaScript también creó un cierre para nuestra devolución de llamada. Los cierres son una combinación de una función y su alcance cuando se creó. Con los cierres, nuestra devolución de llamada puede acceder a la variable i aunque el bucle for ya haya terminado de ejecutarse.

Sin embargo, nuestra devolución de llamada solo tiene acceso al valor de i en el momento de su ejecución. Como el código dentro de la función setTimeout() se aplazó, el bucle for finalizó con i igual a 6. Es por eso que todos registran que llegaron al paso 6.

Este problema se puede resolver con un IIFE:

1
2
3
4
5
6
7
for (var i = 1; i <= 5; i++) {
    (function (step) {
        setTimeout(function() {
            console.log('I reached step ' + step);
        }, 1000 * i);
    })(i);
}

Al usar un IIFE, creamos un nuevo alcance para nuestra función de devolución de llamada. Nuestro IIFE toma un parámetro paso. Cada vez que se llama a nuestro IIFE, le damos el valor actual de i como su argumento. Ahora, cuando la devolución de llamada esté lista para ser ejecutada, su cierre tendrá el valor correcto de paso.

Si ejecutamos este fragmento de código, veremos el siguiente resultado:

1
2
3
4
5
6
$ node iifeCallbackInLoop.js
I reached step 1
I reached step 2
I reached step 3
I reached step 4
I reached step 5

Si bien los IIFE resuelven nuestro problema con cambios mínimos en el código, echemos un vistazo a cómo las funciones de ES6 pueden facilitar la ejecución de código asincrónico en bucles.

Ámbito de bloques con let y const

ES6 agregó las palabras clave let y const para crear variables en JavaScript. Las variables declaradas con let o const tienen ámbito de bloque. Esto significa que solo se puede acceder a ellos dentro de su bloque adjunto: una región encerrada entre llaves { }.

Contemos de 1 a 5 en intervalos de 1 segundo usando la palabra clave let en lugar de var:

1
2
3
4
5
for (let i = 1; i <= 5; i++) {
    setTimeout(function () {
        console.log('I reached step ' + i);
    }, 1000 * i);
}

Obtendremos el siguiente resultado cuando ejecutemos este código:

1
2
3
4
5
6
$ node es6CallbackInLoop.js
I reached step 1
I reached step 2
I reached step 3
I reached step 4
I reached step 5

Ahora que la variable i tiene un alcance de bloque, los cierres para nuestra función de devolución de llamada obtienen el valor apropiado de i cuando finalmente se ejecutan. Esto es más conciso que nuestra implementación IIFE.

Usar let es la forma preferida de ejecutar funciones asincrónicas en un bucle,

Conclusión

Una expresión de función invocada inmediatamente (IIFE) es una función que se ejecuta instantáneamente después de que se define. Este patrón se ha utilizado para alias de variables globales, hacer que las variables y funciones sean privadas y para garantizar que el código asíncrono en los bucles se ejecute correctamente.

Si bien es popular, hemos visto cómo los cambios en ES6 pueden eliminar la necesidad de usar IIFE en JavaScript moderno. Sin embargo, dominar este patrón también nos brinda una comprensión más profunda del alcance y el cierre, y será especialmente útil para mantener el código JavaScript heredado. edado.