JavaScript

JavaScript

Tutorial JavaScript: Polimorfismo

JavaScript polimorfismo: técnicas y ejemplos. Domina las técnicas de polimorfismo en JavaScript con ejemplos prácticos y detallados.

Aprende JavaScript y certifícate

Fundamentos del polimorfismo: Múltiples formas, misma interfaz en la programación orientada a objetos

El polimorfismo es uno de los pilares fundamentales de la programación orientada a objetos, permitiendo que objetos de diferentes clases respondan al mismo mensaje o método de manera distinta. La palabra proviene del griego "poly" (muchos) y "morphos" (formas), literalmente "muchas formas".

En su esencia, el polimorfismo permite tratar objetos de diferentes tipos a través de una interfaz común. Esto facilita escribir código más flexible, modular y reutilizable, ya que podemos trabajar con diferentes implementaciones sin necesidad de conocer sus detalles específicos.

Concepto básico del polimorfismo

Imagina que tienes diferentes tipos de vehículos: coches, motocicletas y bicicletas. Aunque cada uno funciona de manera distinta internamente, todos comparten la capacidad de "moverse". El polimorfismo nos permite invocar este comportamiento común sin preocuparnos por los detalles específicos de implementación de cada vehículo.

class Vehicle {
  move() {
    throw new Error("Method 'move()' must be implemented");
  }
}

class Car extends Vehicle {
  move() {
    return "Car is driving on the road";
  }
}

class Motorcycle extends Vehicle {
  move() {
    return "Motorcycle is riding on the street";
  }
}

La belleza del polimorfismo radica en poder tratar todos estos objetos de manera uniforme:

function startJourney(vehicle) {
  // No necesitamos saber qué tipo de vehículo es
  console.log(vehicle.move());
}

const myCar = new Car();
const myMotorcycle = new Motorcycle();

startJourney(myCar);        // "Car is driving on the road"
startJourney(myMotorcycle); // "Motorcycle is riding on the street"

Tipos de polimorfismo en JavaScript

En JavaScript, podemos identificar principalmente dos tipos de polimorfismo:

  • 1. Polimorfismo de sobrescritura (Override): Ocurre cuando una clase hija redefine un método heredado de su clase padre.
class Shape {
  calculateArea() {
    return 0;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }
  
  calculateArea() {
    return Math.PI * this.radius * this.radius;
  }
}
  • 2. Polimorfismo de interfaz: Se basa en que diferentes objetos implementen los mismos métodos, aunque no estén relacionados por herencia.
// Dos clases sin relación de herencia
class Duck {
  sound() {
    return "Quack!";
  }
}

class Dog {
  sound() {
    return "Woof!";
  }
}

// Función que trabaja con cualquier objeto que tenga el método 'sound'
function makeSound(animal) {
  console.log(animal.sound());
}

Beneficios del polimorfismo

El polimorfismo ofrece varias ventajas clave para el diseño de software:

  • Flexibilidad: Permite que el código funcione con diferentes tipos de objetos sin modificaciones.
  • Extensibilidad: Facilita añadir nuevos tipos sin alterar el código existente.
  • Mantenibilidad: Reduce la duplicación de código y mejora la organización.
  • Abstracción: Permite centrarse en "qué hace" en lugar de "cómo lo hace".

Polimorfismo y principio de sustitución de Liskov

El polimorfismo está estrechamente relacionado con el Principio de Sustitución de Liskov, que establece que los objetos de una clase derivada deben poder sustituir a los objetos de la clase base sin afectar la corrección del programa.

class Bird {
  fly() {
    return "Flying high";
  }
}

class Sparrow extends Bird {
  // Comportamiento compatible con la clase base
  fly() {
    return "Sparrow flying at medium altitude";
  }
}

// Uso polimórfico
function letBirdFly(bird) {
  return bird.fly();
}

const genericBird = new Bird();
const sparrow = new Sparrow();

console.log(letBirdFly(genericBird)); // "Flying high"
console.log(letBirdFly(sparrow));     // "Sparrow flying at medium altitude"

Polimorfismo vs sobrecarga de métodos

Es importante distinguir entre polimorfismo y sobrecarga de métodos:

  • El polimorfismo permite que diferentes clases implementen el mismo método de diferentes maneras.
  • La sobrecarga permite definir múltiples versiones del mismo método con diferentes parámetros.

JavaScript no soporta nativamente la sobrecarga de métodos como otros lenguajes, pero podemos simularla mediante la verificación de argumentos:

class Calculator {
  add(...args) {
    // Polimorfismo basado en argumentos
    if (args.length === 0) return 0;
    if (args.length === 1) return args[0];
    return args.reduce((sum, val) => sum + val, 0);
  }
}

const calc = new Calculator();
console.log(calc.add());        // 0
console.log(calc.add(5));       // 5
console.log(calc.add(2, 3, 4)); // 9

El polimorfismo es un concepto fundamental que permite crear sistemas más modulares y adaptables. En JavaScript, gracias a su naturaleza dinámica, el polimorfismo se implementa de manera más flexible que en lenguajes estáticamente tipados, permitiendo crear interfaces comunes entre objetos sin necesidad de estructuras formales.

Implementación práctica de polimorfismo en JavaScript: Herencia, duck typing y composición

El polimorfismo en JavaScript puede implementarse a través de diferentes técnicas que aprovechan la flexibilidad del lenguaje. Vamos a explorar las tres aproximaciones principales: herencia, duck typing y composición, cada una con sus propias ventajas y casos de uso.

Polimorfismo mediante herencia

La herencia es la forma más tradicional de implementar polimorfismo en JavaScript. Con la introducción de las clases en ES6, esta aproximación se volvió más intuitiva:

class PaymentMethod {
  processPayment(amount) {
    throw new Error("This method must be implemented");
  }
  
  getDescription() {
    return "Generic payment method";
  }
}

class CreditCard extends PaymentMethod {
  constructor(cardNumber, expiryDate) {
    super();
    this.cardNumber = cardNumber;
    this.expiryDate = expiryDate;
  }
  
  processPayment(amount) {
    return `Processing $${amount} via Credit Card ending in ${this.cardNumber.slice(-4)}`;
  }
  
  getDescription() {
    return `Credit Card ending in ${this.cardNumber.slice(-4)}`;
  }
}

class PayPal extends PaymentMethod {
  constructor(email) {
    super();
    this.email = email;
  }
  
  processPayment(amount) {
    return `Processing $${amount} via PayPal account: ${this.email}`;
  }
  
  getDescription() {
    return `PayPal (${this.email})`;
  }
}

Podemos utilizar estas clases de forma polimórfica:

function checkout(cart, paymentMethod) {
  const total = cart.calculateTotal();
  console.log(`Total: $${total}`);
  console.log(`Payment method: ${paymentMethod.getDescription()}`);
  console.log(paymentMethod.processPayment(total));
}

const myCart = { calculateTotal: () => 125.40 };
const creditCard = new CreditCard("4111111111111111", "12/25");
const paypal = new PayPal("user@example.com");

checkout(myCart, creditCard);
checkout(myCart, paypal);

Polimorfismo mediante duck typing

JavaScript, al ser un lenguaje de tipado dinámico, permite implementar polimorfismo a través del duck typing: "si camina como un pato y grazna como un pato, entonces probablemente sea un pato". Esta aproximación se centra en el comportamiento de los objetos más que en su tipo o jerarquía:

// No hay herencia, solo objetos con métodos similares
const audioPlayer = {
  play(track) {
    return `Playing audio track: ${track}`;
  },
  stop() {
    return "Audio playback stopped";
  }
};

const videoPlayer = {
  play(video) {
    return `Playing video: ${video}`;
  },
  stop() {
    return "Video playback stopped";
  }
};

// Función que trabaja con cualquier "reproductor"
function useMediaPlayer(player, mediaName) {
  console.log(player.play(mediaName));
  // Más operaciones...
  console.log(player.stop());
}

useMediaPlayer(audioPlayer, "song.mp3");
useMediaPlayer(videoPlayer, "movie.mp4");

El duck typing es extremadamente flexible y no requiere relaciones de herencia, pero depende de convenciones implícitas que deben ser documentadas adecuadamente.

Polimorfismo mediante composición

La composición ofrece una alternativa a la herencia, siguiendo el principio de "componer en lugar de heredar". Esta técnica consiste en construir objetos complejos combinando objetos más simples:

