Object-Oriented JavaScript: More Than Just Classes
Tags: javascript, prototype, oop, closure
Published: 2023-08-23 09:47:02

I stumbled upon this article I wrote in French precisely 15 years ago and I find it's still relevant. Despite its popularity, JavaScript is a language that deserves to be better known and acknowledged. So, I decided to translate it into English and republish it after a slight brush-up!

This article assumes you're familiar with JavaScript syntax. Note that I'm not discussing the class-based object orientation introduced in ECMAScript 6, but rather the native object orientation that has been present from the beginning in the language, based on the prototype concept.

So, let's take a deep breath and dive into the mysterious world of objects, prototypes, and closures!

*Note*:
The `print()` function used in the examples is not part of the ECMAScript standard. In console mode, it displays text on the standard output. In a browser, it could be replaced by `Document.write()` or `alert()`. In Node.js you could use `console.log()`.

## Genuine Object Pieces Inside...

### Objects

JavaScript is fundamentally built around the concept of objects. Everything in it is an object or a reference to an object. For example, arrays, types, or even functions are objects. Objects contain members, called properties, in the form of (name, value) pairs. Property values can be strings, numbers, booleans, or other objects (including arrays and functions).

Let's start with a simple example to represent my dog Rex:

```javascript
// My dog Rex
var myDog = new Object();
myDog.name = "Rex";
myDog.gender = "Male";
print(myDog.name);
print(myDog.gender);
```

Executing the above code returns:

```
Rex
Male
```

We can also declare and manipulate an object like an associative array:

```javascript
// Declaration as an array
var myDog = new Array();
myDog["name"] = "Rex";
myDog["gender"] = "Male";
print(myDog["name"]);
print(myDog["gender"]);
```

```
Rex
Male
```

myDog is still an object, and its properties can be accessed like array elements.

There's also a shorthand declaration for JavaScript objects:

```javascript
// Shorthand declaration
var myDog = {name: "Rex", gender: "Male"};
print(myDog.name);
print(myDog["gender"]);
```

```
Rex
Male
```

This notation has been adopted by the JSON (JavaScript Object Notation) serialization format, which allows for the exchange of structured data over the network. It's often used as an alternative to the XML format by AJAX web applications for asynchronous client-server communications.

Now, let's give Rex the ability to speak:

```javascript
myDog.bark = function() {
  return "Woof!";
}
print(myDog.bark());
```

```
Woof!
```

In JavaScript, since functions are objects, it's possible to declare them as properties of other objects, thus creating methods. However, note that JavaScript doesn't differentiate between an object's properties, whether they're attributes or methods.

### Constructors and Object Types

Now, imagine I also own a female dog named Mirza and want to represent her in JavaScript. We could replicate the previous declarations, but ideally, we'd reuse a common structure for both Rex and Mirza, i.e., create an object type `Dog`.

JavaScript allows this through the use of a special function called a constructor, as follows:

```javascript
// Dog object constructor function
function Dog(name, gender) {
  this.name = name;
  this.gender = gender;
  this.bark = function() {
    return "Woof!";
  }
}
// Creating new Dog type objects
var myDog = new Dog("Rex", "Male");
var myFemaleDog = new Dog("Mirza", "Female");
print(myDog.name);
print(myFemaleDog.gender);
```

```
Rex
Female
```

Let's clarify two keywords used in the above example:
- `new` is the object creation operator. When applied to a function, that function is used as a constructor to create the new object.
- `this`, within the constructor, refers to the object being created. So, this.name corresponds to a property of the object created by the `Dog` constructor, to which the value of the `name` parameter passed to the function is assigned.

Although JavaScript handles primitive types (retrievable via the `typeof` operator), it's more useful to consider that an object's type is the name of its constructor (retrievable via the constructor.name property). According to this perspective, the types managed by JavaScript are as follows:

- **Object**: generic, untyped object;
```javascript
var myObject = {firstName: "Joe", lastName: "Black"};
print(myObject.constructor.name);
```
```
Object
```
- **Boolean**: boolean (either true or false);
```javascript
var myBoolean = (1 == (2-1));
print(myBoolean.constructor.name);
```
```
Boolean
```
- **Number**: integer or decimal number;
```javascript
var myNumber = 1;
print(myNumber.constructor.name);
```
```
Number
```
- **String**: string of characters;
```javascript
var myString = "Hello";
print(myString.constructor.name);
```
```
String
```
- **Array**: array;
```javascript
var myArray = [1, "brt"];
print(myArray.constructor.name);
```
```
Array
```
- **Function**: function containing a code segment;
```javascript
var myFunction = function() {
  return "hi there";
} 
print(myFunction.constructor.name);
```
```
Function
```
- **[ConstructorName]**: type defined by the user via a constructor function.
```javascript
function Dog(name, gender

) {
  this.name = name;
  this.gender = gender;
  this.bark = function() {
    return "Woof!";
  }
}
var myDog = new Dog("Rex", "Male");
print(myDog.constructor.name);
```
```
Dog
```

By the way, let's recall that JavaScript is a dynamically typed language. This means that it doesn't know in advance the type of a variable (or rather, the object referenced by a variable). It relies on the value of the variable to determine its type and is able to convert the type of a variable based on context.

For instance:

```javascript
var monNombre = 34;
var maChaine = "5";
print(monNombre + maChaine);
```
```
345
```

### Encapsulation

Although JavaScript is an interpreted language, it allows the implementation of the encapsulation principle by distinguishing several levels of visibility:

- **Public properties**: By default, the components (attributes or methods) of an object are accessible to everyone.
```javascript
function Object1() {
  this.attribute = "Public attribute";
}
Object1.prototype.function = function() {
  return "Public method";
}
var MyObject1 = new Object1();
print(MyObject1.attribute);
print(MyObject1.function());
```
```
Public attribute
Public method
```
- **Private properties**: Properties created in the constructor are only accessible to the object's private methods.
```javascript
function Object2() {
  var attribute = "Private attribute";
  function function() {
    return "Private method";
  }
}
var MyObject2 = new Object2();
print(MyObject2.attribute);
try {
  print(MyObject2.function());
} catch(e) {
  print(e);
}
```
```
undefined
TypeError: MyObject2.function is not a function
```
- **Privileged methods**: Methods that can access the object's private properties while being accessible from outside.
```javascript
function Object3() {
  var attribute = "Private attribute";
  this.function = function() {
    return(attribute);
  };
}
var MyObject3 = new Object3();
print(MyObject3.function());
```
```
Private attribute
```

### Namespaces

As we have seen, the default execution context of the script is associated with the Global scope. Thus, all objects and all references declared outside of functions are global properties. As this can create conflicts, it is recommended to use objects to create namespaces and isolate variables in named packages.

This is achieved as follows:
```javascript
// Package declaration
var org = {}; // Package root
org.test = {}; // Package branch

// Package members
org.test.Dog = function(name, gender) {
  this.name = name;
  this.gender = gender;
};

org.test.Dog.prototype.bark = function() {
  return this.name + " barks!";
};

// Using the package
var myDog = new org.test.Dog("Rex", "Male");
print(myDog.bark());
```
```
Rex barks!
```

### Prototypes

In our example, `Dog` is not a class but indeed an object, certainly a special one, but an object nonetheless. In fact, JavaScript doesn't implement a class-based programming model (which leads some to say that it's not an object-oriented language), but rather a prototype-based programming model, similar to that of the Self language.

For every function, JavaScript associates a particular object, called a prototype, to serve as a reference for all objects created using the `new` instruction. This object is accessible through the `prototype` property of the constructor.

When a value is assigned to a property, JavaScript creates the property at the object level if it doesn't already exist. On the other hand, when the value of a property is requested, JavaScript performs the following operations:
- If the property exists at the object level, its value is returned; otherwise, JavaScript accesses the object's prototype and returns the prototype property's value if it exists;
- Otherwise, JavaScript accesses the prototype's prototype, and so on;
- If the property is not found by going up this prototype chain, JavaScript eventually arrives at the highest prototype, that of the generic Object, and then returns `undefined`.

