JavaScript implements OOP (class based inheritance) via prototype inheritance: every object inherits properties and methods from its prototype object. The JavaScript language specification ECMAScript 2015 (ES6), introduced classes: special functions which simplify class syntax.
Class Definition
Just as you define function expressions and function declarations, the class syntax can be: class expressions and class declarations. The expression is evaluated at runtime, when the code executes, whereas a declaration is executed beforehand.
Class declaration
The syntax in classes must be written in “strict mode”.
class Flight {
constructor(number, departure, arrival, price) {
this.number = number;
this.departure = departure;
this.arrival = arrival;
this.price = price;
}
}
let depart = new Date("2020-03-01T14:30:00");
let arrive = new Date("2020-03-08T20:00:00");
let flight = new Flight("vy233", depart, arrive, 180);
console.log(Flight.name, typeof Flight, typeof flight);
// Flight function object
console.log(flight);
/* {
number: "vy233",
departure: Sun Mar 01 2020 14:30:00 GMT+0100 (Central European Standard Time),
arrival: Sun Mar 08 2020 20:00:00 GMT+0100 (Central European Standard Time),
price: 180
}
*/
Class expression
Class expressions can be named or unnamed.// unnamed
let Flight = class {
constructor(number, departure, arrival, price) {
this.number = number;
this.departure = departure;
this.arrival = arrival;
this.price = price;
}
}
let flight = new Flight("vy233", depart, arrive, 80);
console.log(Flight.name);
// Flight
// named
let Flight = class ComercialFlight{
constructor(number, departure, arrival, price) {
this.number = number;
this.departure = departure;
this.arrival = arrival;
this.price = price;
}
}
console.log(Flight.name);
Hoisting
Class declarations cannot be hoisted as function declarations. Using a class before declaring it will throw aReferenceError
.
Constructor
The constructor method is a special method for creating and initializing an object created with a class. Inside the constructorthis
value equals to the newly created instance. The arguments used to instantiate the class become the parameters of the constructor.
If you don’t define a constructor for the class, a default one is created.
JavaScript class can have only one constructor.
A constructor can use the super
keyword to call the constructor of the super class.
Class Fields (properties)
Class fields are variables that hold information and can be attached on the class itself (static) or on the class instance. They can be public (accessible anywhere) or private (accessible only within the body of the class).
Public instance fields
They can be read and updated inside the constructor, methods and outside of the class.class Airport {
name = 'el prat';
location = 'spain';
constructor(name) {
this.name = name;
}
}
const airport = new Airport('girona');
console.log(airport.name, airport.location); //girona spain
airport.location = 'france';
console.log(airport.location); // france
Private instance fields
The private fields are accessible only within the body of the class. Encapsulation is the concept of hiding internal data of an object through private fields.class Airport {
#code;
constructor(code) {
this.#code = code;
}
getCode() {
return this.#code;
}
}
const airport = new Airport('BCN');
airport.getCode(); // => BCN
airport.#code; // SyntaxError
Public static fields
These are helpful to define class constants – information specific to the class, so they cannot be accessed by instance class.class Flight {
static CABIN_BAGS = 'cabin';
static CHECKED_BAGS = 'checked';
static EXCESS_BAGS = 'excess';
price;
type;
name;
luggages;
constructor(name, type, price, luggages) {
this.name = name;
this.type = type;
this.price = price;
this.luggages = luggages
}
}
const flight = new Flight('vy35007', 'domestic', 150, Flight.CABIN_BAGS);
flight.luggages === Flight.CABIN_BAGS; // => true
Private static fields
Private static fields are accessible only within the class (encapsulation).class Order {
static #MAX_ORDERS = 2;
static #num = 0;
customer;
constructor(customer, price) {
Order.#num++;
if (Order.#num > Order.#MAX_ORDERS) {
throw new Error('Limit reached!');
}
this.customer = customer;
this.price = price;
}
}
new Order('Violeta', 210);
new Order('Garcia', 55);
new Order('Maria', 170);
Class Methods
Instance methods can access and modify instance data. A method can be private too (prefix its name with #).class Passenger {
#cardNumber;
name;
constructor(name, cardNumber) {
this.name = name;
this.#cardNumber = cardNumber;
}
getName() {
return this.name;
}
#getCardNumber() {
return this.#cardNumber;
}
hasVisa() {
return /^\d{13, 16}$/g.test(this.#getCardNumber());
}
}
const passenger = new Passenger('jose', 45223201);
passenger.getName(); // jose
passenger.hasVisa(); // false
passenger.#getCardNumber(); // SyntaxError
Getters and Setters
The name of the getter/setter method cannot be the same as the name of the property. You can prefix with underscore the property name.class Customer {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
set name(cname) {
this._name = cname;
}
}
const user = new Customer("Jorge");
user.name; // calls the getter => jorge
user.name = 'adria'; // calls the setter => {_name: "adria"}
Static methods
The static methods are functions attached directly to the class so they can access only the static fields.class ScheduledFlight {
static passengers = [];
capacity = 55;
static hasPassenger(name) {
return ScheduledFlight.passengers.includes(name);
}
constructor(name) {
ScheduledFlight.passengers.push(name)
}
}
const flight = new ScheduledFlight('John');
ScheduledFlight.hasPassenger('John'); // true
Computed property names
Class allows to compose the property names using brackets:class Flight {
['max'+'capacity'] = 50;
['cancel' + 'Flight']() {
return 'ok'
}
get ['max'+'capacity']() {
return this.maxcapacity
}
}
const fl = new Flight();
fl.maxcapacity; // 50
fl.cancelFlight(); // ok
Class Iterators and generators
An object can be iterable if it has a method named with the Symbol.iterator
which return an interface called iterator. This iterator must have a method next
that returns the next result: an object with a value
property and a done
property, which should be true when there are no more results and false otherwise.
Regular functions return only one value or nothing. Generators can return (yield
) multiple values, one after another, on-demand. They work great with iterables, allowing to create data streams easily.
class FlightReservation {
id;
seats = [];
constructor(seatNum) {
this.seats.push(seatNum);
}
*getSeats() {
for (let seat in this.seats) {
yield this.seats[seat];
}
}
[Symbol.iterator]() {
return this.getSeats();
}
}
const reservation = new FlightReservation([31, 4, 6, 22]);
// 3 ways to to output the seats array: [31, 4, 6, 22]
// using iterable
for (let seat of reservation)
console.log(seat);
// using iterator
let iterator = reservation.getSeats();
while (seat = iterator.next().value)
console.log(seat);
// or spread operator
console.log([...reservation].values().next().value);
Single inheritance (Subclassing)
The classes in JavaScript support single inheritance using theextends
keyword. The super
keyword is used to call corresponding methods of super class.
Note that inside the child constructor you must call super()
before other initializations.
class Person {
name;
constructor(name) {
this.name = name;
}
getDetails() {
return this.name
}
}
class Passenger extends Person {
constructor(name, priorityBoarding) {
super(name);
this.priorityBoarding = priorityBoarding;
}
}
class Crew extends Person {
flights = [];
constructor(name, flights) {
super(name);
this.flights = flights;
}
getDetails() {
return super.getDetails() + ` has ${this.flights.length} flights`
}
}
const passenger = new Passenger('John', true);
const pilot = new Crew('Martin', ['ab3xd3', 'vy533', 'es990']);
console.log(passenger.getDetails()); // John
console.log(pilot.getDetails()); // Martin has 3 flights
Person.prototype.constructor === Person; // true
Object.getOwnPropertyNames(Person.prototype);
// returns an array of all properties (non-enumerable also)
// except the ones which use Symbol
// ["constructor", "getDetails"]
pilot instanceof Person; // true
pilot.constructor === Crew; // true
passenger instanceof Person; // true
passenger.constructor === Person; //false
Multiple inheritance (Mixins)
Object.assign(target, ...sources)
method, which copies enumerable and own properties from a source object to a target object. It uses [[Get]]
on the source and [[Set]]
on the target, therefore it assigns properties versus just copying or defining new ones.
// create objects to use them as a mixin template
let GroundVehicle = {
hasFuel() {
return true
}
};
let FlyingVehicle = {
getType() {
return 'commercial'
}
};
class Vehicle {
constructor(name) {
this.name = name;
}
}
Object.assign(Vehicle.prototype, GroundVehicle, FlyingVehicle);
const vehicle = new Vehicle('boeing');
vehicle.getType();
vehicle.hasFuel();
What if we change GroundVehicle and FlyingVehicle to classes ? Just create the mixin function which will copy all properties (enumerable and non-enumerable) from source classes to the target one.
class GroundVehicle {
wheels = 4;
hasFuel() {
return true
}
};
class FlyingVehicle {
getType() {
return 'commercial'
}
};
class Vehicle {
constructor(name) {
this.name = name;
}
}
function mixin(target, ...src) {
for (let srcClass of src) {
for (let prop of Object.getOwnPropertyNames(srcClass.prototype)) {
target.prototype[prop] = srcClass.prototype[prop]
}
}
}
mixin(Vehicle, GroundVehicle, FlyingVehicle);
new Vehicle('boeing').getType(); // "commercial"
Opinions