classes (js)

  • https://reinteractive.com/posts/235-es6-classes-and-javascript-prototypes
  • https://levelup.gitconnected.com/using-classes-in-javascript-e677d248bb6e?gi=5ba413dea026

Why do we need classes?

Classes are simply templates used to create new objects. The most important thing to remember: Classes are just normal JavaScript functions and could be completely replicated without using the class syntax. It is special syntactic sugar added in ES6 to make it easier to declare and inherit complex objects.

We create a class like so:

We declare scoped variable in constructor for privacy.

So, a class like this:

translates to this:

Constructor

There can be only one special method with the name “constructor” in a class. Having more than one occurrence of a constructor method in a class will throw a SyntaxError error.

A constructor can use the super keyword to call the constructor of a parent class.

If you do not specify a constructor method, a default constructor is used.

Then we implement getter/setter functions for it and attach them to the this object.
Whenever we attach functionality and property to this, its public.

We can then declare other functions that use private properties such as _name.

For example, if we want to implement private removeLastNode that accesses these private properties/functions, we do so in the constructor because removeLastNode can access _name, _head, _tail by scope. removeLastNode is not visible to the outside.

public functions that uses private properties/functionalities

Now, say we want to implement remove function that wants to access our private function and private properties, we simply implement the public function inside of the constructor, and access those private functionality and properties via scoping.

Notice our remove function is public because it is attached to this.
It accesses private properties such as _head due to scoping.
It accesses private functions such as removeNodeAtHead(…) due to scoping.

functions

functions are attached to the this object, and are public.
If the functions want to use use private properties, they must be in the same scope as the private properties/functionalities, which are in the constructor as shown previously.

In this case, if the functions are outside of the constructor, they use private variables through get/set functions that are exposed on the this object.

functions attached to this are added to the prototype

Take this example, class Test.

Any functions/properties added to Test.prototype is shared among all instances.
Any functions/properties added outside of the constructor function is added to Test.prototype and is hared among all instances.
Any functions/properties added inside of the constructor belongs to the instance itself.

you can check like so:

you can also simply input the code into Firebug and then simply log a.__proto__

you will see that the prototype object will have haha, print, and constructor.
The object itself will have print2.

Thus the methods is being shared by all instances of Test.
Adding methods on the prototypes of objects in JavaScript is an efficient way
to conserve memory (as opposed to copying the methods on each object).

Hi-Jacking these functions on the fly

Prototypes are just objects that can be changed at runtime

Changing the method on the prototype of ‘a’ changes the result on ‘b’ as well.

This is because they share the same prototype and we are changing that object.

Again, prototypes are just objects that can be changed at runtime.

Oops, there is no going back, we have already modified the prototype for all instances of CircularList, now and in the future.

Remember, classes in JavaScript are not a blueprint like in other languages.
They just define objects that can be modified at will in runtime.

TO RECAP:

Inheritance

Continuing from our Test class example…

And OralText to extend from Text:

this is what it looks like diagram form:

So we start off with class Test. We declare function print2 inside the constructor, so its a property of the class. Every instance created from this class will have its own print2.

We then declare function print within the class, but outside of the constructor. print will be included in Test’s prototype object, which is simply represented as {}. This is because it derives (has __proto__ referencing) the default Object Prototype Object. There is no class name it derives from so its empty. Then, we directly attach the function haha to Test’s prototype object. Also, constructor is the special function that all instances of this class need to call before instantiation. Thus, that is why Test Prototype Object has haha, print, and constructor.


> Test.prototype

{haha: ƒ, constructor: ƒ, print: ƒ}
haha: ƒ ()
constructor: class Test
print ƒ print()
__proto__:Object

Then we have OralText, which extends from Test. It copies over the constructor properties, hence we get print2.

More importantly, this Test{} is independent from Test’s prototype object. OralText.prototype has a constructor property, which points back to OralTest. Thus, all instances created from OralTest will have OralTest as its type.

Another important thing is that OralTest.prototype is an object instantiated from Test.prototype. That’s why it has a __proto__ pointing to Test.prototype. This is so that we have a hierarchy in case instances of OralTest wants to access its parent’s prototype.


> OralTest.prototype

Test {constructor: ƒ}
constructor: class OralTest
__proto__: Object

As you can see OralTest’s prototype has a constructor pointing to OralTest (itself). This is so that all instances of OralTest will have OralTest as the type.
We also wee that the name of our prototype object is called Test This is because we extend from Test. Its __proto__ is referencing an Object. Let’s analyze this Object:


OralTest.prototype.__proto__

{haha: ƒ, constructor: ƒ, print: ƒ}
haha: ƒ ()
constructor: class Test
print: ƒ print()
__proto__: Object

So as we see, its the Test’s Prototype object with the haha and print functions.

So now
OralFinal.prototype will give a literal object with the name OralTest. Because our OralFinal extends from OralTest. You’ll see the special constructor function.

We are now at the lowest level of the hierarchy. We want to go up the hierarchy. So we follow the __proto__ path. You’ll see its __proto__ is that of Test. This is because OralFinal’s Prototype Object OralTest, is an object instantiated from Test.

OralTest {constructor: ƒ}
constructor :class OralFinal
__proto__: Test

If you go up one more __proto__, aka __proto__ of Test, you’ll see the no name Prototype Object with the functions haha, print, constructor.

Then if you go up one more, you’ll reach the default object Prototype object. That is the highest point.

  • Classes are NOT hoisted
  • Classes are first-class citizens
  • Classes are executed in strict mode

Another Example

Given class PersonCl: