JS: Class

By Xah Lee. Date: . Last updated: .

New in ES2015.

This page lets you understand class keyword in depth.

First, here's a example of class.

// define a class
class AA {

    constructor(x) {
        this.kk = x;
        console.log("constructor called with arg " + x)
    }

    ff (x) {console.log("ff called with arg " + x)}

    static sm (x) {console.log("sm called with arg " + x)}

}

// --------------------------------------------------
// using the class

const obj = new AA(4); // prints: constructor called with arg 4

console.log(obj); // { kk: 4 }

obj.ff(2); // prints: ff called with arg 2

// static method is called directly, not via instance of a object
AA.sm(3); // prints: sm called with arg 3

The above code is similar to the following without using class keyword:

// this is the constructor
function AA (x) {
 this.kk = x;
 console.log("constructor called with arg " + x)
};

// static method
AA.sm = function (x) {console.log("sm called with arg " + x)}

// this is the prototype
AA.prototype = {
 ff: function (x) {console.log("ff called with arg " + x)},
};

// --------------------------------------------------
// using the class

const obj = new AA(4); // prints: constructor called with arg 4

console.log(obj); // { kk: 4 }

obj.ff(2); // prints: ff called with arg 2

// static method
AA.sm(3); // prints: sm called with arg 3

It can also be done with without new nor this.

This makes it easier to see what class is doing, because all parent child relationship are explicit. Here's the code:

// this is the “class” and also as constructor
const AA = ((x) => {
    console.log("constructor called with arg " + x);

    return {
        __proto__:AA.prototype,
        kk:x
    } } );

// static method
AA.sm = function (x) {console.log("sm called with arg " + x)};

// this is the prototype
AA.prototype = {
    ff: function (x) {console.log("ff called with arg " + x)},
};

// --------------------------------------------------
// using the class

const obj = AA(4); // prints: constructor called with arg 4

console.log(obj); // { kk: 4 }

obj.ff(2); // prints: ff called with arg 2

// static method
AA.sm(3); // prints: sm called with arg 3

Following is explanation and tech detail.

Type of Class is Function

Class syntax is:

class name {…}

or

const name = class {…};

Class is a special function object. In many ways, it's same as function.

  1. like function, typeof on class is "function".
  2. like function, parent of class is Function.prototype.
  3. like function, it has properties { length, name, prototype}. (the property name is new in ES2015.)
  4. like function, the value of the property key "prototype", is a new object object with a property key "constructor", by default. [see JS: Property Key "prototype"]
  5. like function, the value of property key "constructor", is the class function itself.

However, class function must be called with keyword new. It cannot be called like a function by itself.

// class is a special function object, in many ways, it's same as function

// define a empty class named A
class A {}

console.log( typeof A === "function" ); // true

// parent is Function.prototype
console.log(
    Object.getPrototypeOf (A) === Function.prototype
); // true

// list all properties
console.log( Reflect.ownKeys (A) ); // [ 'length', 'name', 'prototype' ]
// same properties as function would have

// like function, it has a property key "prototype"
console.log(
    A.hasOwnProperty ("prototype")
); // true

// like function, the value of this property key "prototype", is a object with property key "constructor"
console.log(
Reflect.ownKeys ( A.prototype )
);
// [ 'constructor' ]

// the value of the property key "constructor", is A itself
console.log( A.prototype.constructor === A ); // true

Class Declaration Syntax

The syntax for class declaration is:

class class_name { method1 method2 method3 … }

where a method has one of the following form:

Semicolon ; can be used after each method, but is not required.

(Note: class body must all be function definitions.)

Example:

// example of a class
class A {

    constructor(x) {
        this.p = x;
        console.log("constructor called with arg " + x)
    }

 ff (x) {console.log("ff called with arg " + x)}

 set p1 (x) {console.log("setter called")}

 get p1 () {console.log("getter called")}

 static ss (x) {console.log("ss called with arg " + x)}

}

// ---------------------------
// using the class

// static method is called directly, not via instance of a object
A.ss(3); // prints: ss called with arg 3

// create a instance.
const x = new A(4); // prints: constructor called with arg 4

console.log(x); // A { p: 4 }

x.ff(1); // prints: ff called with arg 1

x.p1 = 1; // prints: "setter called"

x.p1; // prints: "getter called"

