Conceptos Fundamentales: Funciones y Objetos en JavaScript

  1. Funciones en JavaScript

1.1. Métodos de Declaración de Funciones

JavaScript ofrece varias maneras para definir funciones, adaptándose a diferentes escenarios:

  • Declaración de Función (function keyword): La forma clásica de definir una función.
  • Expresiones de Función (Funciones Anónimas): Las funciones se asignan a una variable, pudiendo ser nombradas o anónimas.
  • Constructor Function: Menos común y generalmente desaconsejado por motivos de rendimiento y seguridad, permite crear funciones a partir de cadenas de texto.

1.2. Gestión de Parámetros

La flexibilidad de las funciones en JavaScript se extiende a cómo manejan sus argumentos:

  • Discordancia entre Parámetros Formales y Argumentos Reales: JavaScript no impone una cantidad estricta de argumentos, permitiendo llamar a funciones con más o menos argumentos de los declarados.
  • Parámetros con Valores por Defecto (ES6+): Es posible asignar un valor predeterminado a un parámetro si no se proporciona un argumento para este.
  • Parámetros Rest (...rest - ES6+): Para manejar un número variable de argumentos, los parámetros rest permiten recopilar argumentos adicionales en un array.

Además, el objeto arguments (disponible en funciones tradicionales) ofrece acceso a todos los argumentos pasados a una función, independientemente de su declaración.

1.3. Funciones de Retrollamada (Callbacks)

Una función de retrollamada es aquella que se pasa como argumento a otra función, para ser ejecutada en un momento posterior. Esto es fundamental para la programación asíncrona y la implementación de patrones de diseño.

function aplicarOperacion(val1, val2, operacion) {
    // La función 'operacion' se invoca para cada valor
    return operacion(val1) + operacion(val2);
}

// Ejemplo de uso con Math.abs para obtener el valor absoluto
let resultado = aplicarOperacion(-10, 7, Math.abs);
console.log(resultado); // Salida: 17

1.4. Funciones Recursivas

Una función es recursiva cuando se invoca a sí misma durante su ejecución. Esto es útil para resolver problemas que se pueden descomponer en subproblemas más pequeños de la misma naturaleza.

// Cálculo de un factorial usando recursión
function calcularFactorial(numero) {
    if (numero === 0 || numero === 1) {
        return 1;
    }
    return numero * calcularFactorial(numero - 1);
}

console.log(calcularFactorial(5)); // Salida: 120

1.5. Expresiones de Función Ejecutadas Inmediatamente (IIFE)

Las IIFE son funciones que se definen y se ejecutan inmediatamente después de su declaración. Son útiles para crear un nuevo ámbito de visibilidad y evitar la contaminación del ámbito global.

(function() {
    let mensaje = '¡Inicio rápido!';
    console.log(mensaje);
})(); // La función se invoca al instante

1.6. Clausuras (Closures)

Una clausura se forma cuando una función "recuerda" y puede acceder a su ámbito léxico (las variables del entorno donde fue declarada) incluso cuando se ejecuta fuera de ese ámbito. Esto permite mantener variables privadas y crear funciones con estado.

function crearMultiplicadores() {
    let almacenMultiplicadores = [];
    for (let j = 1; j <= 4; j++) {
        // Se utiliza una IIFE para capturar el valor actual de 'j' en cada iteración
        almacenMultiplicadores.push((function(numCapturado) {
            return function() {
                return numCapturado * numCapturado;
            };
        })(j));
    }
    return almacenMultiplicadores;
}

let generadores = crearMultiplicadores();
let m1 = generadores[0];
let m2 = generadores[1];
let m3 = generadores[2];
let m4 = generadores[3];

console.log(m1()); // Salida: 1 (1*1)
console.log(m2()); // Salida: 4 (2*2)
console.log(m3()); // Salida: 9 (3*3)
console.log(m4()); // Salida: 16 (4*4)

  1. Ámbitos (Scopes) en JavaScript

2.1. Ámbito Local

Las variables declaradas dentro de una función tienen un ámbito local, lo que significa que solo son accesibles desde el interior de esa función. Esto ayuda a evitar conflictos de nombres y a organizar mejor el código.

