JavaScript: The Klassic Way!
Tags: javascript, webdev, programming, node
Published: 2024-01-21 06:57:33

Before there was the class keyword, there was the constructor function. And now that there are classes, the constructors are still there. Indeed the `classes` are only a syntactic sugar for what constructor functions have been doing for a long time. 

This post is about the Klassic JS, an era before the `class` keyword existed. When developers used `constructor` functions  to implement `Object-Oriented Behavior`. But this doesn't mean it's useless. All the things we learn about, in this article, are actually how JS still works under the hood.

We'll start by reviewing how special data types likes *Functions* and *Objects* are treated in JS. Then we'll progress through creating objects using *Constructor Functions*. And in the end, we'll learn how JS implements some *advanced OOP patterns*.

## 1. Functions And Objects

When learning about  JS' OOP, objects are the things we'll be talking about. But functions are the ones we'll be using to build those things.

Let's have a quick look at how these two relate in JS.

### 1.1 Everyone Is A First-Class Citizen

Suppose you've a string variable named `a`.

```js
let a = 'Hello World!';
```

You can check how many characters it has using its `length` property.

```js
console.log(a.length) // 12
```

Now let's suppose you've an array, named `b`. You can still check how many elements it has using the same property, `length`.

```js
let b = ['Hello World!']
console.log(b.length) // 1
console.log(b[0].length) // 12
```

What if we define a function and try to log its `length`? Does it still work? Let's test it with an add function.

```js
const add = function(a, b) {
  return a + b;
}
console.log(add.length) // 2
```

The log command in the above code block, will output 2. It's how many arguments the function takes (`a` and `b`). This means that the function we defined has its own set of properties and methods. Like any other of the value types we checked.

This is a simple explanation of what `Functions As First-Class Citizens` in JS means:

>Functions in JS aren't treated like some subroutine to be called later. They're stored as an `object`. We can store them into variables, pass as arguments and return from function calls.

But, what we're most interested in, is how we can use them as `Object Constructors`. 

More on that later.

### 1.2 So Everything Is An Object?

Well, not everything. As a matter of the fact, the string variable, we defined (`a`), is known as a primitive value. So, it's an exact value stored somewhere in memory and assigned to a variable's memory address.

But how does `a.length` work, if it's a simple value rather than an object?

Any time you try to access a property or a method on a primitive value, JS will provide an `Object Wrapper` for the value. It uses that wrapper to give you access to the methods and properties.

So, while everything is not an Object, many things are treated as one. This means objects are a core part of JS. That makes them important when interacting with the language's API.

## 2. Constructing Objects

In most Object-Oriented programming languages, `blueprint` is a keyword in explaining OOP structures. But this isn't the case for JS.

In JavaScript, the keyword is `prototyping`. This is how the language, shapes the objects's structure and behavior. There's a prototypal inheritance chain in the background. It specifies what properties and methods are accessible by an `Object Instance`. This simplifies the process of sharing properties among different instances of an object.

We create these instances using constructor functions. So, before we get deep with the prototypes, let's see what a constructor is and how it works.

### 2.1. Functions Will Do The Job

Constructors are functions. You can define a function. Call it with a `new` keyword, and congratulations! You've used a constructor. 

But in order for a constructor to be a useful one, you should write a useful function. How's that? Let's look at some examples.

Do you remember the `add` function? Let's recall the code block:

```js
const add = function(a, b) {
  return a + b;
}
```

This function simply takes two numbers and returns a new one. We can call it with a `new` keyword, if we want to.

```js
const adder = new add(1, 2)
```

But this line has no meaning. The adder is an empty Object. Of course not an entirely empty object. We'll get to that later. But in our case, this line is meaningless. In order to write a useful constructor function, we need to use the `this` keyword. 

By using `this` we can access the instance of the new object that is created when the function is called with the `new` keyword.

But first let's check what an instance is.

### 2.2 I have an instance, therefore I exist!

We're all Humans. 

Indeed, `human` is a species name. We all share some traits which differentiate us from other species and animals. We can walk on two legs, we have two eyes, and we can speak in different languages. 

While all of us are humans, we're not the same human. Each of us are unique. Our birthday is different. Our parents are different. We're born in different locations, and we even have different names. 

The difference is what we can call the `identity`. You might be of the same type as the others, but you've got a different identity.

The constructor functions, will produce new Objects. These objects will share some traits, but they're not the same production. Each of them is located in a different memory address from the others. 