Since an object's prototype is also an object, it can be dynamically modified. Modifying a prototype property (attribute or method) affects all instances due to the mechanism described above. This allows for dynamic and retroactive modification of all objects sharing the same prototype.

For example:

```javascript
function Dog(name, gender) {
  this.name = name;
  this.gender = gender;
  this.bark = function() {
    return "Woof!";
  };
}

myDog = new Dog("Rex", "Male");

Dog.prototype.sleep = function() {
  return "Shh! " + this.name + " is sleeping...";
}

print(myDog.bark());
print(myDog.sleep());

myDog.sleep = function() {
  return "Zzz...";
};

print(myDog.sleep());
```
```
Woof!
Shh! Rex is sleeping...
Zzz...
```

Let's break down step by step the actions performed by the JavaScript interpreter to fully understand the relationship between an object and its prototype:

![Prototype Chain](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/heutmflqoymtoe2jwrii.png)

- **Step 1** - The interpreter detects a function:
  - An object (`F` here) representing the `Dog` function is created.
  - An object (`P` here) will serve as the prototype if the function is used as a constructor.
- **Step 2** - Assign to the variable `myDog`:
  - A blank object (`O` here) is created.
  - The `Dog` function is used as a constructor, with the implicit parameter `this = O`.
  - The object `O` now has the properties: name, gender, and bark.
  - The variable `myDog` is a reference to the object `O`.
- **Step 3** - Add a property to the constructor's prototype:
  - The bark property is added to object `P`.
- **Step 4** - Call the `bark()` method of the variable `myDog`:
  - `myDog` points to the object `O`.
  - The object `O` has a `bark` property, so it is returned (and since it's a function, it's executed and its value returned).
  - `myDog.bark()` returns "Woof!"
- **Step 5** - Call the `sleep()` method of the variable `myDog`:
  - `myDog` points to the object `O`.
  - The object `O` does not have the `sleep` property, so JavaScript goes to its prototype.
  - The prototype of object `O` is the prototype of its constructor, which is object `P`.
  - The object `P` has the `sleep` property, so it is returned (and since it's a function, it's executed and its value returned).
  - `myDog.sleep()` returns "Shh! Rex is sleeping..."
- **Step 6** - Rewrite the `sleep()` property of the variable `myDog`:
  - `myDog` points to object `O`.
  - The `sleep` property of the object `O` is created.
- **Step 7** - Call the `sleep()` method of the variable `myDog`:
  - `myDog` points to the object `O`.
  - The object `O` now has the `sleep` property, so JavaScript doesn't go to its prototype.
  - `myDog.sleep()` returns "Zzz..."

This dynamic modification mechanism through their prototype is also applicable to JavaScript's predefined objects:

```javascript
String.prototype.reverse = function() {
  var reversed = "";
  for (i = this.length; --i >= 0;) {
    reversed += this.charAt(i);
  }
  return reversed;
} 

var myString = "Hello!";

print(myString.reverse());
```
```
!olleH
```

The `reverse()` method is now available for all strings!

Let's translate the content into English:

### Static Properties

Properties declared outside of the constructor (and therefore not present in the prototype) are not referenced by instantiated objects. This allows for the implementation of the equivalent of static methods and attributes in class-based programming (sometimes referred to as class methods and attributes):

```javascript
var Dog = function(name, gender) {
  this.name = name;
  this.gender = gender;
  this.bark = function() {
    return "Woof!";
  }
}

//Static method for detecting identical names
Dog.hasSameName = function(dog1, dog2) {
  return (dog1.name == dog2.name);
}

var myDog = new Dog("Rex", "Male");
var myBitch = new Dog("Mirza", "Female");

print(Dog.hasSameName(myDog, myBitch));

myBitch.name = "Rex";
print(Dog.hasSameName(myDog, myBitch));
```
```
false
true
```

### Inheritance

#### Prototype-based Inheritance

