Class is useful for objects creation of the same kind. There is not 'real' classes in JS Classes in JS are based internally on constructor functions & prototypes Syntax
class MyClass {
prop = value
constructor() {} // NO commas
method1() {}
method2() {}
get something() {}
set something(param) {}
[Symbol.iterator]() {}
}
const obj = new MyClass() // create a new object with all the listed methods
constructor() method is called when object is created with new keyword, so we can initialize the object there constructor() allows us to pass arguments into the class and assign it to an object MyClass is technically a constructor function methods, getters and setters are written to MyClass.prototype of a constructor function Create
class User {
constructor(name) { this.name = name }
sayHi() { alert(this.name) }
car = "bmw"
dog = prompt("your dog breed?", "husky");
}
let user = new User("John") // object is created
user.sayHi() // "John"
user.car // "bmw"
user.dog // "husky"
typeof User // function
User.prototype // {constructor: ƒ, sayHi: ƒ}
alert(User) // class User { ... } // string representation starts with the “class…”
// for...in // does not work by default, coz enumerable flag is false for all methods in the "prototype"
// Classes always "use strict"
Class expression
let User = class { sayHi() { alert("Hello") } }
let User = class MyClass { sayHi() { alert("Hello") } } // Like a function, class expressions may have a name, it’s visible inside the class only
let xxx = new User()
xxx.sayHi() // Hello
// return class in function on-demand
function makeClass(phrase) {
return class { sayHi() { alert(phrase) } }
}
let User = makeClass("Hello") // Create a new class
new User().sayHi() // Hello
Dynamic method name
class User {
['say' + 'Hi']() { alert("Hello") }
}
new User().sayHi() // Hello
this in class
class Button {
constructor(value) { this.value = value }
click() { alert(this.value) }
}
let button = new Button("hello")
setTimeout(button.click, 1000) // undefined
// but works with arrow function, which takes "this" from above
class Button {
constructor(value) { this.value = value }
click = () => alert(this.value)
}
let button = new Button("hello")
setTimeout(button.click, 1000) // hello
Methods chaining To enable methods chaining just return object's instance by returning this
class Greetings {
hi() { console.log('hi'); return this; }
bye() { console.log('bye'); return this; }
}
const greeting = new Greetings()
greeting.hi().bye() // hi // bye
Extends
class Animal {
constructor(name) {
this.speed = 0
this.name = name
}
setSpeed(speed) { this.speed = speed }
}
let dog = new Animal("Spok")
dog.name // 'Spok'
dog.setSpeed(30)
dog.speed // 30
dog.doFly() // !!! dog.doFly is not a function
class Bird extends Animal {
doFly() { return true }
}
let owl = new Bird('Lintu')
owl.name // 'Lintu'
owl.setSpeed(100)
owl.speed // 100
owl.doFly() // true
Override method
class Animal {
constructor(name) {
this.speed = 0
this.name = name
}
setSpeed(speed) { this.speed = speed }
}
let dog = new Animal("Spok")
dog.name // 'Spok'
dog.setSpeed(30)
dog.speed // 30
class Bird extends Animal {
setSpeed(speed) { this.speed = 2 * speed }
}
let owl = new Bird('Lintu')
owl.name // 'Lintu'
owl.setSpeed(100)
owl.speed // 200
Override constructor constructors in inheriting classes must call super before using this super calls parent method arrow functions do not have super
class Animal {
constructor(name) {
this.speed = 0
this.name = name
}
}
let rabbit = new Animal ("White Rabbit", 10)
// {speed: 0, name: "White Rabbit"}
class Rabbit extends Animal {
constructor(name, earLength) {
super(name)
this.earLength = earLength
}
}
rabbit = new Rabbit("White Rabbit", 10)
// {speed: 0, name: "White Rabbit", earLength: 10}
Static methods & properties can be accessed directly from the class without object instantiation (no idea why it is useful) they belong to the class they are not part of an instantiated object static properties and methods are inherited Static methods
class Man {
name = "John"
static hi() {
return 'John says hi'
}
}
// same as
class Man { name = "John" }
Man.hi = function() { return 'John says hi' }
const john = new Man
john.name // 'John'
john.hi() // ! Uncaught TypeError: john.hi is not a function
Man.hi() // 'John says hi'
Static property
class Article {
static publisher = "John"
}
// or
Article.publisher = "John"
const news1 = new Article
news1.publisher // undefined
Article.publisher // 'John'
Read - only property property can be set & never modified to do that need to make getter, but not the setter
class CoffeeMachine {
constructor(power) { this._power = power }
get power() { return this._power }
}
let coffeeMachine = new CoffeeMachine(100)
alert(`Power is: ${coffeeMachine.power} W`) // Power is: 100 W
coffeeMachine.power = 25; // Error (no setter)
Private properties & methods only accessible from inside the class not supported widely yet should start with # inherits have no direct access
class CoffeeMachine {
#waterAmount = 666
waterAmount() {
return this.#waterAmount
}
}
let machine = new CoffeeMachine()
machine.waterAmount() // 666
machine.#waterAmount // Error Private field '#waterAmount' must be declared in an enclosing class
// can not be inherited
class MegaCoffeeMachine extends CoffeeMachine {
method() {
alert( this.#waterAmount ); // Error Private field '#waterAmount' must be declared in an enclosing class
}
}
Getters & setters Getters & setters can modify property values when we write or read them from the object.
class Human {
get name() {
return this._name
}
set name(str) {
const letters = [...str]
const [firstLetter, ...otherLetters] = letters
this._name = [firstLetter.toUpperCase(), ...otherLetters].join('')
}
}
const john = new Human
john.name = 'john'
john.name // 'John'
class CoffeeMachine {
#waterAmount = 0
get waterAmount() {
return this.#waterAmount
}
set waterAmount(value) {
if (value < 0) value = 0
this.#waterAmount = value
}
}
let machine = new CoffeeMachine()
machine.waterAmount = -666
machine.waterAmount // 0
machine.waterAmount = 100
machine.waterAmount // 100
Getters & setters via functions can accept multiple arguments more flexible
class CoffeeMachine {
#waterAmount = 0
setWaterAmount(value) {
if (value < 0) value = 0
this.#waterAmount = value
}
getWaterAmount() {
return this.#waterAmount
}
}
const machine = new CoffeeMachine()
machine.setWaterAmount(100)
machine.getWaterAmount() // 100
Extend built -in classes Built-in classes like Array, Map, Object and others are extendable Can add additional methods to it Static methods are not inherited
class MyArray extends Array {
isEmpty() { return this.length === 0 }
}
let arr = new MyArray(1, 2, 5, 10, 50)
arr // MyArray(5) [1, 2, 5, 10, 50]
arr.constructor === MyArray // true
let filteredArr = arr.filter(item => item >= 10)
filteredArr // MyArray(2) [10, 50]
filteredArr.constructor === MyArray // true
filteredArr.isEmpty() // false
filteredArr.length = 0
filteredArr // MyArray []
filteredArr.isEmpty() // true
[Symbol.species] If we’d like built-in methods like map or filter to return regular arrays return Array in [Symbol.species]
class MyArray extends Array {
isEmpty() { return this.length === 0 }
static get [Symbol.species]() {
return Array
}
}
let arr = new MyArray(1, 2, 5, 10, 50)
arr // MyArray(5) [1, 2, 5, 10, 50]
arr.isEmpty() // false
let filteredArr = arr.filter(item => item >= 10);
filteredArr // [10, 50]
filteredArr.isEmpty() // TypeError: filteredArr.isEmpty is not a function
instanceof Checks if an object belongs to a certain class Returns true if object belongs to the Class or inherits from it Examines the prototype chain for the check Same as objA.isPrototypeOf(objB)
class Rabbit {}
let rabbit = new Rabbit()
rabbit instanceof Rabbit // true
function Dog() {}
new Dog() instanceof Dog // true
let arr = [1, 2, 3];
arr instanceof Array // true
arr instanceof Object // true
Mixins object can inherit from a single object class may extend only one other class Sometimes it may be limiting Mixin is a class with methods that can be used by other classes without a need to inherit from it Mixin is a class that contains methods for other classes
let sayHiMixin = {
sayHi() { alert(`Hello ${this.name}`) },
sayBye() { alert(`Bye ${this.name}`) }
};
class User {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(User.prototype, sayHiMixin);
// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
Good but difficult example on real mixin usage. Class vs constructor function Example is taken from the OOP lesson , which shows how classes are internally done in JavaScript by prototyping. Class version
let usersArr = []
class User {
constructor(email, name) {
this.email = email
this.name = name
this.online = false
usersArr.push(this)
}
login() {
this.online = true
console.log(this.email, 'has logged in')
}
logout() {
this.online = false
console.log(this.email, 'has logged out')
}
}
class Admin extends User {
constructor(email, name) {
super(email, name)
this.role = ''
}
deleteUser(u) {
usersArr = usersArr.filter(user => user.email !== u.email)
console.log(usersArr)
}
}
const userOne = new User('john@mail.com', 'John')
const userTwo = new User('bob@mail.com', 'Bob')
const admin = new Admin('mike@mail.com', 'Mike')
usersArr // [User, User, Admin]
admin.deleteUser(userOne)
usersArr // [User, Admin]
Constructor function version
let usersArr = []
function User(email, name) {
this.email = email
this.name = name
this.online = false
usersArr.push(this)
}
User.prototype.login = function() {
this.online = true
console.log(this.email, 'has logged in')
}
User.prototype.logout = function() {
this.online = false
console.log(this.email, 'has logged out')
}
function Admin(...args) {
User.apply(this, args)
this.role = ''
}
Admin.prototype = Object.create(User.prototype)
Admin.prototype.deleteUser = function(u) {
usersArr = usersArr.filter(user => user.email !== u.email)
}
const userOne = new User('john@mail.com', 'John')
const userTwo = new User('bob@mail.com', 'Bob')
const admin = new Admin('mike@mail.com', 'Mike')
usersArr // [User, User, Admin]
admin.deleteUser(userOne)
usersArr // [User, Admin]