Object Oriented Programming in JavaScript

Object Oriented Programming in JavaScript

There are certain features or mechanisms which make a Language Object-Oriented in JavaScript like: Object Classes Encapsulation Inheritance Let’s dive into the details. JavaScript first appeared in 1996 in the Netscape browser. Faced with its success, Microsoft released a similar implementation in its IE3. We already have two browsers and two different versions of the language… you know the rest.

If JavaScript is one of the most used languages, it is therefore thanks to the web. Indeed, all terminals that have a browser are able to interpret JS. This specificity of the language makes it essential for anyone who wants to program for the web.

Many developers have thus taken to JS for lack of an alternative, without taking the trouble or the time to really understand the language. All this has led us to codes that are sometimes a bit filthy and practices that are not always brilliant. Added to this are interpreters as varied as there are browsers and OSes in nature, and we end up with a language with a sulfurous reputation.

In recent years, there have been major standardization efforts, particularly on the part of ECMA, the organization responsible for writing the JS specifications, but also on the part of browser publishers. The five main browsers, namely Firefox, Chrome, IE/Edge, Safari, and Opera respect the specification rather well in their latest versions.

Thus, the improvements made to the language, the better support for standards in browsers, and the appearance of Node.js, which makes it possible to execute JS on the server, allow JavaScript to acquire its letters of nobility as a language. programming.

Everything is object

In JS everything is an object. All types inherit from the Object type. Thus, whether strings, arrays, or bools, almost everything is an object.

Well, I'm not going to lie to you, the reality is a little bit more complex than that. In JS, there are objects and primitives. A primitive is simply data that is not an object and has no method.

There are five of them and most of them you probably know: are string, number, boolean, null, undefined, and symbol. You may not know this, but there are two ways to create each of these types: through its primitive type, as you do 99% of the time, and through its constructor. Example :

const primitive = 'I am a string';
const stringObj = new String('I am a string, a string object!');

Where you might think I'm kidding is that earlier I defined primitives as having no methods. However, when we do this:

const primitive = 'I am a string';
console.log(primitive.toUpperCase()); // "I AM A STRING"

We invoke the toUpperCase() method and it works, yet our type is primitive... Yes, it's because JavaScript automatically performs the conversion between the primitive and the String object. The string is temporarily transformed into a String object while processing, then it is destroyed. This obviously applies to other types.

To illustrate this difference, let's do a little experiment:

const primitive = 'one';
const objectString = new String('one');

console.log(typeof primitive); // "string"
console.log(typeof objectString); // "object"

if (primitive === objectString) {
     console.log('==='); // display nothing
}

if (primitive == objectString) {
     console.log('=='); // "=="
}

// primitives are evaluated as source code
const prim='2*6';
console.log(eval(prim)); // returns the number 12

// objects as strings
const obj = newString('2*6');
console.log(eval(obj)); // returns the string "6 * 6"

In use, there are relatively few differences and we tend to use primitive types because they are more concise. However, the differences are worth knowing because they can create unexpected behaviors.

Objects, properties, and attributes

In JS, an object contains properties, so far so good. However, it is less known, that each property has attributes. Its value of course, but also other properties that give it a particular behavior. Let's watch this.

Attributes: Type Description [[Value]]: Any JavaScript value The value is obtained when the property is accessed. [[Writable]]: Boolean If false, the value of the property (the [[Value]] attribute) cannot be changed. [[Enumerable]]: Boolean If true, the property can be listed by a for…in a loop. See also the article on the enumerable character of properties. [[Configurable]]: Booléen If false, the property cannot be deleted, and attributes other than [[Value]] and [[Writable]] cannot be modified.

We rarely have to modify these properties - that's why they are not well known - but when we have to do so, we will use Object. define properties.

const min = {
     model: 'mini',
     make: 'bmw',
     hp: 120,
     color: 'blue',
     tires: '17"'
};

Object.defineProperties(mini, {
     pattern: {
         enumerable: false
     },
     hp: {
         writeable: false
     }
});

// list all properties except "model"
for (let prop in mini) {
     console.log(prop);
}

min.hp = 200; // we try to modify "hp"
console.log(mini.hp); // 120 … hp is not editable

Getters and Setters

Also, Getters and setters are somewhat special attributes. They are used to access or set the value of a property. Very often, we use classic functions like getters or setters, but there are many properties dedicated to this in JS objects.