As we have already pointed out, the prototype-based programming model does not use the concepts of classes and class instances, but only that of objects. Therefore, inheritance doesn't occur between classes defined statically, but directly between object prototypes.

Inheritance between two objects is achieved by assigning the parent object to the prototype of the child. It is then possible to add new properties to the child or even replace those inherited from the parent.

Let's now imagine that I also want to represent my cat Felix, while reusing what we did for Rex and Mirza.

![Inheritance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dreogmhmbgysw8xcf9fi.png)

Let's create a parent object Animal and two child objects, Dog and Cat:

```javascript
// Parent object
function Animal(name, gender) {
  this.name = name;
  this.gender = gender;
  this.eat = function() {
    return this.name + " eats";
  }
  this.sleep = function() {
    return "Quiet! " + this.name + " is sleeping...";
  }
}

// First child object
function Dog(name, gender) {
  // Passing parameters to the parent object
  Animal.call(this, name, gender);
} // which inherits from Animal

Dog.prototype = new Animal();
// but is of type Dog
Dog.prototype.constructor = Dog;

// Add a method to the child
Dog.prototype.bark = function() {
  return this.name + " barks!";
}

// Second child object
function Cat(name, gender) {
  // Passing parameters to the parent object
  Animal.call(this, name, gender);
} // which inherits from Animal

Cat.prototype = new Animal();
// but is of type Cat
Cat.prototype.constructor = Cat;

// Add a method to the child
Cat.prototype.meow = function() {
  return this.name + " meows!";
}

var myDog = new Dog("Buddy", "Male");
var myCat = new Cat("Whiskers", "Male");

// Add a method to the parent object, after creating child instances
Animal.prototype.eat = function() {
  return this.name + " eats";
}

print(myDog.sleep());
print(myDog.bark());
print(myDog.eat());

print(myCat.sleep());
print(myCat.meow());
print(myCat.eat());
```
```
Quiet! Buddy is sleeping...
Buddy barks!
Buddy eats
Quiet! Whiskers is sleeping...
Whiskers meows!
Whiskers eats
```

Note the use of the `call()` method of the JavaScript `Function` object, which allows a function to be called as if it belonged to the object to which it is applied. We use this function to pass the name and gender parameters between the constructors of the children and the parent.

#### Multiple Inheritance

Although JavaScript does not support multiple inheritance, it is possible to partly simulate this behavior by using the `call()` method mentioned above. However, since an object can only have one constructor (and therefore one prototype) at a time, it is impossible to maintain the dynamic link between parent and child objects.

Let's try to represent a Werewolf, which is both a `Man` and a `Wolf`, through prototype inheritance:

![Multiple inheritance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66jtdxpmq7045en833aq.png)

```javascript
// First parent object
function Wolf() {
  this.bite = function() {
    return this.name + " has bitten you!";
  };
}

// Second parent object
function Man() {
  this.speak = function() {
    return this.name + " speaks to you...";
  };
}

// Child object
function Werewolf(name) {
  this.name = name;
  Wolf.call(this); // Call the constructor of the first parent
  Man.call(this); // Call the constructor of the second parent
  this.transform = function() {
    return this.name + " has transformed!";
  };
}

var myWerewolf = new Werewolf("Jack");

print(myWerewolf.speak());
print(myWerewolf.transform());
print(myWerewolf.bite());
```
```
Jack speaks to you...
Jack has transformed!
Jack has bitten you!
```


#### Dynamic Inheritance

When creating a new object using the `new` operator, the created object is assigned an implicit reference to the constructor's prototype. Since this prototype is also an object, it seems feasible to dynamically modify an object's inheritance by modifying its prototype chain.

Let's now try to represent a Werewolf, which is either a Man or a Wolf but never both at the same time:

![Dynamic inheritance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tn7el0k32l64fx4x5wab.png)