So each instance of an object, is a working example of that object that is stored in a specific memory address.

Now we can finally try creating an object with a constructor function. And see where it goes.

### 2.3 `new`

We already learned the `add` function was not usable. That's because calling it with the `new` keyword returned an empty object. But where does this object come from, and why the hell does the `new` keyword even work?

The `new` keyword always creates an empty new Object. Why? So, it can give us access to that object, inside the function call. 

Let's illustrate it through steps:

1 You call the function with the new keyword:

```js
const adder = new add(1, 2)
```

2 Now when this code is getting interpreted, as the JS engine reaches the `new` keyword, it creates an empty object:

```
*** INTERPRETER MEMORY

ADDRESS: 0x123456789 
=> STORE NEW OBJECT REFERENCE 
=> PROPERTIES: []

```

This is a very simple illustration of the memory allocation of the new object that is created. This object has no properties, which is shown as an empty array.

3 The function is called, and the new object is passed into it as the `this` keyword.

```

*** INTERPRETER MEMORY

ADDRESS: 0x22222222 
=> FUNCTION DEFINITION 
=> CALL FUNCTION BODY 
=> THIS = 0x123456789

... run the rest of function body
```

So, when trying to access the `this` keyword inside the function block, it will give you an empty object.

```js
console.log(this) // {}
```

`this` is the object that `new` created.

4 The interpreter runs your function. It evaluates everything and then assigns the value for the `adder` variable. But what value? Will it be the sum of the two numbers? No. It will be the object which was passed to this. In case of our function, an empty object.

```js
console.log(adder) // {}
```

Thus, an object is created on the air, and after the function is called, the same object is assigned to the variable. 

But what good is this new object for us? The answer is, we can mutate this object. We can add properties to it, and shape what we need to be returned. 

Finally, we're on track!

### 2.4 `this` Is An Object

Objects in JS are mutable. This means, they don't have private or strict members. You can always change a member of an object. Or assign members and values to it.

To create a new object in our constructor function, we've to utilize this behavior. We're given an empty object via the `this` keyword and then we can shape it as our needs.

So, let's change our add function to make it a useful `constructor`.

```js
const add = function(a, b) {
  this.first_number = a;
  this.second_number = b;

  this.sum = a + b;
}
```

As you can see, we don't need extra steps to get access to the object stored in `this`. We can start on the fly and try adding members to it.

We've added three members to the Object. The `first_number` and `second_number` properties store the passed arguments in the object. The `sum` property is the result of adding them.

Next, we call the function with the new keyword:

```js
const adder = new add(1, 2)
console.log(adder.sum) // 3
```

Voila! We've done the equivalent of creating a class using the `class` keyword in ES6+. Indeed we've done what the `class` keyword actually does under the hood.

Let's try creating another object using the `add` constructor.

```js
const adder2 = new add(2, 3)
console.log(adder2.sum) // 5
```

So, we did it. Now what?

## 3. JS OOP: Behind The Scenes

By this point, we have learned about many things. Including `constructors`, `new` and `this` keywords, and how functions are objects.

In this section we'll dive deeper into the topics concerning how JavaScript thinks about these processes. In short, we want to grasp the concepts!

### 3.1 Prototypes (again)

I mentioned the `prototype` earlier in this post. It defines how OOP behaviors like `inheritance` and `polymorphism` are implemented in JS.

We'll first have a look at how prototypes can be inspected and altered.

#### 3.1.1 Defining Methods

All that our `adder` objects have are two number properties with their sum as a third one. This is cool, but not a very useful thing. In a dynamic programming environment, functions are more useful.

What if `adder.first_number` is updated by mutation, but `adder.sum` stays the same value? We can change `sum` into a function:

```js
const add = function(a, b) {
  this.first_number = a;
  this.second_number = b;

  this.sum = function() {
    return this.first_number + this.second_number;
  }

}
```

We can create a new instance and check its properties:

```js
const adder = new add(1, 2);
console.log(adder);
```

This will output the following object:

```js
{
    "first_number": 1,
    "second_number": 2,
    "sum": f()
}

```

So, we can call `adder.sum` whenever we want and have access to the latest sum of the two numbers in the adder:

```js
console.log(adder.sum()); // 3
adder.first_number = 4;
console.log(adder.sum()); // 6
```

And as you can see, the `sum` function has access to the latest value of `first_number`. So, it will return correct values after `first_number` is updated.

#### 3.1.2 In Search Of A Better Solution