// Comportamientos como objetos independientes
const swimmer = {
  swim() {
    return "Swimming";
  }
};

const flyer = {
  fly() {
    return "Flying";
  }
};

const walker = {
  walk() {
    return "Walking";
  }
};

// Creamos objetos compuestos
function createDuck(name) {
  return {
    name,
    ...swimmer,
    ...flyer,
    ...walker,
    makeSound() {
      return "Quack!";
    }
  };
}

function createPenguin(name) {
  return {
    name,
    ...swimmer,
    ...walker,
    makeSound() {
      return "Honk!";
    }
  };
}

// Uso polimórfico basado en capacidades
function letSwim(creature) {
  if ('swim' in creature) {
    return `${creature.name} is ${creature.swim()}`;
  }
  return `${creature.name} cannot swim`;
}

const donald = createDuck("Donald");
const pingu = createPenguin("Pingu");

console.log(letSwim(donald)); // "Donald is Swimming"
console.log(letSwim(pingu));  // "Pingu is Swimming"

La composición permite crear sistemas más flexibles donde los comportamientos pueden combinarse de múltiples formas sin las limitaciones de la herencia simple.

Mixins: Extendiendo el polimorfismo por composición

Los mixins son una técnica avanzada de composición que permite añadir funcionalidades a clases existentes:

// Definimos mixins como funciones que extienden una clase
const TimestampMixin = (Base) => class extends Base {
  getCreatedAt() {
    return this.createdAt || (this.createdAt = new Date());
  }
};

const ValidationMixin = (Base) => class extends Base {
  validate() {
    return this.isValid ? "Valid" : "Invalid";
  }
};

// Clase base
class User {
  constructor(name) {
    this.name = name;
    this.isValid = true;
  }
}

// Aplicamos mixins
class EnhancedUser extends ValidationMixin(TimestampMixin(User)) {
  getDetails() {
    return `${this.name}, created: ${this.getCreatedAt()}, status: ${this.validate()}`;
  }
}

const user = new EnhancedUser("Alice");
console.log(user.getDetails());

Comparación de enfoques

Cada enfoque tiene sus ventajas y desventajas:

  • Herencia:

  • Ventajas: Estructura clara, fácil de entender, bien soportada por el lenguaje.

  • Desventajas: Jerarquías rígidas, problemas con herencia múltiple.

  • Duck typing:

  • Ventajas: Extremadamente flexible, sin restricciones de jerarquía.

  • Desventajas: Sin verificación en tiempo de compilación, puede llevar a errores sutiles.

  • Composición:

  • Ventajas: Modular, flexible, evita problemas de la herencia múltiple.

  • Desventajas: Puede resultar en más código, requiere diseño cuidadoso.

Aplicación práctica: Sistema de notificaciones

Veamos un ejemplo práctico de un sistema de notificaciones que utiliza polimorfismo:

// Interfaz común (implícita en JavaScript)
class NotificationChannel {
  send(message) {
    throw new Error("Method not implemented");
  }
}

class EmailNotification extends NotificationChannel {
  constructor(email) {
    super();
    this.email = email;
  }
  
  send(message) {
    return `Email to ${this.email}: ${message}`;
  }
}

class SMSNotification extends NotificationChannel {
  constructor(phoneNumber) {
    super();
    this.phoneNumber = phoneNumber;
  }
  
  send(message) {
    return `SMS to ${this.phoneNumber}: ${message}`;
  }
}

class PushNotification extends NotificationChannel {
  constructor(deviceId) {
    super();
    this.deviceId = deviceId;
  }
  
  send(message) {
    return `Push to device ${this.deviceId}: ${message}`;
  }
}

// Sistema de notificación que usa polimorfismo
function notifyUser(channels, message) {
  return channels.map(channel => channel.send(message));
}

// Uso
const userChannels = [
  new EmailNotification("user@example.com"),
  new SMSNotification("+1234567890"),
  new PushNotification("device-xyz-123")
];

const notifications = notifyUser(userChannels, "Your order has shipped!");
notifications.forEach(n => console.log(n));

Este ejemplo demuestra cómo el polimorfismo nos permite tratar diferentes canales de notificación de manera uniforme, facilitando la extensión del sistema con nuevos canales sin modificar la lógica principal.

Patrones y mejores prácticas: Diseño de sistemas polimórficos mantenibles y extensibles