```javascript
// First parent object
function Wolf() {
}

Wolf.prototype.speak = function() {
  return this.name + " growls...";
};

Wolf.prototype.bite = function() {
  return this.name + " has bitten you!";
};

// Second parent object
function Man() {
}

Man.prototype.speak = function() {
  return this.name + " speaks to you...";
};

Man.prototype.bite = function() {
  return this.name + " bit his tongue!";
};

// Child object
function Werewolf(name) {
  this.name = name;
}

Werewolf.prototype = new Man();

var myWerewolf = new Werewolf("Jack");
print(myWerewolf.speak());

Werewolf.prototype = new Wolf();
print(myWerewolf.speak());

var myWerewolf2 = new Werewolf("Joe");
print(myWerewolf2.speak());
```
Regrettably, the previous code returns this:
```
Jack speaks to you...
Jack speaks to you...
Joe growls...
```

The `myWerewolf` instance wasn't impacted by the modification of its constructor's prototype. This can be explained by the fact that its implicit prototype property still points to the same object, even if it's not the new prototype of its constructor. On the other hand, the new `myWerewolf2` instance does indeed have an implicit reference to the new prototype.

To dynamically modify an object's inheritance, we would need to be able to directly modify the implicit reference to the constructor's prototype. The implementation of this property, although discussed in the ECMA-262 standard, is not mandatory. Thus, it's not available in all ECMAScript implementations.
It is generally implemented by the `__proto__` property. The following code proves that the `__proto__` property indeed points to the object's constructor's prototype:

```javascript
// Parent object
function Wolf() {
}

Wolf.prototype.speak = function() {
  return this.name + " growls...";
};

Wolf.prototype.bite = function() {
  return this.name + " has bitten you!";
};

// Child object
function Werewolf(name) {
  this.name = name;
}

Werewolf.prototype = new Wolf();

var myWerewolf = new Werewolf("Jack");
print((myWerewolf.__proto__ === Werewolf.prototype));
```
```
true
```

Thus, we can now dynamically reassign the Werewolf's parent:

```javascript
// First parent object
function Wolf() {
}

Wolf.prototype.speak = function() {
  return this.name + " growls...";
};

Wolf.prototype.bite = function() {
  return this.name + " has bitten you!";
};

// Second parent object
function Man() {
}

Man.prototype.speak = function() {
  return this.name + " speaks to you...";
};

Man.prototype.bite = function() {
  return this.name + " bit his tongue!";
};

// Child object
function Werewolf(name) {
  this.name = name;
}

Werewolf.prototype = new Man();

// Reassignment of the Werewolf's prototype
Werewolf.prototype.transform = function() {
  switch (this.__proto__.constructor.name) {
    case "Man":
      Wolf.call(this);
      this.__proto__.__proto__ = new Wolf();
      return this.name + " has transformed into a Wolf!";
      break;
    case "Wolf":
      Man.call(this);
      this.__proto__.__proto__ = new Man();
      return this.name + " has transformed into a Man!";
      break;
  }
}

var myWerewolf = new Werewolf("Jack");

print(myWerewolf.speak());
print(myWerewolf.bite());
print(myWerewolf.transform());

print(myWerewolf.speak());
print(myWerewolf.bite());
print(myWerewolf.transform());

print(myWerewolf.speak());
```
```
Jack speaks to you...
Jack bit his tongue!
Jack has transformed into a Wolf!
Jack growls...
Jack has bitten you!
Jack has transformed into a Man!
Jack speaks to you...
```

This dynamic inheritance example demonstrates the power of JavaScript.

### Closures

Closures are a very powerful feature of JavaScript, yet they remain relatively unknown or often misunderstood, leading to the inadvertent production of faulty code. In simple terms, it's the ability of an inner function to access properties of the enclosing function, even after the enclosing function has finished its execution.

This is made possible through two mechanisms implemented by JavaScript: execution contexts and scopes. With every function call, JavaScript creates a new execution context and associates it with a scope, consisting of a series of objects that determine how the function's variables are initialized. This is applicable to functions nested within other functions, which includes recursive functions. These objects carry the different contexts that led to the function's execution. The lowest execution context is that of the JavaScript script itself and is carried by the `Global` object.

#### Example №1

The concept of closures isn't easy to grasp in a theoretical sense, so let's illustrate it with a concrete example:

```javascript
01: var number1 = 7;
02: var number2 = -2;
03:
04: function Outer(num) {
05:   var factor = 3;
06:   this.Inner = function (coef) {
07:    return (num * factor * coef);
08:   };
09: }
10:
11: var MyObject1 = new Outer(number1);
12: print(MyObject1.Inner(10));
13:
14: var MyObject2 = new Outer(number2);
15: print(MyObject2.Inner(100));
```
```
210
-600
```

![Closure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5yo09v0p80jhoipgu5d.png)

Let's delve into the details of what JavaScript does when it parses the above code:
- **global context**:
  - **lines 1 and 2**: at the most basic execution context, JavaScript adds properties `number1` and `number2` with values 7 and -2 to the `Global` object, which holds the script's execution context.
  
- **Step 1**:
  - **line 11**: JavaScript creates a new object `MyObject1` by executing the `Outer` function as a constructor:
    - **line 4**: JavaScript establishes a new execution context for the `Outer` function and an object (called "Outer Call Scope" in the diagram) carrying the `num` parameter and function properties (including `factor`). The scope of this execution context consists of the "Outer Call Scope" object linked with `Global`.
    - **line 6**: JavaScript creates a new execution context for the `Inner` function and an object (called "Inner Call Scope" in the diagram) carrying the `coef` parameter. The scope of this execution context consists of the "Inner Call Scope" object linked with "Outer Call Scope" and then `Global`.
    - **line 8**: execution of the `Inner` function concludes, but its scope is retained in memory as it's referenced by another execution context (usage of `return`).
    - **line 9**: execution of the `Outer` function ends, its scope isn't referenced anymore and will be removed in the next garbage collector cycle. JavaScript returns to the global execution context.
  - **line 12**: the `Inner` method is called, JavaScript uses the retained context and scope to initialize variables and execute the function:
    - num equals 7;
    - factor equals 3;
    - coef equals 10;
    - Inner() returns 210.
    
- **Step 2**:
  - **line 14**: the previous process is performed again, leading to the creation of a new execution context and a new scope, different from those in Step 1.
  - **line 15**, in this new scope:
    - num equals -2;
    - factor equals 3;
    - coef equals 100;
    - Inner() returns -600 this time.

#### Example №2

Here's a second example of a closure used to implement a function of functions:

```javascript
function CreateDogAction(action) {
  return function() {
    return this.name + " is " + action;
  }
}

function Dog(name) {
  this.name = name;
  this.eat = CreateDogAction("eating");
  this.sleep = CreateDogAction("sleeping");
  this.bite = CreateDogAction("biting");
}

var myDog = new Dog("Rex");
print(myDog.eat());
print(myDog.sleep());
print(myDog.bite());
```
```
Rex is eating
Rex is sleeping
Rex is biting
```

#### Example #3

A closure can contain multiple functions accessing a common property, as illustrated by this counter management example:

```javascript
function initCounter(start) {
  // Private variable
  var counter = start;

  // Create the counter manipulation functions
  getCounter = function() {
    return counter;
  };

  incCounter = function() {
    counter++;
  };

  setCounter = function(value) {
    counter = value;
  };

  resetCounter = function() {
    counter = start;
  };
}

initCounter(0);
setCounter(10);
incCounter();
print(getCounter());

resetCounter();
print(getCounter());
```
```
11
0
```

## Conclusion

This article was written to shed light on some of JavaScript's features that often go unnoticed when the language is simply associated with dynamic web page features. However, delving deeper reveals that JavaScript is indeed a potent, ever-evolving language, underpinning the success of modern web applications.

The lack of knowledge regarding JavaScript's object features is likely tied to unfamiliarity with the prototype-based programming model. This is probably why ECMAScript version 6 also introduces the class-based programming model to the language. 

Was this truly a wise move? I'll leave that for you to decide. In my view, it certainly hasn't encouraged the widespread use of prototypes, which are, after all, at the very heart and strength of JavaScript...