DEV Community

Cover image for Classes - JavaScript Core Concepts
Angeline Wang
Angeline Wang

Posted on

Classes - JavaScript Core Concepts

The Section 2: Functions & Methods of my JavaScript Core Concepts Series is divided into 6 Parts:

Part 1 Functions
Part 2 Classes
Part 3 Methods
Part 4 Callback Methods
Part 5 Scope
Part 6 Hoisting

Classes

Part 1 What are Classes?

= Template for creating Objects
→ Record data using logic that works through the data

How are JavaScript classes created?

= They are created on Prototypes
→ And include some syntax & semantics that are not shared with ES5 class-like semantics

What is the purpose of Classes?

= Class declaration introduced in ES2015
→ Just syntactic sugar: On top of existing prototype-based inheritance
→ Does not add anything new to JavaScript

= Classes can add Properties, Methods & Functions
→ Methods created can be ran later: After the Constructor/Object is created
2 Types of Methods:
1. Instance Methods
= Can be called by Instances

2. Static Methods
= Can be called by the Class

How do you define a Class?

= 2 Ways to define a Class:

1st Way: Class Expressions
#1 Can be Named or Unnamed

= Name given: Is local to Class’ body
→ But can be accessed through the name property of the Class

#2 Need to be declared

= Before they can be used
→ Due to hoisting restrictions

Body of a Class
= W/in curly braces
→ Where Class members
ie Methods & Constructor are defined

Execution in Strict Mode
= Stricter syntax for optimal performance
→ Normally silent errors thrown
→ Particular keywords reserved for ECMAScript future versions

2nd Way: Class Declarations

= Using Class keyword
→ Followed by the name of the Class

Code Example
class Event {
    constructor(date, time) {
        this.date = date;
        this.time = time;
    }
}
Enter fullscreen mode Exit fullscreen mode
Function Declarations vs Class Declarations

Function Declarations
= Can be called in code before they are defined

Class Declarations
= Need to be defined before they can be constructed
→ Because the Class is hoisted: But its values are not initialized

Class Re-definition

= Re-defining a Class is not possible
→ An Attempt to redefine a Class will result in: SyntaxError
Alerting you that you are trying to declare a variable that has already been declared

...

Part 2 Constructor Methods

Purpose of Constructors & Prototypes

  1. Act as JavaScript’s main method of defining similar & related Objects

  2. Bring similar functionality facilitated through Classes in other languages
    = Does not add extra functionality
    → Thus Class Declaration made available through ES2015: Just syntactic sugar for existing prototype-based inheritance
    Rapidly accumulate many different Objects
    = By calling a Constructor different times w/ different arguments
    → Reflective of pattern used in built-in constructors

What are Constructor Methods?

= Here are a few definitions:

  1. Mold for creating multiple Objects w/ same Properties & Methods

  2. Special Method for creating & initializing an Object created w/ a Class
    = Can only be 1 special method w/ name constructor in a Class

  3. Any Function that can be used as a Constructor
    = No specific distinction within the language
    → Function can be written to be used as a Constructor, to be called as a Normal Function, or it can be used either way
    ie JS functions: Are also Object Constructors

Example

function Event(date, time) {
    this.date = date;
    this.time = time;
}
Enter fullscreen mode Exit fullscreen mode

Types of Constructors

Type #1: Built-in constructors

= Accessible directly in execution environment at runtime

#1 Object()
#2 Array()
#3 String()
#4 Number()
#5 Boolean()
#6 Date()
#7 Function()
#8 Error()
#9 RegExp()
Creation of Values

= There are 2 ways to create values:

  1. Object Literals

  2. Constructors

Object literals vs Constructors

1 Barely any difference

= Can call Methods on Literals
→ Which means JavaScript will turn the Literal into a temporary Object
→ Helping the Method perform the operation
→ Temporary Object is then trashed when it is no longer useful

2 Why are Object Literals better?

a. Easier to read

b. Faster to run
= Can be optimised at parse time
Simple Objects

Example:
Instead of using this...

var obj = new Object(5);
obj.toFixed(2);
Enter fullscreen mode Exit fullscreen mode

You should use this...

var num = 5;
num.toFixed(2);
Enter fullscreen mode Exit fullscreen mode
Type #2: Custom Constructors

= Define Properties & Methods for your own type of Object

Custom Constructor Creation

= Just like creating a Function
→ Capitalize their name: As convention & for identifiability

= A Constructor can also use the super keyword
→ To call the Constructor of a superclass

Scope-safe Constructors

