Una función es un bloque de código paramétrico que se define una vez y se llama varias veces después. En JavaScript una función está compuesta e influenciada por muchos componentes:
- Código JavaScript que forma el cuerpo de la función
- La lista de parámetros
- Las variables accesibles desde el ámbito léxico
- El valor devuelto
- El contexto
this
cuando se invoca la función - .
- Nombre de la función o una función anónima
- La variable que contiene el objeto de la función
-
arguments
objeto (o falta en una función de flecha) - Asignada a una variable como un objeto
count = function(...) {...}
- Crea un método en un objeto
sum: function() {...}
. - Utiliza la función como callback
.reduce(function(...) {...})
- Se crea una función con nombre, es decir.
name
propiedad contiene el nombre de la función - Dentro del cuerpo de la función una variable con el mismo nombre contiene el objeto de la función
- Los mensajes de error y las pilas de llamadas muestran información más detallada cuando se utilizan los nombres de las funciones
- Más comodidad en la depuración al reducir el número de nombres anónimos de la pila
- El nombre de la función dice lo que hace la función
- Se puede acceder a la función dentro de su ámbito para realizar llamadas recursivas o desprender escuchadores de eventos
- Una sintaxis más corta es más fácil de entender
- La definición abreviada de métodos crea una función con nombre, al contrario que una expresión de función. Es útil para la depuración.
- La función flecha no crea su contexto de ejecución, sino que lo toma léxicamente (al contrario que la expresión de función o la declaración de función, que crean su propio
this
en función de la invocación) - La función flecha es anónima. Sin embargo, el motor puede inferir su nombre a partir de la variable que contiene la función.
- El objeto
arguments
no está disponible en la función de flecha (al contrario que otros tipos de declaración que proporcionanarguments
objeto). Usted es libre de utilizar los parámetros de descanso(...params)
, sin embargo.
Este post te enseña seis enfoques para declarar funciones de JavaScript: la sintaxis, los ejemplos y las trampas más comunes. Además, entenderás cuándo utilizar un tipo de función específico en determinadas circunstancias.
1. Declaración de funciones
Una declaración de función está formada por la palabra clave
function
, seguida obligatoriamente de un nombre de función, una lista de parámetros en un par de paréntesis(para1, ..., paramN)
y un par de llaves{...}
que delimita el cuerpo del código.
Un ejemplo de declaración de función:
// function declarationfunction isEven(num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false
function isEven(num) {...}
es una declaración de función que define isEven
la función que determina si un número es par.
La declaración de la función crea una variable en el ámbito actual con el identificador igual al nombre de la función. Esta variable contiene el objeto de la función.
La variable de la función se eleva a la parte superior del ámbito actual, lo que significa que la función puede ser invocada antes de la declaración (ver este capítulo para más detalles).
La función creada tiene nombre, lo que significa que la propiedad name
del objeto función contiene su nombre. Es útil a la hora de ver la pila de llamadas: en la depuración o en la lectura de mensajes de error.
Veamos estas propiedades en un ejemplo:
// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name) // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}
La declaración de la función function hello(name) {...}
crea una variable hello
que se eleva a la parte superior del ámbito actual. La variable hello
contiene el objeto de la función y hello.name
contiene el nombre de la función: 'hello'
.
1.1 Una función regular
La declaración de la función coincide para los casos en que se necesita una función regular. Regular significa que se declara la función una vez y posteriormente se invoca en muchos lugares diferentes. Este es el escenario básico:
function sum(a, b) { return a + b;}sum(5, 6); // => 11().reduce(sum) // => 10
Debido a que la declaración de función crea una variable en el ámbito actual, junto con las llamadas a funciones regulares, es útil para la recursión o para separar los oyentes de eventos. Al contrario que las expresiones de función o las funciones de flecha, que no crean un enlace con la variable de la función por su nombre.
Por ejemplo, para calcular recursivamente el factorial hay que acceder a la función dentro de:
function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1);}factorial(4); // => 24
Dentro de factorial()
se está haciendo una llamada recursiva utilizando la variable que contiene la función: factorial(n - 1)
.
Es posible utilizar una expresión de función y asignarla a una variable regular, por ejemplo const factorial = function(n) {...}
. Pero la declaración de función function factorial(n)
es compacta (no es necesario const
y =
).
Una propiedad importante de la declaración de función es su mecanismo de elevación. Permite utilizar la función antes de la declaración en el mismo ámbito.
El hoisting es útil en algunas situaciones. Por ejemplo, cuando se quiere ver cómo se llama a la función al principio de un script, sin leer la implementación de la función. La implementación de la función puede estar situada más abajo en el archivo, por lo que es posible que ni siquiera se desplace hasta allí.
Puede leer más detalles sobre el levantamiento de la declaración de función aquí.
1.2 Diferencia con la expresión de función
Es fácil confundir la declaración de función y la expresión de función. Se parecen mucho pero producen funciones con propiedades diferentes.
Una regla fácil de recordar: la declaración de función en una sentencia siempre empieza con la palabra clave function
. De lo contrario, es una expresión de función (ver 2.).
El siguiente ejemplo es una declaración de función en la que el enunciado comienza con la palabra clave function
:
// Function declaration: starts with "function"function isNil(value) { return value == null;}
En el caso de las expresiones de función, la declaración de JavaScript no comienza con la palabra clave function
(está presente en algún punto del código de la declaración):
// Function expression: starts with "const"const isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()const numbers = ().filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');
1.3 Declaración de funciones en condicionales
Algunos entornos JavaScript pueden lanzar un error de referencia al invocar una función cuya declaración aparece dentro de bloques {...}
de sentencias if
for
o while
.
Habilitemos el modo estricto y veamos qué ocurre cuando se declara una función en un condicional:
(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();
Al llamar a ok()
, JavaScript lanza ReferenceError: ok is not defined
, porque la declaración de la función está dentro de un bloque condicional.
La declaración de la función en condicionales está permitida en modo no estricto, lo que lo hace aún más confuso.
Como regla general para estas situaciones, cuando una función debe ser creada por condiciones – utilice una expresión de función. Veamos cómo es posible:
(function() { 'use strict'; let ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();
Porque la función es un objeto regular, asignarlo a una variable dependiendo de la condición. Invocar ok()
funciona bien, sin errores.
2. Expresión de función
Una expresión de función viene determinada por una
function
palabra clave, seguida de un nombre de función opcional, una lista de parámetros en un par de paréntesis(para1, ..., paramN)
y un par de llaves{ ... }
que delimita el cuerpo del código.
Algunos ejemplos de la expresión de la función:
const count = function(array) { // Function expression return array.length;}const methods = { numbers: , sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count(); // => 3methods.sum(); // => 14
La expresión de la función crea un objeto de función que se puede utilizar en diferentes situaciones:
La expresión de la función es el caballo de batalla en JavaScript. Normalmente, se maneja este tipo de declaración de función, junto a la función flecha (si se prefiere la sintaxis corta y el contexto léxico).
2.1 Expresión de función con nombre
Una función es anónima cuando no tiene nombre (name
propiedad es una cadena vacía ''
):
( function(variable) {return typeof variable; }).name; // => ''
Esta es una función anónima, cuyo nombre es una cadena vacía.
A veces el nombre de la función se puede inferir. Por ejemplo, cuando la anónima se asigna a una variable:
const myFunctionVar = function(variable) { return typeof variable; };myFunctionVar.name; // => 'myFunctionVar'
El nombre de la función anónima es 'myFunctionVar'
, porque myFunctionVar
nombre de la variable se utiliza para inferir el nombre de la función.
Cuando la expresión tiene el nombre especificado, se trata de una expresión de función con nombre. Tiene algunas propiedades adicionales en comparación con la expresión de función simple:
Usemos el ejemplo anterior, pero estableciendo un nombre en la expresión de la función:
const getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number'console.log(getType.name); // => 'funName'console.log(typeof funName); // => 'undefined'
function funName(variable) {...}
es una expresión de función con nombre. La variable funName
es accesible dentro del ámbito de la función, pero no fuera. En cualquier caso, la propiedad name
del objeto de la función contiene el nombre: funName
.
2.2 Expresión de función con nombre de favor
Cuando una expresión de función const fun = function() {}
se asigna a una variable, algunos motores infieren el nombre de la función a partir de esta variable. Sin embargo, las devoluciones de llamada podrían pasarse como expresiones de función anónimas, sin almacenarse en variables: así el motor no puede determinar su nombre.
Es razonable favorecer las funciones con nombre y evitar las anónimas para obtener beneficios como:
3. Definición abreviada de métodos
La definición abreviada de métodos se puede utilizar en una declaración de métodos sobre literales de objetos y clases ES2015. Puedes definirlos usando un nombre de función, seguido de una lista de parámetros en un par de paréntesis
(para1, ..., paramN)
y un par de llaves{ ... }
que delimita las declaraciones del cuerpo.
El siguiente ejemplo utiliza una definición de método abreviada en un literal de objeto:
const collection = { items: , add(...items) { this.items.push(...items); }, get(index) { return this.items; }};collection.add('C', 'Java', 'PHP');collection.get(1) // => 'Java'
add()
y get()
métodos en el objeto collection
se definen utilizando la definición de método abreviado. Estos métodos se llaman como siempre: collection.add(...)
y collection.get(...)
.
El enfoque corto de la definición de métodos tiene varios beneficios sobre la definición tradicional de propiedades con un nombre, dos puntos :
y una expresión de función add: function(...) {...}
:
La sintaxis class
requiere declaraciones de métodos de forma abreviada:
class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}const sun = new Star('Sun');sun.getMessage(' is shining') // => 'Sun is shining'
3.1 Nombres de propiedades y métodos computados
ECMAScript 2015 añade una buena característica: nombres de propiedades computados en literales de objetos y clases.
Las propiedades computadas utilizan una sintaxis ligeramente diferente () {...}
, por lo que la definición del método tiene este aspecto:
const addMethod = 'add', getMethod = 'get';const collection = { items: , (...items) { this.items.push(...items); }, (index) { return this.items; }};collection('C', 'Java', 'PHP');collection(1) // => 'Java'
(...) {...}
y (...) {...}
son declaraciones abreviadas de métodos con nombres de propiedades computadas.
4. Función de flecha
Una función de flecha se define utilizando un par de paréntesis que contiene la lista de parámetros
(param1, param2, ..., paramN)
, seguido de una flecha gorda=>
y un par de llaves{...}
que delimita las declaraciones del cuerpo.
Cuando la función de flecha tiene un solo parámetro, el par de paréntesis puede omitirse. Cuando contiene una sola sentencia, las llaves pueden ser omitidas también.
Veamos el uso básico de la función flecha:
const absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10absValue(5); // => 5
absValue
es una función flecha que calcula el valor absoluto de un número.
La función declarada mediante una flecha gorda tiene las siguientes propiedades:
4.1 Transparencia del contexto
this
La palabra clave es un aspecto confuso de JavaScript (consulta este artículo para una explicación detallada sobre this
).
Debido a que las funciones crean su propio contexto de ejecución, a menudo es difícil detectar this
el valor.
ECMAScript 2015 mejora el uso de this
introduciendo la función arrow, que toma el contexto de forma léxica (o simplemente utiliza this
del ámbito exterior inmediato). Esto es bueno porque no tienes que usar .bind(this)
o almacenar el contexto var self = this
cuando una función necesita el contexto envolvente.
Veamos cómo se hereda this
de la función exterior:
class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}const numbersObject = new Numbers();const addMethod = numbersObject.addNumber();addMethod(1);addMethod(5);console.log(numbersObject.array); // =>
Numbers
la clase contiene un array de números y proporciona un método addNumber()
para insertar nuevos números.
Cuando se llama a addNumber()
sin argumentos, se devuelve un cierre que permite insertar números. Este cierre es una función de flecha que tiene this
como instancia numbersObject
porque el contexto se toma léxicamente del método addNumbers()
.
Sin la función de flecha, tienes que arreglar manualmente el contexto. Significa usar soluciones como el método .bind()
:
//... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...
o almacenar el contexto en una variable separada var self = this
:
//... const self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...
La transparencia del contexto se puede utilizar cuando se quiere mantener this
tal cual, tomado del contexto que lo encierra.
4.2 Devoluciones de llamada cortas
Cuando se crea una función de flecha, los pares de paréntesis y las llaves son opcionales para un solo parámetro y una sola declaración de cuerpo. Esto ayuda a crear funciones callback muy cortas.
Hagamos una función que encuentre si un array contiene 0
:
const numbers = ;numbers.some(item => item === 0); // => true
item => item === 0
es una función flecha que parece sencilla.
Nótese que las funciones de flecha corta anidadas son difíciles de leer. La forma más conveniente de usar la función de flecha corta es una sola devolución de llamada (sin anidación).
Si es necesario, utilice la sintaxis expandida de las funciones de flecha cuando escriba funciones de flecha anidadas. Simplemente es más fácil de leer.
5. Función generadora
La función generadora en JavaScript devuelve un objeto Generador. Su sintaxis es similar a la de la expresión de función, declaración de función o declaración de método, sólo que requiere un carácter de estrella *
.
La función generadora puede declararse de las siguientes formas:
a. Forma de declaración de la función function* <name>()
:
function* indexGenerator(){ var index = 0; while(true) { yield index++; }}const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
b. Formulario de expresión de función function* ()
:
const indexGenerator = function* () { let index = 0; while(true) { yield index++; }};const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
c. Forma abreviada de definición del método *<name>()
:
const obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}const g = obj.indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
En los 3 casos la función generadora devuelve el objeto generador g
. Posteriormente g
se utiliza para generar series de números incrementados.
6. Una cosa más: nueva Función
En JavaScript las funciones son objetos de primera clase – una función es un objeto regular de tipo function
.
Las formas de la declaración descritas anteriormente crean el mismo tipo de objeto función. Veamos un ejemplo:
function sum1(a, b) { return a + b;}const sum2 = function(a, b) { return a + b;}const sum3 = (a, b) => a + b;console.log(typeof sum1 === 'function'); // => trueconsole.log(typeof sum2 === 'function'); // => trueconsole.log(typeof sum3 === 'function'); // => true
El tipo de objeto función tiene un constructor: Function
.
Cuando se invoca Function
como constructor new Function(arg1, arg2, ..., argN, bodyString)
, se crea una nueva función. Los argumentos arg1, args2, ..., argN
pasados al constructor se convierten en los nombres de los parámetros de la nueva función y el último argumento bodyString
se utiliza como código del cuerpo de la función.
Creemos una función que sume dos números:
const numberA = 'numberA', numberB = 'numberB';const sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25
sumFunction
creada con el constructor Function
. la invocación tiene los parámetros numberA
y numberB
y el cuerpo return numberA + numberB
.
Las funciones creadas de esta manera no tienen acceso al ámbito actual, por lo que no se pueden crear cierres. Siempre se crean en el ámbito global.
Una posible aplicación de new Function
es una mejor forma de acceder al objeto global en un navegador o en un script de NodeJS:
(function() { 'use strict'; const global = new Function('return this')(); console.log(global === window); // => true console.log(this === window); // => false})();
Recuerda que las funciones casi nunca deben declararse usando new Function()
. Debido a que el cuerpo de la función se evalúa en tiempo de ejecución, este enfoque hereda muchos eval()
problemas de uso: riesgos de seguridad, depuración más difícil, no hay forma de aplicar optimizaciones del motor, no hay autocompletado del editor.
7. Al final, ¿qué camino es mejor?
No hay un ganador o un perdedor. La decisión de qué tipo de declaración elegir depende de la situación.
Sin embargo, hay algunas reglas que puedes seguir en situaciones comunes.
Si la función utiliza this
de la función adjunta, la función flecha es una buena solución. Cuando la función de devolución de llamada tiene una declaración corta, la función de flecha es una buena opción también, porque crea un código corto y ligero.
Para una sintaxis más corta al declarar métodos sobre literales de objetos, es preferible la declaración abreviada de métodos.
new Function
manera de declarar funciones normalmente no debe ser utilizada. Principalmente porque abre potenciales riesgos de seguridad, no permite autocompletar el código en los editores y se pierden las optimizaciones del motor.