Funciones y Scope
Aprende a crear y utilizar funciones en JavaScript, comprendiendo el ámbito de las variables y los conceptos avanzados como closures y hoisting.
Cristian Escalante
Última actualización: 20 de abril de 2025
Introducción a las funciones
Las funciones son bloques de código reutilizables diseñados para realizar una tarea específica. Son fundamentales en JavaScript, ya que permiten estructurar el código, evitar repeticiones y crear abstracciones.
Definición de funciones
Declaración de función
La forma más común de definir una función es mediante una declaración:
function saludar() {
console.log("¡Hola, mundo!");
}
// Llamada a la función
saludar(); // ¡Hola, mundo!
Expresión de función
Una función también puede definirse como una expresión:
const saludar = function() {
console.log("¡Hola, mundo!");
};
// Llamada a la función
saludar(); // ¡Hola, mundo!
La diferencia principal entre declaraciones y expresiones de función es el hoisting (elevación), que veremos más adelante.
Funciones flecha (Arrow Functions)
Introducidas en ES6, ofrecen una sintaxis más concisa:
const saludar = () => {
console.log("¡Hola, mundo!");
};
// Versión aún más concisa para funciones de una sola línea
const sumar = (a, b) => a + b;
console.log(sumar(5, 3)); // 8
Funciones anónimas
Son funciones sin nombre, a menudo usadas como argumentos para otras funciones:
// Función anónima como callback
setTimeout(function() {
console.log("Este mensaje aparece después de 2 segundos");
}, 2000);
// Lo mismo con función flecha
setTimeout(() => {
console.log("Este mensaje aparece después de 2 segundos");
}, 2000);
Parámetros y argumentos
Los parámetros son las variables listadas en la definición de la función. Los argumentos son los valores que se pasan a la función cuando se la llama.
// a y b son parámetros
function sumar(a, b) {
return a + b;
}
// 5 y 3 son argumentos
const resultado = sumar(5, 3);
console.log(resultado); // 8
Parámetros predeterminados
ES6 introdujo la capacidad de establecer valores predeterminados para los parámetros:
function saludar(nombre = "Invitado") {
console.log(`¡Hola, ${nombre}!`);
}
saludar("María"); // ¡Hola, María!
saludar(); // ¡Hola, Invitado!
Parámetros rest
Permiten representar un número indefinido de argumentos como un array:
function sumar(...numeros) {
return numeros.reduce((total, num) => total + num, 0);
}
console.log(sumar(1, 2, 3, 4, 5)); // 15
console.log(sumar(10, 20)); // 30
Desestructuración de parámetros
Permite extraer propiedades de objetos o elementos de arrays pasados como argumentos:
function mostrarPersona({ nombre, edad }) {
console.log(`${nombre} tiene ${edad} años`);
}
const persona = {
nombre: "Carlos",
edad: 30,
profesion: "Desarrollador"
};
mostrarPersona(persona); // Carlos tiene 30 años
Return y valores de retorno
Las funciones pueden devolver valores usando la declaración return
:
function multiplicar(a, b) {
return a * b;
}
const resultado = multiplicar(4, 5);
console.log(resultado); // 20
Características importantes de return
:
- Una función devuelve
undefined
si no tiene una declaraciónreturn
o si esta no especifica un valor - La ejecución de la función se detiene cuando se encuentra una declaración
return
- Una función puede devolver cualquier tipo de dato, incluyendo objetos, arrays y otras funciones
function crearSaludoPersonalizado(nombre) {
return function() {
console.log(`¡Hola, ${nombre}!`);
};
}
const saludarAMaria = crearSaludoPersonalizado("María");
saludarAMaria(); // ¡Hola, María!
Diferencias entre tipos de funciones
Funciones declarativas vs. expresiones de función
// Función declarativa (se puede llamar antes de su definición)
console.log(sumar(2, 3)); // 5
function sumar(a, b) {
return a + b;
}
// Expresión de función (no se puede llamar antes de su definición)
console.log(restar(5, 2)); // Error: restar is not a function
const restar = function(a, b) {
return a - b;
};
Funciones tradicionales vs. funciones flecha
- Sintaxis más concisa:
// Función tradicional const sumar1 = function(a, b) { return a + b; }; // Función flecha const sumar2 = (a, b) => a + b;
- No tienen su propio
this
:// Función tradicional const persona1 = { nombre: "Juan", saludar: function() { setTimeout(function() { console.log(`Hola, soy ${this.nombre}`); // undefined }, 100); } }; // Función flecha const persona2 = { nombre: "Juan", saludar: function() { setTimeout(() => { console.log(`Hola, soy ${this.nombre}`); // Juan }, 100); } };
- No pueden usarse como constructores:
// Funciona const Persona = function(nombre) { this.nombre = nombre; }; const juan = new Persona("Juan"); // Error: Arrow functions cannot be used as constructors const Persona = (nombre) => { this.nombre = nombre; }; const juan = new Persona("Juan");
- No tienen objeto
arguments
:function tradicional() { console.log(arguments); } const flecha = () => { console.log(arguments); // Error o referencia al objeto arguments del ámbito externo };
Scope (ámbito)
El scope determina la accesibilidad de variables, objetos y funciones desde diferentes partes del código.
Scope global
Las variables declaradas fuera de cualquier función o bloque tienen ámbito global:
// Variable global
const mensaje = "Hola, mundo";
function mostrarMensaje() {
console.log(mensaje); // Accede a la variable global
}
mostrarMensaje(); // Hola, mundo
Scope local o de función
Las variables declaradas dentro de una función solo son accesibles dentro de esa función:
function mostrarMensaje() {
// Variable local
const mensaje = "Hola desde la función";
console.log(mensaje);
}
mostrarMensaje(); // Hola desde la función
console.log(mensaje); // Error: mensaje is not defined
Scope de bloque (con let y const)
Las variables declaradas con let
y const
tienen ámbito de bloque:
if (true) {
// Variable de bloque
let mensaje = "Hola desde el bloque";
const saludo = "Bienvenido";
var antiguo = "Variable con var"; // No respeta el ámbito de bloque
console.log(mensaje); // Hola desde el bloque
}
console.log(mensaje); // Error: mensaje is not defined
console.log(saludo); // Error: saludo is not defined
console.log(antiguo); // Variable con var (¡accesible fuera del bloque!)
Anidamiento de scopes
Los ámbitos internos pueden acceder a variables de ámbitos externos, pero no al revés:
const global = "Variable global";
function externa() {
const externa = "Variable externa";
function interna() {
const interna = "Variable interna";
console.log(global); // Variable global
console.log(externa); // Variable externa
console.log(interna); // Variable interna
}
interna();
console.log(global); // Variable global
console.log(externa); // Variable externa
console.log(interna); // Error: interna is not defined
}
externa();
Closures (Cierres)
Un closure es una función que recuerda el entorno en el que fue creada, incluso después de que la función externa haya terminado de ejecutarse.
function crearContador() {
let contador = 0;
return function() {
contador++;
return contador;
};
}
const incrementar = crearContador();
console.log(incrementar()); // 1
console.log(incrementar()); // 2
console.log(incrementar()); // 3
En este ejemplo, la función interna forma un closure que "recuerda" la variable contador
, incluso después de que crearContador()
haya terminado su ejecución.
Usos comunes de closures
- Datos privados:
function crearCuenta(saldoInicial) { let saldo = saldoInicial; return { consultar: function() { return saldo; }, depositar: function(cantidad) { saldo += cantidad; return saldo; }, retirar: function(cantidad) { if (cantidad <= saldo) { saldo -= cantidad; return saldo; } else { return "Fondos insuficientes"; } } }; } const cuenta = crearCuenta(100); console.log(cuenta.consultar()); // 100 console.log(cuenta.depositar(50)); // 150 console.log(cuenta.retirar(70)); // 80 console.log(cuenta.saldo); // undefined (no se puede acceder directamente)
- Funciones de fábrica:
function crearMultiplicador(factor) { return function(numero) { return numero * factor; }; } const duplicar = crearMultiplicador(2); const triplicar = crearMultiplicador(3); console.log(duplicar(5)); // 10 console.log(triplicar(5)); // 15
Hoisting (elevación)
Hoisting es el comportamiento de JavaScript de mover las declaraciones al principio del ámbito actual antes de la ejecución del código.
Hoisting de variables
console.log(x); // undefined (no Error)
var x = 5;
console.log(x); // 5
// Es equivalente a:
var x;
console.log(x); // undefined
x = 5;
console.log(x); // 5
Con let
y const
, las variables también se elevan pero entran en una "zona muerta temporal" (TDZ) hasta su declaración:
console.log(y); // Error: Cannot access 'y' before initialization
let y = 10;
Hoisting de funciones
Las declaraciones de funciones se elevan completamente:
saludar(); // "Hola, mundo"
function saludar() {
console.log("Hola, mundo");
}
Pero las expresiones de función no:
despedir(); // Error: despedir is not a function
var despedir = function() {
console.log("Adiós, mundo");
};
Mejores prácticas
- Preferir
let
yconst
sobrevar
:// Evitar var contador = 0; // Preferir const PI = 3.14159; let contador = 0;
- Funciones pequeñas y con un solo propósito:
// Mejor tener varias funciones pequeñas que una grande function validarFormulario() { return validarNombre() && validarEmail() && validarContraseña(); }
- Usar parámetros predeterminados:
// En lugar de function saludar(nombre) { nombre = nombre || "Invitado"; console.log(`Hola, ${nombre}`); } // Usar function saludar(nombre = "Invitado") { console.log(`Hola, ${nombre}`); }
- Evitar variables globales:
// Evitar const contador = 0; // global // Preferir (function() { const contador = 0; // local al IIFE })();
- Usar funciones flecha para callbacks cortos:
// En lugar de [1, 2, 3].map(function(num) { return num * 2; }); // Usar [1, 2, 3].map(num => num * 2);