Do you remember when we talked about instances? In our previous examples each of `adder1` and `adder2` are instances of the `add` function. Instance means they each have a new memory address assigned to them.

This also means every property and method that is a member of  this object is stored in a new memory address specific to it. This includes the method definitions too. While this can seem reasonable, it isn't the most efficient approach.

Efficiency in this case is concerned with the fact that each time we create a new instance of add, we also utilize a new memory address for the sum method. This is because, as we learned through this post, function definitions are objects and first class citizens. So they will require to be redefined every time.

But what if there was a way we could define this method only once and access it in every instance? Because a function like `sum` doesn't change every time a new instance of add is created. 

The definition is always the same. The only thing that changes is the value of `this`. The solution in this case is the object `prototype`. 

Let's travel in time and return our constructor function to the state when it didn't still have the sum function definition. We also remove the sum property. 

```js
const add = function(a, b) {
  this.first_number = a;
  this.second_number = b;
}
```

What do you think will happen if we call `adder.sum()`? It surely throws an error indicating that sum is `undefined`. But what will exactly happen, before JS throws the error? Does it only check the adder's instance to find sum? The answer is no. It will also look into `adder.prototype`.

In the next section. We'll review the exact order of what happens under the hood.

#### 3.1.3 The Prototypal Chain

When we call `adder.sum()` on a version of it that doesn't have `sum` function defined, JS will do a chain of searches before it returns to us and identifies `sum` as `undefined`. 

First of all, keep this in mind:

>Every object in JS has a link to another object, which is known as its prototype.

Then let's visualize how the JS engine looks for the sum method. For this, we can log the objects in our devtools console to check the output:

```js
const adder = new add(3, 4);
console.log(adder)
```

This will output the following object:

```js
{
  first_number: 3,
  second_number: 4
}
```

The above output displays the `own properties` of the object. Own properties only exist on this instance of the object and have the highest priority in the prototypal chain. The JS engine will look into these properties and doesn't find `sum`.

But it doesn't lose hope, and looks further, into another object. But which one?

The above output is what the console.log typically shows of an object which includes only the `own properties`. Our object has another property which isn't listed here. It's the prototype which we can access and read in three ways: 

1. By opening the browser devtools console and typing the above console.log command.

2. Using `Object.getPrototypeOf(adder)`.

3. With `console.log(adder.[[Prototype]])`. This is the property key which stores the prototype object in an instance. Every object has this key.

We'll follow with the second approach as it's a clean one. We run the following statement:

```js
console.log(Object.getPrototypeOf(adder))
```

Which results in:

```js
{
  constructor: f {}
}
```

Where does this prototype come from? As every function in JS is an object, every one of these functions has a prototype which initially has one important method. It's the `constructor` we see in the above output. It's the `add` function we've defined. 

So, every instance of objects returned by a constructor can actually access its constructor function using `instance.[[Prototype]].constructor`. But it can also access any other property or method set in prototype.

The JS engine will look into this prototype object and still doesn't find a property named `sum`. So, what next? Does it print the error? No. The engine will continue searching. This time in `adder.[[Prototype]].[[Prototype]]`. 

But what is this second protoytpe?

Every constructor prototype, when created, has a link to the global `Object` namespace of JS. Object is an object by itself. But it's used to create other objects and is the base prototype of all objects in JS.

Let's inspect a log of it:
```js
const adderPrototype = Object.getPrototypeOf(adder)
console.log(Object.getPrototypeOf(adderPrototype)
```
Do you notice `Object.getPrototypeOf`? Yes it's the same Object, whose prototype is available in every JS object. The output of the above command looks like the following:
```
{
  constructor,
  hasOwnProperty,
  isPrototypeOf,
  ...
}
```

There are a lot of utility methods in this prototype, which we haven't included here. But they're not our concern in this article. What we want to know is what will happen if JS doesn't find the sum method here too?

Well, it will look into prototype of this object. You might think we'll keep this loop forever. But it will end right here. That's because `Object.[[Prototype]].[[Prototype]]` is `null`. Which means it isn't linked to any other object.

So every object is linked to another object, if its prototype is an object. If the prototype is set to null, it's safe to say that the object isn't linked to another one.

This is the end of the chain. JS will throw an error and the program exits.

But we've still got things to do!

#### 3.1.4 Adding Members To The Prototype
Like how we altered the `this` keyword inside the functions we can alter the prototypes too. To do so how we have different ways. For example as we have been using the `Object.getPrototypeOf` for retrieving the prototype object we can use `Object.setPrototypeOf` for updating it. 