What are scope-safe constructors?

= Made to return same result
→ No matter if it’s called with or without new
→ Protects against mistake of omitting new

What is the purpose of scope-safe constructors?

= New programmers can accidentally call a Constructor
→ W/o new keyword
Resulting in bugs

Which constructors are scope-safe?

= Majority of Built-in Constructors
→ ie Object, Regex and Array

= They use a unique pattern to call the Constructor
→ So that in the case new is not used

= Returns a proper Instance of the Object
→ Through calling Constructor again w/ new

Make a Custom Constructor scope-safe
Code Example
function Event(date, time) {
    if (!(this instanceof Event)) {
        return new Event(date, time);
    }
    this.date = date;
    this.time = time;
}

var event1 = new Event(“September 24th, 2022”, “12:20PM”);
var event2 = Event(“September 24th, 2022”, “12:20PM”);
Enter fullscreen mode Exit fullscreen mode

= Now both these variable definitions (with and without new), will return an instance of Event

Object.defineProperty() Method

= Can be used w/in a Constructor
→ To execute the Property setup needed

Here’s a Use Case…

User Input Manipulation

= Before assignment to an Object
→ Manipulate Value given through date Parameter into a String
Consisting of Date: followed by the Value
→ & Assign it to the date property of Object created

Purpose

= Need to manipulate user-given Values
→ Into a suitable/consistent format
Before saving it to an Object

Code Example
function Event(date) {
    Object.defineProperty(this, ‘date’, {
        get: function() {
            return ‘Event: ‘ + date;
        },
        set: function(newDate) {
            date = newDate;
        },
        configurable: false
});
}
Enter fullscreen mode Exit fullscreen mode

Accessor Properties

= Do not contain any Properties or Methods
→ Create a Getter to call when Property is read
Expected to return a Value
→ Create a Setter to call when Property is written to
Receives Value assigned at Getter as an Argument

= Entire Constructors: Containing Object.defineProperty() Method
→ Returns an Instance whose date property can be set or changed in Object.defineProperty() Method
→ But cannot be deleted

Above Example
  1. After receiving the value of date

  2. Getter adds Date: to beginning of the Value

  3. Returns that

  4. Object.defineProperty creates these accessor properties

Purpose of Calling Constructor

= So that JavaScript can perform these 4 Tasks:

  1. Create a new Object

  2. Set constructor Property of the Object that is the Variable the Constructor is assigned to
    = This Property is different from Standard Properties
    → Because it will not be listed
    → And it cannot be overwritten

  3. Underlying constructor property
    = Something that cannot be set manually
    → Can only be set up by JavaScript when new keyword is used

  4. Object is created to delete Event.prototype in the above example

  5. JavaScript calls Event()
    = In the Context of the new Object
    → Result of new Event: Is the new Object

How to Call a Constructor

  1. Create an Instance of it
    = Using the new keyword
    → And assigning it to a Variable

  2. Constructor assigns given Arguments to Parameters indicated in Initial Definition

  3. Then Constructor assigns given Arguments to the relevant Properties

Code Example
function Event() {

}

var myEvent = new Event();
Enter fullscreen mode Exit fullscreen mode

When to use a Constructor

= Creating various similar Objects
→ With same Properties & Methods

Example Use Case

= Creating an Event

If Function exists
= All need to do is use a new statement

If the new Keyword is not used
= Alteration of Global Object will occur
→ Instead of the newly created one

W/o new
= Calling a Function w/o a Return Statement
this in the Constructor points to Window
→ Instead of flat8

Thus, 2 Global Variables are created
W/ new
= Context is switched from Global (Window)
→ To Instance correctly pointing to flat8

Code Example
var flat8  = new Event(‘September 18th, 2022’)
Enter fullscreen mode Exit fullscreen mode
new not used & in Strict Mode

= Code would throw an Error
→ B/c Strict Mode designed to protect from accidental calls of a Constructor w/o the new keyword

Check if Object is Instance

= There are 2 ways to see if an Object is an Instance of another:

1st Way: instanceof Operator

= Right side of the operator must be a function
→ Even if the prototype.constructor is reassigned, and the constructor is lost
instanceof operator will still have the accurate information

Code Example

Check a new Event has been created:

flat-8 instanceof Event
Enter fullscreen mode Exit fullscreen mode

= This will return true

2nd Way: constructor Property

= constructor property of any object
→ Will point to the constructor function that created it
→ This property is inherited from its prototype

Code Example
myEvent.constructor === Event;
Enter fullscreen mode Exit fullscreen mode