const min = {
     model: 'mini',
     make: 'bmw',
     hp: 120,
     color: 'blue',
     tires: '17"',

     getcolor() {
         // here we use the ES6 syntax for template literals
         return `${this.model}'s color is ${this.color}`;
     },

     set paint(newColor) {
         this.color = newColor;
     }
};

console.log(mini.color); // "blue"

// notice that we don't call it like a function!
mini.paint = 'red';
console.log(mini.color); // "red"

The setters and getters themselves have attributes, two to be exact, are [[Enumerable]] and [[Configurable]], we configure them exactly the same way as for the other attributes, with Object. define properties.

Proto-what?

As many developers do not take the time to understand JS, certain concepts escape them. For someone who already knows how to program, it is easy to have basic use of the language and to achieve one's ends without understanding its true nature.

JavaScript is an object-oriented prototype language. Well, what is that you ask me? No need for me to try to lay out my own definition, that of Wikipedia seems very clear to me.

Prototype-oriented programming is a classless form of object-oriented programming, based on the notion of prototype. A prototype is an object from which new objects are created.

I still pump on Wikipedia, but the article presents two lists that highlight the differences between the two types of inheritance.

Objects with classes:

  1. A class defined by its source code is static;
  2. It represents an abstract definition of the object;
  3. Every object is an instance of a class;
  4. Inheritance is at the class level.

Objects to prototypes:

  1. A prototype defined by its source code is mutable;
  2. It is itself an object in the same way as the others;
  3. It, therefore, has a physical existence in memory;
  4. It can be modified, called;
  5. It is obligatorily named;
  6. A prototype can be seen as a model copy of a family of objects;
  7. An object inherits properties (values ​​and methods) from its prototype;

I feel that this concept of prototype remains vague, let's try to clarify your ideas. In JS, each object has a link to another object: its prototype, itself also has a link to its prototype, and so on until a prototype is no longer an object but null.

Thus, when we want to access a property of an object, JavaScript first looks at the object itself, then if it finds nothing, looks in its prototype, and so on until the beginning of the chain. Let's illustrate this by example.

// we create a literal object
const o = { a: 'b', b: 'c' };

// we add a property to its prototype
o.__proto__ = { d: 'e' };

// we call this property from the object
console.log(o.d); // "e"

// check own properties
console.log(o.hasOwnProperty('a')); // true
console.log(o.hasOwnProperty('d')); // false

// we display the object in the devtools (screen below)
console.log(o);

We realize here that the object has two properties and that it will look for the property "d" in its prototype when it is asked to access it.

The prototype chain here looks like this:

{ a: 'b', b: 'c' } --> { d: 'e' } --> Object.prototype --> null

It is because we have the prototype of Object in the prototype chain that we are able to call methods like hasOwnProperty that we have not explicitly defined.

Also, note that in the example we are using proto as a setter, which can significantly impact performance. As far as possible, we will prefer to use other methods, we will see later how to define the prototype.

A bit of vocabulary

Before going into the details of creating objects and discussing different patterns, let's clarify the vocabulary a bit. The belonging of certain words to languages ​​with different object paradigms, combined with the use of certain terms incorrectly, is responsible for a large part of the misunderstanding of the JavaScript object model.

OOP

Let's start with the beginning. The term object-oriented programming is shared by the JS, based on objects whose inheritance is prototypal, and by the “classic” OO languages, based on the classes.

To clearly mark the difference between the object model of JS and that of other languages ​​such as Java, we could try to redefine the terms as Kyle Simpson does in his series on OOJS [en].

Thus, JavaScript would deserve the term object-oriented because the language is object-based in its purest form. While the so-called “classic” object languages ​​are rather class-based – insofar as it is not possible to create an object without going through a class – it is therefore class-oriented programming.

Legacy

The other central notion is that of inheritance. While in classic OOP, when you instantiate a class, the object thus created contains all the properties and methods of this class and possibly of its parent classes, it is quite different in JS.

In POOP the concept is clearer if we talk about delegation rather than inheritance (this concept is explained in depth in another article [in] of K. Simpson's series). The object does not inherit properties from other objects – we don't really have classes in JS, we'll come back to this – in the sense that it doesn't contain a copy of them but a link to another object: its prototype.

The idea of ​​delegation takes on its full meaning when we understand that our object delegates to the prototype the responsibility of finding a property or a method if it does not have it itself.

Method

The term method is often mentioned, but JS does not have a method in the classic OO sense. A method in OOJS is simply a function attached to an object as a property. It obeys the same rules as any other property, it “inherits” the same way, the only difference is that it can be called.

It is worth noting that when a function contained in the prototype is executed, this refers to the object from which the function is called and not to the prototype.

Class

JS has no classes per se, there are no class implementations in the languages. Everything is an object and inheritance is entirely based on prototypes. ES6 introduces a few additional keywords to facilitate work in OO, in particular the keyword class, but this is only syntactic sugar and the internal functioning remains unchanged.

Instance

If the JS has no classes, we are entitled to wonder if it has instances. In a classic OOP, an instance is an object from a class. As often in JS, we will use the words usually used in POO, so we will talk about instances. However, an instance is nothing but an object that inherits the prototype from its constructor.

Global Objects

JavaScript has a number of native objects. This is the case of the String object which we talked about above, but also of Object, Math, etc. You can find the complete list of global objects on this DND page.

It is thanks to these predefined objects that we can invoke methods without having to define them beforehand. They are defined in the prototype of the objects we create.

Let's look at the prototype chain of the most common objects.

const text = 'string your mother!';
// text --> String.prototype --> Object.prototype --> null

const num = 42;
// num --> Number.prototype --> Object.prototype --> null

const object = { test: 1 };
// object --> Object.prototype --> null

const array = ['test'];
// array --> Array.prototype --> Object.prototype --> null

function fn() {
     return 'osef';
}
// fn --> Function. prototype --> Object. prototype --> null

It is easy to understand that the native objects that we create inherit specific properties from their parent objects, then from Object. This is why it is possible to call toUpperCase() on a string but not on a function or on a number. This method is part of the prototype of String and not of Object.

Create objects

Let's talk about serious things! We already know how to create objects with the literal syntax, and we have also seen that it is easy to instantiate native objects with the new keyword. So let's see how to create our own objects.

There are three ways to create objects in JS. As we have seen, the language does not have classes, so when we talk about constructors, we are talking about functions (themselves objects) that act as constructors.

The first two methods are already familiar to you, they are the creation of literal objects with {} and the instantiation of objects with the new keyword, we will see later how to create our constructors.

By the way, do you know what happens when you use the new key? Three things:

  1. A new object is created which inherits from Toto. prototype.

  2. The Toto constructor function is called with the supplied arguments, this being bound to the newly created object. new Foo will be equivalent to new Foo() (i.e. a call with no arguments).

  3. The object returned by the constructor becomes the result of the expression that contains new. If the constructor does not explicitly return an object, the object created in step 1 will be used. (In general, constructors do not return values, but if you want to override the usual process, you can use this return value).

Finally, the third method appeared in version 5 of the ECMAScript, it is Object. create. The particularity of this method is that it allows you to create a new object that “inherits” the object passed as a parameter; without using new, or employing a complex pattern.

Obviously, if inherits is in quotes, it is because in reality, the create method adds the object passed as a parameter to the prototype of the new object.

Now let's see the most common patterns.

Constructor pattern
function Vehicule() {}

const voiture = new Vehicule();

Our object does nothing, but if we inspect it in the dev tools, we realize that its prototype is Vehicle. More precisely, its prototype is of type Object, this object has a constructor property: Vehicle.

car --> Object --> Object prototype
                |
           builder
                |--> car.__proto__.constructor === Vehicle

Explanations. Objects have a standard constructor property that references the function (in JS constructors are functions). When a function is declared, the interpreter creates the new function and its prototype.

Subsequently, when we create a new object from our constructor using the new keyword, the object thus created also contains a prototype with a constructor property. The latter references the function that served as a constructor, itself containing the proto of the constructor (here Vehicle having Function as a prototype).

Until then, the object created is of little interest. Let's add some details.

function Car (make, model, color, power, tires) {
     const horsePower = power;
     this. make = make;
     this. model = model;
     this.color = color;
     this.tires = tires;

     this. getHP = function () {
         return horsePower;
     };
}

const miniCooperS = new Car('mini', 'cooper s', 'pink', 180, '17"');

console.log(miniCooperS.color); // "pink"
console.log(miniCooperS.getHP()); // 180
console.log(miniCooperS.horsePower); // undefined

Conclusion

There would still be a lot to say in OOP is a vast subject. But still, we went through the main points, you should be very comfortable with the concepts of OOJS if you have followed and understood them well. You will be able to use ES6 sugar while understanding the implicit functioning of the JS engine.