You might also be tempted to mutate the `[[Prototype]]` object directly. You can do that too. You can set it to a completely different object, create your own chain of inheritances or even make it null, so the prototype won't affect your object.

This means it's a dirty but yet powerful concept. In this section we only want to learn how methods are added to the prototype so they're defined once and accessible in every instance of the object.

Indeed we saw the solution to this problem in the last section, when we were journeying through the prototype chain. You might have noticed when we checked prototype of the `adder` we got an object that had an instance to the `constructor` function. This is the best place for storing methods or static properties. Since it's only related to the constructor function and is available to every instance in the first level of prototypal chain.

We can mutate the prototype and add the sum method. It's done quit easily by `add.prototype.sum = ...`. This is a convenient way to add methods to constructor functions in JS.
```js
const add = function(a, b) {
  this.first_number = a;
  this.second_number = b;
}

add.prototype.sum = function() {
  return this.first_number + this.second_number;
}
```
We can easily check that sum works:
```js
const adder = new add(1,2);
console.log(adder.sum()); // 3

adder.first_number = 100;
console.log(adder.sum()); // 102
```

This was all that we needed to do. But there's one last thing to check out. The fact that sum works correctly in this example means that when JS finds a method in the prototypal chain of an inheriting object, the `this` keyword inside that method always points to the inheriting object. 

In our example, the `inheriting` object is `adder` and the method is in the prototype of the function `add`'s object. But `this` doesn't point to the function's object. It points to the object, the search started from.

Over.

### 3.2 Inheritance and Polymorphism
So far, we can create objects from functions, and we can assign methods to them. We have even learned about the Prototypal chain. But did we go through all that hassle, only to learn how to define shared methods for our objects?

No. We can do more. Indeed prototypes make us able to customize our objects in more complex ways. These include inheriting and polymorphism. These are both advanced OOP concepts well-known in most of OOP languages.

First of all we want to learn what inheritance is.

#### 3.2.1 The Problem: Create More Objects, Write Less Code.
The `add` function does its job very great. And all our `adders` work perfectly. But what if we wanted to create a type of adder objects which also keep a record of every time a sum was called and the values had been changed.

We can do it in many ways. We can create an absolutely new constructor function, which does the job for us. Or we can change the add function so it keeps the records. But what if we don't want the adders to be able to do more than what they are currently capable of? 

Yet the add function shares some of the code we need for the new objects. So, the third way will be a combination of these two. 

We don't rewrite the part of code that is shared among the objects. But we create new objects, which in addition to the initial properties can do more advanced things.

Prototypes come to our help. Again.

#### 3.2.2 A New Function.
We start by creating a new constructor. We name it `addRecord`. As it's the same as add with the difference it records the changes.
```js
const addRecord = function(a, b) {

}
```
Our function's body, is empty, because we don't yet know how to handle it. First of all we know that we need to reuse the `add` function. Maybe we can try calling the add function inside `addRecord`? Let's try that:
```js
const addRecord = function(a,b) {
  const adder = new add(a,b) // But where do we put adder?
}
```

As you can see we can try creating a new instance of add using new. We even stored it in a variable. But there's a problem. The variable is not a part of our object's instance. Maybe we can try assigning the properties of adder to our `this` object? But that's not clean and what if we had a hundred properties? Spreading? Seriously? No. These solutions only add references to the instance created by adder. This is not what we want. 

Whatever approach we use, we're not going to get a clean object. While addRecord reuses add, it shouldn't treat add as a different object. Indeed it's an altered version of add. It should inherit the behaviors of add.

But how? JS has two interesting methods available for us. They're the `call` and the `apply`. Our saviors.

#### 3.2.2 `this`. Again.
Some years ago, when I was reading about the `call` and `apply` methods for the first time I had a serious question. While the definitions were clear and I exactly knew what they did, I still couldn't understand what their use case can be.

Well, it turned out they were mainly used in OOP patterns. And they do their job beautifully.

Here's how MDN defines the `call` method:
>The call() method of Function instances calls this function with a given `this` value and arguments provided individually.

The syntax looks like the following:
```js
function.call(thisArg, arg1, arg2,... , argN)
```

What `apply` does is identical to what call does. It actually doesn't do anything different. The only difference is in the syntax. Apply accepts the arguments as an array. it doesn't take them separately. here's the syntax:
```js
function.apply(thisArg, [arg1, arg2,... , argN])
```

