Ir al contenido
wannacode

Métodos de instancia

En resumen: Un método de instancia es una función de la clase que trabaja con los datos del objeto a través de this, y toString dice cómo el objeto se convierte en texto.

Problema

Cada uno retira a su manera

El banco tiene una clase Account con un titular y un saldo. En el programa se retira dinero en dos lugares. El cajero comprueba que haya dinero suficiente. El pago con tarjeta lo escribió otra persona, y no comprueba nada:

const account = new Account("María", 100);
// El cajero comprueba que haya dinero suficiente.
if (account.balance >= 70) {
account.balance -= 70;
}
// El pago con tarjeta lo escribió otra persona, y no comprueba nada.
account.balance -= 50;
console.log(account.balance);
Salida
-20

La cuenta está por debajo de cero. La regla «no puedes retirar más de lo que tienes» es importante, pero se escribe (o se olvida) por separado en cada lugar que retira.

La idea

Los métodos viven con los datos

La lógica del retiro es parte de la cuenta, así que ponla en la clase Account. Una función escrita en la clase es un método del objeto:

bank.js
class Account {
owner;
balance;
constructor(owner, balance) {
this.owner = owner;
this.balance = balance;
}
deposit(amount) {
this.balance += amount;
}
withdraw(amount) {
if (amount <= this.balance) {
this.balance -= amount;
} else {
console.log(`No hay dinero suficiente: ${this.owner}`);
}
}
}
const account = new Account("María", 100);
account.withdraw(70);
account.withdraw(50);
account.deposit(20);
console.log(account.balance);
Salida
No hay dinero suficiente: María
50
  • Escribes el método en la clase sin la palabra function: withdraw(amount) { … }.
  • Lo llamas sobre un objeto con un punto: account.withdraw(70), igual que a los campos.
  • Dentro, this.balance es el saldo de esta cuenta, el objeto que está antes del punto. El this. es obligatorio: sin él, balance no es el campo.
  • La comprobación se escribe una vez. Todo el que retira a través del método pasa por ella.

const maria = new Account("María", 100);
const peter = new Account("Pablo", 40);
maria.withdraw(70);
peter.withdraw(70);
console.log(maria.balance, peter.balance);
Paso a paso, en palabras
  1. Dos cuentas, dos objetos. Cada uno tiene su propio saldo.
  2. maria.withdraw(70): this recibe una flecha al objeto que está antes del punto.
  3. En el método, this.balance es el saldo de María: 100 - 70 = 30.
  4. El mismo método, otro objeto: ahora this apunta a Pablo.
  5. 70 es más que 40: el método imprime un rechazo, y el saldo de Pablo no cambia.
  6. El código del método existe una sola vez. Los campos son del objeto sobre el que lo llamas.

El pago con tarjeta ya no puede «olvidar» la comprobación si retira con withdraw. Pero todavía puede escribir directamente en el campo. Cómo impedirlo lo verás en la lección «Encapsulamiento».

La idea

Método o función

Las funciones del módulo «Funciones» viven fuera de la clase. Lo reciben todo como parámetros y no tienen un objeto propio. Un método trabaja con los campos del objeto sobre el que se llamó:

function applyDiscount(price, percent) {
return price * (100 - percent) / 100;
}
const price = applyDiscount(10, 10);
account.withdraw(70);

No puedes llamar a un método de instancia a través del nombre de la clase. No está claro de qué cuenta retirar:

Account.withdraw(70);
Error
TypeError: Account.withdraw is not a function

El método withdraw pertenece a los objetos, no a la propia clase Account. También existen métodos de la clase: llevan static delante. Los verás en la lección «static: lo que comparten todos los objetos».

Pruébalo

¿De quién es la cuenta?

¿Qué imprimirá el programa?

const maria = new Account("María", 100);
const peter = new Account("Pablo", 40);
maria.deposit(30);
peter.withdraw(10);
console.log(maria.balance, peter.balance);
Respuesta

Pruébalo

Encuentra el error

account.withdraw(30) detiene el programa con un error. ¿Qué línea tiene el error?

Elige la línea con el error.

La idea

Un método también puede responder

Un método de instancia puede devolver un resultado, igual que las funciones del módulo «Funciones». Un producto conoce su precio y su cantidad, así que puede decirte lo que cuesta:

class Product {
// los campos y el constructor, como antes
total() {
return this.price * this.quantity;
}
}
const cart = [
new Product("Pan", 1.20, 2),
new Product("Leche", 2.10, 1),
new Product("Café", 7.50, 1),
];
let sum = 0;
for (const p of cart) {
sum += p.total();
}
console.log(`Total: ${sum} €`);
Salida
Total: 12 €

