El extraño caso de la función negada en Javascript

08 Jun 2012

Introducción

Tras este novelesco título lo que se esconde en esta ocasión es una estructura que podemos encontrar últimamente en bibliotecas y códigos de terceros. Se trata de una variante de las funciones autoejecutables (o que se auto invocan) que ya tratamos aquí hace algún tiempo.

En concreto, se trata de la siguiente pieza de código:

!function() {
  // My awesome code...
}();

¿Qué es exactamente esta función negada? ¿Cómo y porqué funciona? Echemos un vistazo a las tripas de Javascript…

Funciones autoejecutables

Quizá el nombre correcto para esta estructura sea el de funciones auto invocadas pero como me suena mal, evito utilizarlo. El concepto tras esta idea es la de declarar una función que se inicia inmediatamente sin necesidad de llamarla de forma explícita después. Lo vemos a diario en códigos modernos (jQuery, Underscore, Zepto, …) y suele presentarse con el siguiente aspecto:

( function() {
 
} )();

Como vimos en su día, el último juego de paréntesis hace la magia y lleva a cabo la auto invocación.

De forma alternativa, hay quienes prefieren incluir estos últimos paréntesis dentro del primer par:

( function() {
 
} () );

Personalmente no me gusta esta estructura porque la encuentro poco legible; sin embargo, es perfectamente válida.

Recordado esto, estudiemos ahora qué pasa con nuestra extraña función negada

A vueltas con la especificación ECMAScript

En un mundo ideal, una función anónima auto ejecutable debería construirse del siguiente modo:

function() {
  // ...
}();

Sin embargo, esto nos da un error de sintaxis: el intérprete, cuando encuentra al comienzo de una línea la palabra function espera que ésta sea una declaración de función y las declaraciones tienen la siguiente estructura:

function <em>Identifier</em> ( <em>FormalParameterList</em> ) { <em>FunctionBody</em> }

Donde:

Identifier es el identificador/nombre de la función.
FormalParameterList son los parámetros, opcionales, que podemos pasar.
FunctionBody es el bloque de código que compone nuestra función.

Como nuestra función ideal auto ejecutable no cumple este patrón (de entrada no tiene identificador), se produce el error.

Una forma de evitar esto es enrollando, como hemos hecho hasta ahora, a nuestra función entre paréntesis. Con esto, evitamos crear una declaración para pasar a evaluar la función como una expresión:

( function() {
  // ...
} )();

Y aquí ya no hay error. La cuestión, y la clave de esta estructura, es que toda forma de crear una expresión será válida y funcionará como se espera. Ahí entra en juego nuestro operador de negación (!).

!function() {
  // ...
}();

El operador unario de negación (NOT) convierte el objeto sobre el que se aplica en una expresión permitiendo así que la función pueda invocarse así misma.

Del mismo modo, en determinados escenarios puede incluso prevenirnos de errores como los que pueden darse al olvidar el punto y coma al final de cada instrucción. Valoremos los dos siguientes fragmentos:

var foo = 'bar'
 
(function() {
  console.log('Hello World');
} )();
var foo = 'bar'
 
!function() {
  console.log('Hello World');
}();