Diseñar sistemas polimórficos que sean mantenibles y extensibles requiere más que simplemente implementar interfaces comunes. Necesitamos aplicar patrones de diseño y seguir prácticas que garanticen que nuestro código permanezca flexible a lo largo del tiempo, incluso cuando los requisitos cambien o el sistema crezca.

Principios SOLID en sistemas polimórficos

Los principios SOLID proporcionan una base sólida para diseñar sistemas polimórficos efectivos:

  • Principio de responsabilidad única (SRP): Cada clase debe tener una única razón para cambiar.
// En lugar de una clase que haga todo
class UserManager {
  constructor(database) {
    this.database = database;
  }
  
  // Separación de responsabilidades
  authenticate(credentials) {
    // Lógica de autenticación
    return this.database.verifyUser(credentials);
  }
  
  // Delegamos la notificación a otra clase
  notifyUser(user, message) {
    const notifier = new UserNotifier();
    return notifier.send(user, message);
  }
}
  • Principio abierto/cerrado (OCP): Las entidades deben estar abiertas para extensión pero cerradas para modificación.
// Sistema de filtros extensible
class ProductFilter {
  filter(products, specification) {
    return products.filter(product => specification.isSatisfied(product));
  }
}

// Especificaciones que podemos combinar y extender
class PriceSpecification {
  constructor(maxPrice) {
    this.maxPrice = maxPrice;
  }
  
  isSatisfied(product) {
    return product.price <= this.maxPrice;
  }
}

class CategorySpecification {
  constructor(category) {
    this.category = category;
  }
  
  isSatisfied(product) {
    return product.category === this.category;
  }
}
  • Principio de inversión de dependencias (DIP): Depender de abstracciones, no de implementaciones concretas.
// Dependemos de una interfaz, no de implementaciones específicas
class OrderProcessor {
  constructor(paymentGateway) {
    this.paymentGateway = paymentGateway;
  }
  
  processOrder(order) {
    // Trabajamos con cualquier gateway que implemente process()
    return this.paymentGateway.process(order.total);
  }
}

// Diferentes implementaciones de la misma interfaz
class StripeGateway {
  process(amount) {
    return `Processed $${amount} through Stripe`;
  }
}

class PayPalGateway {
  process(amount) {
    return `Processed $${amount} through PayPal`;
  }
}

Patrones de diseño para sistemas polimórficos

Ciertos patrones de diseño son particularmente útiles para crear sistemas polimórficos robustos:

  • Patrón Strategy: Permite definir una familia de algoritmos intercambiables.
// Contexto que usa diferentes estrategias
class ShippingCalculator {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  setStrategy(strategy) {
    this.strategy = strategy;
  }
  
  calculate(package) {
    return this.strategy.calculate(package);
  }
}

// Estrategias intercambiables
class StandardShipping {
  calculate(package) {
    return package.weight * 1.5;
  }
}

class ExpressShipping {
  calculate(package) {
    return package.weight * 2.5;
  }
}

// Uso
const calculator = new ShippingCalculator(new StandardShipping());
const cost = calculator.calculate({weight: 5});
  • Patrón Factory: Centraliza la creación de objetos polimórficos.
// Factory que crea diferentes tipos de loggers
class LoggerFactory {
  static createLogger(type, config) {
    switch(type) {
      case 'console':
        return new ConsoleLogger(config);
      case 'file':
        return new FileLogger(config);
      case 'remote':
        return new RemoteLogger(config);
      default:
        throw new Error(`Logger type ${type} not supported`);
    }
  }
}

// Uso
const logger = LoggerFactory.createLogger('console', {level: 'info'});
logger.log('System started');
  • Patrón Composite: Permite tratar objetos individuales y composiciones de objetos de manera uniforme.
// Componente base
class UIComponent {
  render() {
    throw new Error('Must implement render method');
  }
}

// Componente hoja
class Button extends UIComponent {
  constructor(text) {
    super();
    this.text = text;
  }
  
  render() {
    return `<button>${this.text}</button>`;
  }
}

// Componente compuesto
class Panel extends UIComponent {
  constructor() {
    super();
    this.children = [];
  }
  
  add(component) {
    this.children.push(component);
    return this;
  }
  
