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);-20La 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:
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);No hay dinero suficiente: María50- 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.balancees el saldo de esta cuenta, el objeto que está antes del punto. Elthis.es obligatorio: sin él,balanceno 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
- Dos cuentas, dos objetos. Cada uno tiene su propio saldo.
- maria.withdraw(70): this recibe una flecha al objeto que está antes del punto.
- En el método, this.balance es el saldo de María: 100 - 70 = 30.
- El mismo método, otro objeto: ahora this apunta a Pablo.
- 70 es más que 40: el método imprime un rechazo, y el saldo de Pablo no cambia.
- 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);TypeError: Account.withdraw is not a functionEl 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);Cada objeto tiene su propio saldo. maria.deposit(30) cambia solo el saldo de María, y peter.withdraw(10) cambia solo el de Pablo. Pablo tiene 40, así que retirar 10 sale bien.
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.
Sin this., el nombre balance es una variable normal, y no existe ninguna variable así:
ReferenceError: balance is not definedEl campo es this.balance. La línea siguiente lo escribe bien, así que el error está solo en la comprobación.
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} €`);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);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}`);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);"Leyendo: " + book tiene que convertir book en texto. Así que se llama al toString() del libro, y devuelve el título y el autor.Ejercicio
Objetos que hacen algo
Resuelve las tareas en tu editor. Toma las clases de las lecciones anteriores y añádeles métodos.
Person: un métodointroduce()que imprime el nombre y la edad.SimpleCar: un métodoaccelerate()que aumenta la velocidad en 10. Ahora el código de debajo de la clase no toca la velocidad directamente.DanceMove: un métodoperform()que «ejecuta» el paso.Book: un métodoread()que imprime «Leyendo [título] de [autor]».
Crea un objeto de cada clase y llama a sus métodos:
Hola, soy María y tengo 34 añosVelocidad tras acelerar: 10Ejecuto moonwalk durante 5 segundosLeyendo Cien años de soledad de Gabriel García MárquezEjercicio
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.
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));Velocidad: 20Toyota: 100 km, 14 lNo hay combustible suficiente para 300 kmToyota: 100 km, 50 lToyota: 400 km, 32 ldistance * 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
Productun métodoapplyDiscount(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
Accountun contador de operaciones: cada depósito y cada retiro que sale bien lo aumenta en 1. Un métodotoString()devuelve el titular, el saldo y el número de operaciones.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Funciones · Parámetros y return · Clases y objetos · Constructores y this
- Continúa en: Encapsulamiento · static: lo que comparten todos los objetos · Herencia · Polimorfismo
- Ejercicios sobre el tema: Clases y objetos