What “class” Keyword Do Exactly

Class is a convenient way to define object and the object's parent, and its methods.

When you have class A {body}, it creates 2 objects:

  1. A (a special function object), to be called by new A(…).
  2. A.prototype. (A object object.)

A will have properties that's all the static methods you defined in body.

A.prototype will have properties that's all the prototype methods you defined in body.

Then, when you create a object by new A(…), its parent will be A.prototype.

Note, JavaScript class is not like Java, Python, Ruby's class model. [see JS: Prototype and Inheritance]

Here's detail.

When a class is defined, for example, this:

class CName { contructor (params) {body} method1 method2 static smethod1 }

  1. A function object named CName is created. (typeof CName is "function") CName cannot be called directly, must be called with the operator new. CName is called the “constructor”.
  2. All static methods (e.g. smethod1) is attached as properties to CName.
  3. A property key "prototype" is created. The value of CName.prototype is a new object object. It has properties of all the methods defined in CName, except the constructor function and static methods, and plus a string property key named "constructor", whose value is CName itself.

That is, the value of CName.prototype is this:

{ "constructor": CName, method1, method2 }

When new CName(args) is called:

  1. A new temporary empty object object T is created. Parent of T is CName.prototype.
  2. Constructor method is called with arguments args, its this value is T. [see JS: “this” Binding]
  3. If the constructor method has return statement and returns a value of type object, then that object is returned. Else, T is returned.
// The class function defines the prototype object
class C {
    // constructor. called when class is called with new.
    constructor(x) {
        console.log("constructor called with arg " +x);
        this.kk = x; // add a property
    }

    ff (x) { console.log("ff called with arg " +x) }

    static ss (x) { console.log("ss called with arg " +x) }

}

console.log (
 Reflect.ownKeys (C)
);
// [ 'length', 'prototype', 'ss', 'name' ]

// show prototype's properties
console.log( Reflect.ownKeys (C.prototype) ); // [ 'constructor', 'ff' ]

console.log( C.prototype.constructor === C ); // true

// --------------------------------------------------

// create a object
const cc = new C(3);
// prints: constructor called with arg 3

console.log(cc); // C { kk: 3 }

console.log( Reflect.ownKeys (cc) ) // [ 'kk' ]

// parent
console.log( Object.getPrototypeOf (cc) === C.prototype ); // true

Class Expression

Class can be created by expression form:

Class expression returns a value. Can be called inline.

// class is a expression. it returns a value. can be called inline
console.log (
    new ( class B { constructor(x) { this.p = x; } } ) (3)
); // B { p: 3 }

The name in x = class name {…}; is optional. When given, it allows you to refer to itself inside the class body, but the name is not exposed outsite of it.

// the name of a named class expression is only available inside the class body

const D = class C { constructor(x) { this.p = x; } };

new C(3);
// ReferenceError: C is not defined

Repeated Class Declaration

Class declaration of the same name cannot happen twice. If so, it's an error.

class x {};
class x {}; // SyntaxError: Identifier 'x' has already been declared

No Name Hoisting

Class name is not hoisted. (regardless it's defined via declaration or expression)

This means, the name won't be available until the class definition is reached. Unlike function declaration. [see JS: Function Declaration vs Function Expression]

// class definition does not have name hoisting
// here's a example with class declaration

const a = new A();
// ReferenceError: A is not defined

class A {}
// class definition does not have name hoisting
// here's a example with class expression

const a = new A();
// TypeError: A is not a constructor

const A = class {};

Class Must be Called with “new”

Class function must be called with keyword new. It cannot be called like a function by itself.

class A {}
A();
// TypeError: Class constructor A cannot be invoked without 'new'

Define Getter/Setter Properties

To define getter/setter properties, just use the get or set keyword in front of the method definition.

[see JS: Getter/Setter Properties]

class A {
    // getter property
    get m() {
        return new Date();
    }
}

const x = new A;

console.log(x.m); // prints current date time

// (getter properties are properties that return value from a function, but with normal acess syntax not function call)

keyword “static”

Method names can be declared with a static keyword in front. This is called static method.

Static methods becomes a property of the class function object itself.

Static method does not become part of the prototype object. Thus, object created by the class will not inherit static methods.

// static methods are properties of the class function itself
class D {
    static s(x) { console.log("s called"); return x + 1;}
}

console.log(
    D.s(0)
);
 // prints: s called
// 1

// show all property keys
console.log( Reflect.ownKeys (D) ); // [ 'length', 'name', 'prototype', 's'' ]

Static method cannot be called from a instance of the object. (because static method isn't in the prototype object.)

class E { static f (x) { return x + 1;} }

console.log(
    E.prototype.hasOwnProperty ("f")
); // false

const a = new E;

// static method cannot be called from a instance of the object
console.log(
    a.f(4)
);
// TypeError: a.f is not a function

Properties of Class Related Objects

When a class function object class A {} is created, 3 objects are related to it:

  1. A
  2. A.prototype
  3. new A

We are interested in their properties:

  1. The properties of A
  2. The properties of A.prototype
  3. The properties of new A
class AA {
    constructor(x) { this.kk = x; }
    ff (x) {return x+1;}
    static sm (x) {return x+2;}
}

// 3 objects are related to a class creation
// we show their properties

// object AA
console.log ( Reflect.ownKeys ( AA ) ); // [ 'length', 'prototype', 'sm', 'name' ];
// “length”, “prototype”, “name”, came from any function object
// the “sm” is the static method, user defined

// object AA.prototype
console.log ( Reflect.ownKeys ( AA.prototype ) ); // [ 'constructor', 'ff' ]
// “constructor” is default in any function.
// the “sm” is the static method, user defined

// object new AA
console.log ( Reflect.ownKeys ( new AA() ) ); // [ 'kk' ]
// “kk” is from the user defined constructor

The properties of ‹A›

All static methods in the body of class A {body} become properties of A.

To add data properties to A, simply add it manually.

// class with 1 static method
class AA {
    static sm (x) { return x+1;}
}

// add your own data property
AA.mykey = 3;

console.log ( Reflect.ownKeys ( AA ) );
// [ 'length', 'prototype', 'sm', 'name', 'mykey' ]

console.log ( AA.mykey === 3 ); // true

[see JS: Reflect.ownKeys]

The properties of 「‹A›.prototype」

The property of A.prototype are:

  1. All prototype methods defined in the body of class A {body}. (i.e. all methods except constructor and static.)
  2. string property key "constructor". Its value is A. This is always there.

[see JS: Property Key “constructor”]

class AA {
    constructor(x) { this.kk = x; }
    ff (x) { return x+1;}
    static sm (x) { return x+2;}
}

console.log ( Reflect.ownKeys ( AA.prototype ) );
// [ 'constructor', 'ff' ];

console.log ( AA.prototype.ff(3) === 4 ); // true

console.log ( AA.prototype.constructor === AA ); // true

You can manually add more properties to A.prototype

class AA {
    constructor(x) { this.kk = x; }
    ff (x) { return x+1;}
    static sm (x) { return x+2;}
}

console.log ( Reflect.ownKeys ( AA.prototype ) );
// [ 'constructor', 'ff' ];

AA.prototype.gg = 9;

console.log ( Reflect.ownKeys ( AA.prototype ) );
// [ 'constructor', 'ff', 'gg' ]

The properties of 「new ‹A›」

To add properties to A.prototype, use the this keyword in the constructor function definition.

For example, here we add 2 properties, kk, and jj:

class AA {
    constructor(x,y) {
        this.kk = x;
        this.jj = y;
    }}

const obj = new AA(3,4);

console.log ( Reflect.ownKeys ( obj ) ); // [ 'kk', 'jj' ]

console.log ( obj ); // AA { kk: 3, jj: 4 }

Keyword “extends”

JS: Keyword “extends”

Keyword “super”

JS: Keyword “super”

Reference

ECMAScript® 2016 Language Specification#sec-class-definitions

JS Constructor/Class

  1. “this” Binding
  2. What's Constructor?
  3. Property Key "prototype"
  4. Operator “new”
  5. “instanceof” Operator
  6. Property Key “constructor”
  7. Class
  8. Keyword “extends”
  9. Keyword “super”
Liket it? Put $5 at patreon.

Or, Buy JavaScript in Depth

If you have a question, put $5 at patreon and message me.

Web Dev Tutorials

  1. HTML
  2. Visual CSS
  3. JS Basics
  4. JS in Depth
  5. JS Reference
  6. DOM
  7. SVG
  8. JS Misc