  render() {
    return `<div>${this.children.map(child => child.render()).join('')}</div>`;
  }
}

// Uso polimórfico
const loginPanel = new Panel()
  .add(new Button('Login'))
  .add(new Button('Cancel'));

document.body.innerHTML = loginPanel.render();

Técnicas de extensibilidad

Para mantener los sistemas extensibles a largo plazo:

  • Plugins y hooks: Proporciona puntos de extensión explícitos.
class Application {
  constructor() {
    this.plugins = [];
    this.hooks = {
      beforeInit: [],
      afterInit: [],
      beforeShutdown: []
    };
  }
  
  registerPlugin(plugin) {
    this.plugins.push(plugin);
    // El plugin puede registrar sus propios hooks
    if (plugin.registerHooks) {
      plugin.registerHooks(this);
    }
  }
  
  on(hookName, callback) {
    if (this.hooks[hookName]) {
      this.hooks[hookName].push(callback);
    }
  }
  
  triggerHook(hookName, data) {
    if (this.hooks[hookName]) {
      for (const callback of this.hooks[hookName]) {
        callback(data);
      }
    }
  }
  
  init() {
    this.triggerHook('beforeInit');
    // Inicialización
    this.triggerHook('afterInit');
  }
}
  • Middleware: Permite insertar comportamiento en una cadena de procesamiento.
class RequestProcessor {
  constructor() {
    this.middlewares = [];
  }
  
  use(middleware) {
    this.middlewares.push(middleware);
    return this;
  }
  
  process(request) {
    // Creamos una cadena de ejecución
    const chain = this.middlewares.reduceRight(
      (next, middleware) => {
        return (req) => middleware(req, next);
      },
      (req) => req // Middleware final que devuelve la solicitud
    );
    
    return chain(request);
  }
}

// Middlewares
const authenticate = (req, next) => {
  console.log('Authenticating...');
  req.authenticated = true;
  return next(req);
};

const validate = (req, next) => {
  console.log('Validating...');
  req.validated = true;
  return next(req);
};

// Uso
const processor = new RequestProcessor()
  .use(authenticate)
  .use(validate);

const result = processor.process({url: '/api/data'});

Evitando trampas comunes

Hay varios anti-patrones que debemos evitar al diseñar sistemas polimórficos:

  • Verificación de tipos excesiva: Confía en el polimorfismo en lugar de verificar tipos.
// Evita esto
function processShape(shape) {
  if (shape instanceof Circle) {
    // Lógica específica para círculos
  } else if (shape instanceof Rectangle) {
    // Lógica específica para rectángulos
  }
}

// Prefiere esto
function processShape(shape) {
  // Confía en que shape implementa calculateArea()
  const area = shape.calculateArea();
  // Procesamiento genérico
}
  • Jerarquías de herencia profundas: Limita la profundidad de tus jerarquías.
// Evita jerarquías profundas como:
// Entity -> Character -> Player -> SpecialPlayer -> ...

// Prefiere composición:
class Player {
  constructor(name) {
    this.name = name;
    this.abilities = [];
  }
  
  addAbility(ability) {
    this.abilities.push(ability);
  }
  
  useAbility(abilityName, target) {
    const ability = this.abilities.find(a => a.name === abilityName);
    if (ability) {
      return ability.execute(this, target);
    }
    return null;
  }
}
  • Acoplamiento excesivo: Minimiza las dependencias entre clases.
// Evita que las clases conozcan demasiado sobre otras
class OrderProcessor {
  constructor(paymentService, inventoryService, shippingService) {
    this.paymentService = paymentService;
    this.inventoryService = inventoryService;
    this.shippingService = shippingService;
  }
  
  // Cada servicio tiene una interfaz clara y limitada
  processOrder(order) {
    if (this.paymentService.processPayment(order.payment)) {
      this.inventoryService.updateStock(order.items);
      return this.shippingService.createShipment(order);
    }
    return false;
  }
}

Pruebas de sistemas polimórficos

Las pruebas son cruciales para mantener sistemas polimórficos robustos:

  • Pruebas de contrato: Verifica que todas las implementaciones cumplan con el contrato esperado.