Compáralo con la primera lección del módulo: allí la suma se calculaba en el código de debajo de la clase a partir de los campos. Ahora ese código solo le pregunta a cada producto. Si mañana se añade un descuento por cantidad, cambias solo total().

La idea

¿Cómo se ve el objeto como texto?

Si imprimes un producto directamente, obtienes algo inútil:

const coffee = new Product("Café", 7.50, 1);
console.log(`Compra: ${coffee}`);
console.log(coffee);
Salida
Compra: [object Object]
Product { name: 'Café', price: 7.5, quantity: 1 }

Como texto, el objeto es [object Object]. console.log del objeto en sí muestra sus campos, pero eso sirve para revisar mientras escribes código. No es un texto que puedas poner en una frase.

La clase puede decir cómo debe mostrarse. El método se llama toString(), y devuelve un texto:

class Product {
// los campos y el constructor, como antes
total() {
return this.price * this.quantity;
}
toString() {
return `${this.name} x${this.quantity}: ${this.total()} €`;
}
}
const coffee = new Product("Café", 7.50, 1);
const bread = new Product("Pan", 1.20, 2);
console.log(String(coffee));
console.log(`En el carrito: ${bread}`);
Salida
Café x1: 7.5 €
En el carrito: Pan x2: 2.4 €

No lo llamas tú. String(coffee), el template literal `En el carrito: ${bread}` y "En el carrito: " + bread lo llaman por ti cuando el objeto tiene que convertirse en texto. Solo console.log(coffee) no lo llama: sigue mostrando los campos.

El nombre tiene que ser exactamente toString, con t minúscula y S mayúscula. De dónde saca el objeto un toString antes incluso de que escribas uno viene en el módulo «Herencia y polimorfismo».

Pruébalo

Un libro en una frase

¿Qué imprimirá el programa?

class Book {
title;
author;
constructor(title, author) {
this.title = title;
this.author = author;
}
toString() {
return `${this.title} de ${this.author}`;
}
}
const book = new Book("Rayuela", "Julio Cortázar");
console.log("Leyendo: " + book);
Respuesta

Ejercicio

Objetos que hacen algo

Resuelve las tareas en tu editor. Toma las clases de las lecciones anteriores y añádeles métodos.

  1. Person: un método introduce() que imprime el nombre y la edad.
  2. SimpleCar: un método accelerate() que aumenta la velocidad en 10. Ahora el código de debajo de la clase no toca la velocidad directamente.
  3. DanceMove: un método perform() que «ejecuta» el paso.
  4. Book: un método read() que imprime «Leyendo [título] de [autor]».

Crea un objeto de cada clase y llama a sus métodos:

Salida
Hola, soy María y tengo 34 años
Velocidad tras acelerar: 10
Ejecuto moonwalk durante 5 segundos
Leyendo Cien años de soledad de Gabriel García Márquez

Ejercicio

Un coche con combustible

Una clase Car con una marca, una velocidad, combustible (en litros) y un kilometraje. El constructor recibe la marca y el combustible. La velocidad y el kilometraje empiezan en 0.

  • accelerate() aumenta la velocidad en 10.
  • refuel(liters) añade combustible, pero en el depósito caben como mucho 50 litros.
  • drive(distance) gasta 6 litros cada 100 km y suma la distancia al kilometraje. Si no hay combustible suficiente, imprime un mensaje y el coche no se mueve.
  • toString() devuelve la marca, el kilometraje y el combustible.
El programa
const car = new Car("Toyota", 20);
car.accelerate();
car.accelerate();
console.log(`Velocidad: ${car.speed}`);
car.drive(100);
console.log(String(car));
car.drive(300);
car.refuel(40);
console.log(String(car));
car.drive(300);
console.log(String(car));
Salida
Velocidad: 20
Toyota: 100 km, 14 l
No hay combustible suficiente para 300 km
Toyota: 100 km, 50 l
Toyota: 400 km, 32 l
Pista: distance * 6 / 100. En JavaScript, / siempre da un número decimal, así que para 30 km obtienes 1,8 litros.

Más tareas

  • Descuento. Añade a Product un método applyDiscount(percent) que baja el precio del producto. Compáralo con la función del módulo «Funciones» que devolvía un precio nuevo.
  • Una cuenta con historial. Añade a Account un contador de operaciones: cada depósito y cada retiro que sale bien lo aumenta en 1. Un método toString() devuelve el titular, el saldo y el número de operaciones.

Test

Ponte a prueba

1. ¿Qué hace maria.deposit(30)?
2. ¿Qué puede ver un método que una función fuera de la clase no puede?
3. ¿Por qué Account.withdraw(70) se detiene con un TypeError?
4. ¿Cuándo llama JavaScript a toString() por sí solo?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·