= Returns true if myEvent is an instance of Event

Warning

= Using the constructor property to check the type of an instance
→ It can be overwritten: So it is bad practice

Constructor vs Function Creation

= When writing a Constructor
→ You should use it like a Constructor

= When writing a Function
→ You should use it like a Function

Call as Function w/o new

Code Example

= Call Event as a Function w/o using new

Event(‘September 18th, 2022’)
Enter fullscreen mode Exit fullscreen mode

= Returns undefined
→ Creates a date global variable
Overriding existing date variable
→ Not what we want

Call Function as Function w/o new

= this is set to the Global Object
→ Which is the Window object
→ Which is the Browser

Polluting Namespace

= You should prevent pollution of the namespace
→ This is done by not creating Global Variables

= Confirm if this is an Event
→ And if it is not, use new to create an Event
→ And return it

Code Example
function Event(date) {
    if(!(this instanceof Person))
        return new Event(date)
    this.date = date
}   
Enter fullscreen mode Exit fullscreen mode

Benefit of Constructor Usage

= To create Methods (instead of creating one big object)

→ Inheritance

Inheritance in JavaScript

= Inheritance in JS is different to inheritance in traditional Object-oriented languages
→ Bc it uses Prototype Inheritance

...

Part 3 Prototype

What is the prototype?

Here are a few definitions:

  1. A Property
    = That all Functions automatically get

  2. An Empty Object
    = Receives some special treatment

Object created through Constructor
  1. Creates its own prototype Object

  2. Own prototype Object inherits all Properties of its Constructor’s prototype
    = Constructor’s prototype Object is the Parent of its own prototype Object

  3. Any Properties set on own prototype Object
    = Can override inherited Properties from Constructor’s prototype

New Property

= Setting a New Property to prototype Object on the Constructor
→ Will be passed down to any Objects created by Constructor

Code Example
Event.prototype.totalLength = 4;
flat8.totalLength; //This will output 4 
Enter fullscreen mode Exit fullscreen mode
Properties created on prototype

= Passed down to Instances
→ But can always be overridden on the Instance itself

Code Example
flat8.totalLength = 10;
flat8.totalLength; //Now this will output 10
Enter fullscreen mode Exit fullscreen mode

Prototype Inheritance

= In place of Classes, JS uses the prototype
→ Allows Inheritance to happen

If Object has many of Properties
= And a Special Parent Property called the prototype of the Object
→ JS Objects inherit all Properties of its Parent
→ These Properties can be overridden by setting the Property on itself

Define Methods on Objects

Most Memory Efficient
  1. Declare methods on the prototype

  2. Ensuring only 1 displayEvent is ever created

  3. And it is shared between all the Events created
    = Done by making Event.prototype their parent

Code Example
function Event(date){
    this.date = date
}
Event.prototype.displayEvent = function(){
    return ‘This event is on ‘ + this.date
}
Enter fullscreen mode Exit fullscreen mode

Define Instance Methods

= Defining Methods that can be used from all Instances of a Constructor
→ Done on Constructor’s Prototype Property

Code Example
Event.prototype = {
    display: function display() {
        return “This is the event!”;
    }
}
Enter fullscreen mode Exit fullscreen mode

apply Method

= Belongs to Function.prototype
→ Can be used to call a Function while binding it to an Object
→ Works even if the Function is not connected to the Object, and if the Object is a different type

Can you bind a Function to an Object yourself?
= Yes
→ Can also include Parameters by passing them as an Array as the 2nd parameter of the apply() Method

Code Example
function displayEvent(other){
    return ‘This event is on ‘ + this.date + ‘and on’ + other.date
}

displayEvent.apply(flat8, [flat9]) 
Enter fullscreen mode Exit fullscreen mode
apply Use Cases
1. Calling a Function w/ a List of Arguments

= That is a Dynamic List
ie Math.max(4, 1, 8, 9, 2)
→ Use Math.max on an arbitrary array (instead of a statically set one)
Use Math.max.apply(Math, myarray)

2. Calling it as a Constructor

= Rather than calling it as a function
→ Create a new Method of Function.prototype

Code Example

Function.prototype.new = function() {
    var args = arguments
    var constructor = this
    function Fake(){
        constructor.apply(this, args)
}
Fake.prototype = constructor.prototype
return new Fake
}
Enter fullscreen mode Exit fullscreen mode

= new method inside Function.prototype
→ Can call constructors with the new method
→ Will have feel of calling a constructor
Without actually using a constructor

Example Usage

var nextWeek = [new Event(‘September 24th, 2022’), new Event(‘September 29th, 2022’)]
var args = [‘September 20th, 2022’].concat(nextWeek)
var allEvents = Event.new.apply(Event, args)
Enter fullscreen mode Exit fullscreen mode

= Simplify even further, create a Helper Method

Example Helper Method

Function.prototype.applyNew = function(){
    return this.new.apply(this, arguments)
}

var allEvents = Event.applyNew(args)
Enter fullscreen mode Exit fullscreen mode

call Function

= Another Method of Function.prototype
→ Works like apply()
→ Passes parameters differently
Instead of as an array in the 2nd parameter
They are added to the end

Code Example
displayEvent.call(flat8, flat9)
Enter fullscreen mode Exit fullscreen mode

Subclass Creation: 1st Way

= Set prototype of subclass to an Instance of superclass
→ ‘subclass’ only called once
→ Cannot customize ‘subclass’ on different calls of ‘superclass’

Code Example
var Event = function Event() {};
Event.prototype = new Venue(“Shoreditch”);
Event.prototype.displayVenue = function displayVenue() { return “Venue Submitted” };
var flat8 = new Event();
flat8 instanceof Event; // Outputs true
flat8 instanceof Venue; // Outputs true
Enter fullscreen mode Exit fullscreen mode

Inheritance w/o Power to Override

= In most prototype-based languages
→ There is inheritance without power to override
→ However, JavaScript does not work like this

Here’s a way to do it:

Code Example
function create(parent) {
    var F = function() {};
    F.prototype = parent;
    return new F();
}
Enter fullscreen mode Exit fullscreen mode

Static Methods & Properties

= static keyword
→ Defines a static method or property for a class

= Called w/o creating an instance of the class
→ Cannot be called through a class instance

Static methods

= Used to create utility functions for an application

Static Properties

= Used for caches, fixed-configuration
→ Or any other data you do not need to be replicated across instances
Unlike prototype methods and properties which will appear on individual instances

Code Example
class Event {
    constructor(date, time) {
        this.date = date;
        this.time = time;
}

    static displayName = “September Events”;
    static timeframe(a, b) {
        const tdate = a.date - b.date;
        const ttime = a.time - b.time;

return Math.hypot(tdate, ttime); 
    }
}

const flat8 = new Event(123, 2);
const flat9 = new Event(432, 5);

flat8.displayName; //Outputs undefined
flat9.displayName; //Outputs undefined

flat8.timeframe; //Outputs undefined 
flat9.timeframe; //Outputs undefined

console.log(Event.displayName); //Outputs “September Events”;
console.log(Event.timeframe); //Outputs …
Enter fullscreen mode Exit fullscreen mode
Bind this w/ Prototype & Static Methods
  1. Value for this
    = Must be included when calling a static or prototype method
    → Or else the this value will be undefined inside the method

  2. Body is always executed in strict mode
    = So this will occur regardless of whether or not the “use strict” directive is present

Calling Static & Property Methods

= Static & Property Methods cannot be called on their own
→ Must be called as a Property of another Object

Instance Properties

= Must be defined inside Class Methods

Code Example
class Event {
    constructor(date, time) {
        this.date = date;
        this.time = time;
    }
}
Enter fullscreen mode Exit fullscreen mode

Field Declarations

= 2 Different Types of Field Declarations:

1. Public

= Do not need to be made w/ Keyword like let, const or var
→ Allow Class Definitions to become more self-documenting
→ Fields will always be there
= Can be declared w/ or w/o a Default Value

Code Example
class Event {
    date = “”;
    time = “”;
    constructor (date, time) {
        this.date = date;
        this.time = time;
}
}
Enter fullscreen mode Exit fullscreen mode
2. Private

= Cannot be referenced from outside of the Class
→ Can only be read or written w/in Class body

Benefit of Private Field Declarations
  1. Use of Field Declarations not visible outside of Class
    = Ensures Classes’ users cannot depend on

  2. Intervals (which may change version to version)

  3. Can only be declared upfront in a Field Declaration
    = Cannot be created later through assigning them the way normal properties can

Code Example
class Event {
    #date = “”;
    #time;
    constructor(date, time) {
        this.#date = date;
        this.
    }
}
Enter fullscreen mode Exit fullscreen mode

Subclass Creation: 2nd Way

= extends is used in Class Declarations or Class Expressions
→ For sake of creating a Class as a child of another Class

Constructor in Subclass
= Constructor must first call super() before using this