2.2. Ámbito Global

Las variables declaradas fuera de cualquier función tienen un ámbito global. Son accesibles desde cualquier parte del código, incluyendo dentro de funciones.

El levantamiento (hoisting) es un comportamiento de JavaScript donde las declaraciones de variables y funciones son procesadas antes de la ejecución del código. Esto puede llevar a resultados inesperados si no se comprende.

var valorGlobal = 50; // Variable global

function mostrarValor() {
    console.log(valorGlobal); // Aquí se vería 'undefined' si no se comenta la línea de abajo
    var valorGlobal = 150; // Esta declaración es "levantada" al inicio de la función
    console.log(valorGlobal); // Salida: 150
}
mostrarValor(); // Llama a la función
console.log(valorGlobal); // Salida: 50 (el ámbito global no ha cambiado)

2.3. Cadena de Ámbitos (Scope Chain)

Cuando las funciones están anidadas, se forma una cadena de ámbitos. Una función interna puede acceder a las variables de sus funciones externas, pero no al revés. La búsqueda de una variable comienza en el ámbito más interno y asciende por la cadena hasta encontrarla o llegar al ámbito global.

function funcionExterna() {
    let variableExterna = 'Soy externa';

    function funcionInterna() {
        let variableInterna = 'Soy interna';
        console.log(variableExterna); // Acceso a variableExterna
        function funcionMasInterna() {
            console.log(variableInterna); // Acceso a variableInterna
        }
        funcionMasInterna();
    }
    funcionInterna();
    // console.log(variableInterna); // Error: variableInterna no está definida aquí
}
funcionExterna();

2.4. Ámbito de Bloque (ES6+)

Con la introducción de let y const en ES6, JavaScript ganó el concepto de ámbito de bloque. Las variables declaradas con let o const dentro de un bloque (cualquier par de llaves {}, como en bucles o condicionales) solo son accesibles dentro de ese bloque.

for (let k = 0; k < 5; k++) {
    // 'k' es accesible solo dentro de este bloque
    console.log(`Valor de k: ${k}`);
}
// console.log(k); // Generará un ReferenceError: k no está definido

  1. Objetos en JavaScript

3.1. Tipo Object

Creación de Objetos

Los objetos son colecciones de propiedades, donde cada propiedad tiene un nombre (clave) y un valor.

  • Literales de Objeto: La forma más común y concisa de crear objetos.
let perfilUsuario = {
    nombre: 'Ana',
    edad: 28,
    obtenerDatos: function() {
        console.log(`Nombre: ${this.nombre}, Edad: ${this.edad}`);
    }
};
perfilUsuario.obtenerDatos(); // Salida: Nombre: Ana, Edad: 28
    
  1. Constructor new Object(): Otra forma de crear objetos, aunque menos utilizada que los literales.
let itemTienda = new Object();
itemTienda.id = 'ITEM001';
itemTienda.descripcion = 'Teclado mecánico';
itemTienda.precio = 75.99;

console.log(itemTienda.descripcion); // Salida: Teclado mecánico
   

Operaciones con Propiedades

  • Acceder y Modificar Propiedades: Se puede hacer mediante la notación de punto o la notación de corchetes.
console.log(perfilUsuario.nombre); // Acceso con notación de punto
perfilUsuario.edad = 29; // Modificación

console.log(itemTienda['precio']); // Acceso con notación de corchetes
itemTienda['descripcion'] = 'Ratón inalámbrico'; // Modificación
    
  1. Eliminar Propiedades: El operador delete se usa para eliminar una propiedad de un objeto.
delete perfilUsuario.obtenerDatos;
console.log('obtenerDatos' in perfilUsuario); // Salida: false

delete itemTienda['id'];
console.log(itemTienda.id); // Salida: undefined
   
  1. Verificar Existencia de Propiedades: El operador in comprueba si una propiedad existe en un objeto o en su cadena de prototipos.
console.log('nombre' in perfilUsuario); // Salida: true
console.log('color' in perfilUsuario);  // Salida: false
   

3.2. Clases (Funciones Constructoras)