The `thisArg` is what we're most interested in. It lets us do what we did with new with one difference. `new` passed a new object as `this` to `add`. But with `call` we can pass any object we want as `this`. I think you can guess it now.

We already have a `this` when `addRecord` is called with new. Why not pass it to `add` and let it add `first_number` and `second_number` properties to the instance created by `addRecord`?

Okay, we'll do it!
```js
const addRecord = function(a, b) {
 add.call(this, a, b)
}
```

Then let's create an instance of addRecord and log its properties:
```js
const addRecorder = new addRecord(1, 2);
console.log(addRecorder)
```
It will output the following object:
```js
{
  first_number: 1,
  second_number: 2
}
```
But if we inspect its prototype we'll see, it only contains the constructor of `addRecord`. There's no sign of the `sum` function again. But don't worry! We don't need to look for it through the whole chain. We'll add it to the chain. 

How? We manually link the prototype of `addRecord` to `add`.
```js
// after we've defined addRecord
addRecord.prototype.prototype = add
```
This way, when we later call `addRecorder.sum()`, it will be found down a deep chain of prototypes. (You can figure it out by yourself)

Mission accomplished.

#### 3.2.3 Everything is a Prototype of a Prototype of a Prototype!
Directly setting a prototype's prototype to a new object, looks a very interesting example of how inheritances can be implemented and mutated in JS.

By now we've only written one line inside `addRecord` and one line outside of it. And yet we've totally inherited every property and method of `add`. The next step is to define the recording functionality.

For this purpose we add a new property to `addRecord` object. It will be an array to later keep the records. The next step will be defining a function to save the result of a sum every time it's called.

We can try defining a new method which has a different name from sum. But that goes against what we're trying to accomplish. The reason that we defined the addRecord in the first place was that we wanted it to be identical to add. The only difference is that when `sum` itself is called the result should be recorded. 

For this, we can define a new method in `addRecord` prototype and name it sum. It does the job of doing the sum, recording the result and returning it. 
```js
const addRecord = function(a, b) {
 add.call(this, a, b);

 this.records = [];
}

addRecord.prototype.prototype = add;

addRecord.prototype.sum = function() {
  const result = this.first_number + this.second_number;
  this.records.push([this.first_number, this.second_number, result]) // add an array which keeps records of current (a, b) and the sum
  
  return result
}
```

This should be the complete code of addRecord. Now if we call `addRecorder.sum()` it should return the result and also save an array of the current (a,b) values and the sum in the records array.

You might be asking yourself how is this sum method useful? Why not just rename it in some other way? Or why not just add recording behavior to the original add function?

The answer actually depends on the design of your application. In our case we want the developers using our code to have access to two different APIs. Both of them are kept really identical. They both take two arguments and they both have the same properties and methods. This will keep the implementation simple for them. Because they won't need to remember two different methods or property names.

This also keeps the behaviors separated. Maybe the `add` object is used for some anonymous behaviors in which we never want any ways of keeping records. So we don't at all implement the behavior in it.

On the other hand the addRecord function is exactly built for this behavior. It doesn't need too much rewrite and it overrides the sum method, without breaking any functionalities accessible by add.prototype.

by the way, the thing we did by overriding the `sum` method is called `Polymorphism`. It means the function does different behaviors in different objects or situations. It was cool, wasn't it? :)

Here's the result:
```js
const addRecorder = new addRecord(1, 2)
console.log(addRecorder.sum()) // 3
console.log(addRecorder.records) // [[1,2,3]]

addRecorder.first_number = 66;
console.log(addRecorder.sum()) // 68
console.log(addRecorder.records) // [[1,2,3], [66, 2, 68]]
```

## 4. The End
This has already been a long article. But I have tried to be as through as possible. Because, by learning all of these concepts you actually know what's happening under the hood every time you use the `class` keyword. Everything we learned about Inheritance, Polymorphism and Prototypes is exactly how the `extends` keyword of classes works.

Understanding the `this` keyword and what value it keeps in different situations can come really handy. Besides those things, knowing how functions are more than just functions, can give you a new insight into how JavaScript is actually shaped.

JS can be really strange and crazy at times. But don't forget; it's the same tool that has evolved the web so far. And it's capable of evolving even more!

Thanks for reading this far. And I hope it has been useful to you. If you have any questions, or ideas about what I should write next, reach me in the comments section. I will be glad to answer you!