// Función de prueba genérica para cualquier storage
function testStorageImplementation(storage) {
  // Prueba el contrato básico
  storage.set('key', 'value');
  const value = storage.get('key');
  assert(value === 'value', 'Should retrieve stored value');
  
  storage.delete('key');
  const deleted = storage.get('key');
  assert(deleted === undefined, 'Should return undefined for deleted keys');
}

// Prueba múltiples implementaciones
testStorageImplementation(new MemoryStorage());
testStorageImplementation(new LocalStorage());
testStorageImplementation(new RedisStorage());
  • Mocks y stubs: Facilitan probar componentes que dependen de interfaces polimórficas.
// Prueba con un mock que implementa la interfaz
function testNotificationSystem(notifier) {
  // Mock de un canal de notificación
  const mockChannel = {
    send: jest.fn().mockReturnValue(true)
  };
  
  notifier.addChannel(mockChannel);
  notifier.notify('Test message');
  
  // Verificamos que se llamó al método send
  expect(mockChannel.send).toHaveBeenCalledWith('Test message');
}

Evolución de sistemas polimórficos

Para que los sistemas polimórficos evolucionen correctamente:

  • Versionado de interfaces: Gestiona cambios en las interfaces con cuidado.
// Versión 1 de la API
class APIClientV1 {
  fetchUsers() {
    return fetch('/api/v1/users').then(r => r.json());
  }
}

// Versión 2 con parámetros adicionales
class APIClientV2 {
  fetchUsers(options = {}) {
    const params = new URLSearchParams(options);
    return fetch(`/api/v2/users?${params}`).then(r => r.json());
  }
}

// Adaptador para mantener compatibilidad
class APIClientAdapter {
  constructor(client) {
    this.client = client;
  }
  
  fetchUsers(options) {
    // Si es v1, ignoramos options
    if (this.client instanceof APIClientV1) {
      return this.client.fetchUsers();
    }
    return this.client.fetchUsers(options);
  }
}
  • Refactorización gradual: Evoluciona el sistema sin romper la funcionalidad existente.
// Enfoque de strangler pattern para migrar sistemas
class LegacyPaymentProcessor {
  process(payment) {
    // Lógica antigua
  }
}

class ModernPaymentProcessor {
  processPayment(paymentData) {
    // Nueva implementación mejorada
  }
}

// Adaptador para migración gradual
class PaymentProcessorAdapter {
  constructor(modernProcessor) {
    this.modernProcessor = modernProcessor;
  }
  
  process(payment) {
    // Traduce del formato antiguo al nuevo
    return this.modernProcessor.processPayment({
      amount: payment.amount,
      method: payment.type,
      currency: payment.currency || 'USD'
    });
  }
}

Aplicando estos patrones y prácticas, podemos crear sistemas polimórficos que no solo funcionen hoy, sino que también puedan adaptarse a los cambios futuros con un mínimo de fricción. El polimorfismo bien implementado nos permite escribir código que abraza el cambio en lugar de resistirlo.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende JavaScript online

Ejercicios de esta lección Polimorfismo

Evalúa tus conocimientos de esta lección Polimorfismo con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Herencia

JavaScript
Puzzle

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Test

Funciones

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Selección de elementos DOM

JavaScript
Puzzle

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Estructuras de control

JavaScript
Puzzle

Todas las lecciones de JavaScript

Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a JavaScript y certifícate

Certificados de superación de JavaScript

Supera todos los ejercicios de programación del curso de JavaScript y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender el concepto de polimorfismo y su importancia en la programación orientada a objetos.
  2. Conocer cómo la herencia en JavaScript facilita la implementación del polimorfismo.
  3. Aprender a utilizar funciones constructoras y objetos prototipo para crear subclases con implementaciones específicas de métodos.
  4. Entender cómo el polimorfismo permite tratar diferentes objetos como si fueran de una misma clase, ejecutando métodos compartidos sin conocer los detalles específicos de cada objeto.
  5. Conocer el polimorfismo ad hoc en JavaScript, que permite trabajar con diferentes tipos de datos en una función sin crear múltiples funciones similares.
  6. Aprender sobre la composición de objetos y cómo esta técnica proporciona polimorfismo al agregar comportamientos únicos a un objeto sin recurrir a la herencia típica.
  7. Saber cómo el polimorfismo en JavaScript brinda flexibilidad y escalabilidad en el diseño y la implementación de soluciones de software.