Antes de ES6, las "clases" en JavaScript se implementaban con funciones constructoras, que son funciones destinadas a ser llamadas con el operador new para crear instancias de objetos.

function Producto(nombreArticulo, costo) {
    this.nombre = nombreArticulo;
    this.precio = costo;

    this.mostrarInfo = function() {
        console.log(`Artículo: ${this.nombre}, Costo: $${this.precio}`);
    };
}

let productoNuevo = new Producto('Monitor 27"', 299.99);
productoNuevo.mostrarInfo(); // Salida: Artículo: Monitor 27", Costo: $299.99

  • Todo objeto creado por un constructor tiene una propiedad .constructor que apunta a su función constructora.
  • El operador instanceof permite verificar si un objeto es una instancia de un constructor particular.

3.3. La Palabra Clave this

this es una palabra clave especial que se refiere al contexto de ejecución actual. Su valor depende de cómo se llama la función:

  • En un método de objeto, this se refiere al objeto al que pertenece el método.
  • En una función "regular" (no un método ni una arrow function en el ámbito global), this suele referirse al objeto global (window en navegadores, global en Node.js) en modo no estricto, o undefined en modo estricto.
  • Las "arrow functions" (funciones flecha) no tienen su propio this; lo heredan del ámbito léxico que las rodea.

3.4. Prototipos y Cadena de Prototipos

¿Qué son los Prototipos?

En JavaScript, cada objeto tiene un objeto prototipo del que hereda propiedades y métodos. Cuando intentas acceder a una propiedad en un objeto, si no la encuentra directamente en él, JavaScript busca esa propiedad en el prototipo del objeto, y luego en el prototipo de ese prototipo, y así sucesivamente.

Los objetos creados usando literales de objeto ({}) tienen Object.prototype como su prototipo directo.

Cadena de Prototipos

La herencia en JavaScript se logra a través de la cadena de prototipos. Si un objeto tiene un prototipo, y ese prototipo tiene su propio prototipo, se forma una cadena. La búsqueda de propiedades recorre esta cadena hasta llegar a null o encontrar la propiedad.

Acceder al Prototipo

  • Desde una función constructora: NombreConstructor.prototype
  • Desde una instancia de objeto (no estándar, pero ampliamente soportado): objetoInstancia.__proto__
  • Forma estándar moderna: Object.getPrototypeOf(objetoInstancia)

Añadir Propiedades y Métodos al Prototipo

Las propiedades y métodos añadidos al prototipo de un constructor estarán disponibles para todas las instancias de ese constructor, lo que es eficiente para compartir funcionalidad.

function Animal(nombre) {
    this.nombre = nombre;
}

// Añadir un método al prototipo de Animal
Animal.prototype.saludar = function() {
    console.log(`Hola, soy ${this.nombre}.`);
};

let miPerro = new Animal('Buddy');
miPerro.saludar(); // Salida: Hola, soy Buddy.

let miGato = new Animal('Whiskers');
miGato.saludar(); // Salida: Hola, soy Whiskers.

Detectar Propiedades Propias vs. Heredadas

El método hasOwnProperty(), disponible en todos los objetos, permite verificar si una propiedad es una propiedad directa del objeto (propia) o si ha sido heredada de su cadena de prototipos.

console.log(miPerro.hasOwnProperty('nombre'));   // Salida: true
console.log(miPerro.hasOwnProperty('saludar'));  // Salida: false (es heredada del prototipo)
console.log(Animal.prototype.hasOwnProperty('saludar')); // Salida: true

Creación de Objetos con Prototipo Específico

El método Object.create() permite crear un nuevo objeto con un objeto prototipo especificado.

let prototipoBase = {
    tipo: 'electrónica',
    obtenerTipo: function() {
        return this.tipo;
    }
};

let tablet = Object.create(prototipoBase);
tablet.marca = 'TechBrand';
tablet.modelo = 'X100';

console.log(tablet.obtenerTipo()); // Salida: electrónica (heredado)
console.log(tablet.marca);       // Salida: TechBrand (propiedad propia)

Etiquetas: JavaScript funciones objetos Scope closures

Publicado el 7-7 16:10