Traditional Function-based “Classes”
= Can also be extended

Subclass & Superclass both have same method
= Subclass’ method overrides Superclass’

Code Example
class Event {
    constructor(date) {
        this.date = date;
    }

    display() {
        console.log(`${this.date} is the date of the event.`);  
    }
}

class smallEvent extends Event {
    constructor(date) {
        super(date);
    }

    speak() {
        console.log(`${this.date} is the date of a small event.`);
    }
}

const houseWarming = new smallEvent(‘September 22nd, 2022’);
houseWarming.display(); // Output: September 22nd, 2022 is the date of a small event.
Enter fullscreen mode Exit fullscreen mode
Inappropriate Use Case

= Classes cannot extend regular (non-constructible) Objects
→ They must extend another Class

Regular Object Inheritance

= For a Class to inherit Methods & Properties from a Regular Object
→ Instead of another Class
Use Object.setPrototypeOf

Code Example
const Event = {
    display() {
        console.log(`${this.date} is the date of the event.`);
    }
};

class smallEvent = {
    constructor(date) {
        this.date = date;
}
}

Object.setPrototypeOf(smallEvent.prototype, Event);

const houseWarming = new smallEvent(‘September 22nd, 2022’);
houseWarming.display(); //Output: September 22nd, 2022 is the date of the event.
Enter fullscreen mode Exit fullscreen mode

Species Pattern

  1. Allows creation of Objects which are Instances of Superclass that a Subclass extends
    = Instead of creating Objects that are Instances of the Subclass

  2. Constructor of Objects created by the Subclass
    = Overridden & Set to the Superclass

  3. Can create Instances of Superclass that contain more Methods & Properties
    = On top of ones defined in original Class Creation

Code Example
class MyArray extends Array {
static get [Symbol.species] () {
    return Array; // This overwrites the Object Constructor and sets it to Array instead of MyArray
}
}
const a = new MyArray(1, 2, 3);
//This allows a to access the default Constructors set at Array

const mapped = a.map((x) => x * x);

//Because a.map() uses a method defined in Array rather than MyArray, the variable called mapped will be an instance of Array rather than MyArray

console.log(mapped instanceof MyArray); //Output: false
console.log(mapped instanceof Array); //Output: true 
Enter fullscreen mode Exit fullscreen mode

super vs Prototype-based Inheritance

Advantage of super

= Ability to call Superclass Methods w/in Subclass

Make Superclass calls w/ super

= Call Methods of Superclass w/in a Method being defined in a Subclass
→ Using super keyword

Code Example
class Event {
    constructor(date) {
        this.date = date;
    }

    display() {
        console.log(`${date} is the date of the event.`);
}
}

class smallEvent extends Event {
    display() {
        super.display();
        console.log(`${this.date} is a good time for a small event.`);
    }
    //Since a method with the same name in the subclass overrides the one defined in the superclass, object created with the subclass will have a method called display() which has additional utility to the objects created with the superclass
}

const houseWarming = new smallEvent(‘September 22nd, 2022’);
houseWarming.display();
//September 22, 2022 is the date of the event.
//September 22, 22 is a good time for a small event.
Enter fullscreen mode Exit fullscreen mode

What are Mixins?

Here are a few definitions:

  1. A Class that includes Methods
    = That can be used by other Classes
    → W/o need to be Parent Class of those Classes using its Methods

  2. Abstract Subclasses
    = Which are Templates for Classes

Rule for Mixins
= Each Subclass can only have 1 Superclass
→ Multiple inheritance from Tooling Classes is not possible
→ Functionality that needs to be inherited must be provided by 1 Superclass

Using Mix-ins
  1. Create a Function w/ a Superclass as Input
    = & a Subclass extending that Superclass as Output

  2. One Class must accept another Class as an Argument
    = This Class must also be accepted as an Argument of another Class

Examples of Mix-ins
const calculatorMixin = (Base) => class extends Base {
    calc() {}
};

const randomizerMixin = (Base) => class extends Base {
    randomize() {}
};
Enter fullscreen mode Exit fullscreen mode
Example Class using Mix-in
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}
// Base is in the end set as Base { randomize() {} }
Enter fullscreen mode Exit fullscreen mode

Resources for Further Exploration:

Javascript Constructors and Prototypes By Toby Ho

Understanding JavaScript Constructors By Faraz Kelhini

MDN Web Docs: Classes

MDN Web Docs: Object.prototype.constructor

VMWare: JavaScript constructors, prototypes, and the new keyword

Top comments (0)