A causa del juego de paréntesis, el primero de ellos dará un error del tipo «bar» is not a function `(function() {, mientras que el segundo, al separar la función de la instrucción anterior con el operador, si funcionaría.

NOTA: Esta particularidad puede ser especialmente interesante cuando concatenamos ficheros Javascript. Con esta práctica, evitamos tener que estar pendientes si el fichero que lo precede tiene su código correctamente formateado. Es por este motivo precisamente por el que otras tantas veces vemos empezar un fichero JS con un punto y coma:

/*!
* Lo-Dash v0.3.0-pre <http://lodash.com>
* Copyright 2012 John-David Dalton <http://allyoucanleet.com/>
* Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
* <http://documentcloud.github.com/underscore>
* Available under MIT license <http://lodash.com/license>
*/
;(function(window, undefined) {
  'use strict';
  //...

Nótese el punto y coma justo antes de la función que enrolla al resto del código.

(Ejemplo extraído de la biblioteca Lodash de John David Dalto)

Formas más exóticas

Visto lo anterior, si cualquier operador con el que podamos hacer que function sea una expresión funciona, todos los ejemplos que siguen son también válidos:

+function() {
  // ...
}();
 
-function() {
  // ...
}();
 
~function() {
  // ...
}();
 
delete function() {
  // ...
}();
 
typeof function() {
  // ...
}();
 
void function() {
  // ...
}();

Si a estas alturas alguien se está ya preguntando por una posible diferencia de rendimiento entre cada método, cada navegador lo trata de una determinada manera según fabricante y versión. Podemos ver una comparativa con algunos de ellos en esta página de JSPerf, nuestro servicio de medición del rendimiento por excelencia.

La razón de que veamos con más frecuencia la negación es por simple convención: se pretende que solo con ver este indicador delante de una función, se sepa que estamos ante una de tipo autoejecutable (o auto invocada).

Conclusión

Como casi siempre, en Javascript hay varias formas de conseguir un mismo resultado.

En esta ocasión, ya sea por curiosidad, por seguir las convenciones más actuales o buscando una mayor seguridad en la ejecución de nuestros códigos, hemos visto una alternativa a la definición de funciones autoejecutables, verdaderas piedras angulares del desarrollo Javascript moderno.

Más:

{6} Comentarios.

  1. Cristian Martín Mouat

    Me gusta mucho, lo encuentro más legible que las autoejecutables con paréntesis.

  2. pykiss

    OJO
    jshint se queja con la función negada.

  3. ewarez

    Muy útil la información, te lo agradezco, un gran saludo amigo

  4. Matías

    Excelente. Estaba analizando una librería y no entendía este uso. Gracias.

  5. Luis

    amm.. he estado reescribiendo este comentario durante practicamente 2 horas u_ú !

    me apegare al principio KISS y te diré:
    – loco tu deberías escribir un libro, hacer un curso o lo que sea, que sea gratuito o de pago no importa, pero deberías hacerlo.

    ¿por qué? ¿que a que me refiero?

    ps me refiero al limito impuesto… ¿ que aun no pillas de que voy ? tranquilo ya lo entenderás 😉 !

    bueno se me ocurren miles de razones para decir porque deberías hacerlo que no se cual escribir, pero la principal es:
    en internet los libros y cursos te prometen enseñarte TODO, convertirte en un GRAN PROGRAMADOR… pero gracias a esos cursos y libros (algunos hasta de pago) me he dado cuenta que estoy en el nivel básico de tu articulo «Tu no sabes javascript» basto con leerme tu sitio web para darme cuenta que ajaja todo este tiempo creí saber mucho y estar muy preparado, pero que realmente no sabia NADA >:c !

    Es como si todos esos libros y cursos se hubieran puesto de acuerdo a no sobrepasar un LIMITE de enseñanza y viendo tu sitio ese nivel es realmente muy bajo es el básico, por eso tu deberías escribir un libro que sobrepase ese limite, aunque trate solo un tema de los que hablas aquí en tu web, la POO en Javascript por ejemplo.

    bueno eso es el porque y a que me refiero y vamos digo que tu deberías, pero esta claro que no es tu obligación es solo si quieres y es que tu tienes el conocimiento, explicas super bien, hasta demasiado algunas cosas y tal vez no tengas tiempo pero aunque le dediques 20 minutos al libro o curso por día, aunque escribas solo unas cuantas lineas algún día lo terminaras y será genial 😉

    ajajajaja bueno, aunque no hagas el libro, curso o lo que sea ya dejaste la mitad de mi frustrado y la otra mitad con ganas de aprender y salir de ese pequeño mundo donde creía saber mucho *O* !
    algún día seré grande como tu, ahora eres mi meta y si para cuando te alcance no existe esa fuente de conocimientos que le expliquen a los «mortales» como mi yo actual, como sobrepasar ese limite entonces lo haré yo, sin rencores lml !
    hasta entonces…

  6. Elizabeth

    Realmente interesante. Ignoraba esta alternativa a los paréntesis, lo encuentro más legible. Gracias!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *