Object orientation with prototypes

Photo by Sigmund on Unsplash

Object orientation with prototypes

Proto_types in JS for season developers

ยท

9 min read

A prototype is an object to which the search for a particular property can be delegated. Prototypes are a convenient means of defining properties and functionality that will be automatically accessible to other objects. Prototypes serve a similar purpose to that of classes in classical object-oriented languages. Indeed, the main use of prototypes in JavaScript is in producing code written in an object-oriented way, similar to, but not exactly like, code in more conventional, class-based languages such as Java or C#.

More importantly, we'll discover why it's important to having prototypes in JS land

Generate Object using functions

In JavaScript, objects are collections of named properties with values. For example, we can easily create new objects with object-literal notation

const obj = {
    prop1 : 'a',
    prop2 : ()=> {},
    prop3 : {}
}

When developing software, we strive not to reinvent the wheel, so we want to reuse as much code as possible

function userCreation(name,score) {
  const newUser = {};
  newUser.name = name;
  newUser.score = score;
  newUser.increment = ()=> {
    newUser.score++;
  }
  return newUser;
}
๐Ÿ’ก
We do two good things: we avoid creating a repeat piece of code to manually assign variables to an object, and we bundle data and functions together in one place. So we can use it all together

The problem is that every time the user calls this function to create a JS object, it will create the same objects that take up a computer's memory to store. Technically,only the data is dynamic, but the function will remain the same no matter how many times we create an object. Can we do it better?

What if I told you that we could save up some memories by putting all functions on the upper chain and sharing them all with the newly created object?

The power of Object.create

Object.create is used for creating objects with a specified prototype. This allows you to create objects that inherit properties and methods from another object (the prototype).

const functionStore = {
   increment:function() { this.score++; },
   login:function() { console.log("you log in"); }
}

function userCreation(name,score) {
  const newUser = Object.create(functionStore);
  newUser.name = name;
  newUser.score = score;
  return newUser;
}

๐Ÿ’ก
[[Prototype]] is a special property that links to prototype property that contains a bunch of re-usable functions

Let's have a look at this keyword again.

// [[Prototype]]:Object
const functionStore = {
   increment:function() { this.score++; },
   login:function() { console.log("you log in"); }
}

This keyword is a very flexible word in JS that depends on the object calling a function, In our case, we group our store function object in higher up the chain and we might not know what the value of the object that executes our function is, but with this, it replies on the object calling our function

newUser.increment;
// It will increase property score of the newUser to 1;

This,call,apply and bind with fns in JS

You might have noticed that putting the share common function in the upper chain is a great way to cut down on the computer's memory resource, but you should also think about how it knows the context of the value is calling to the function. Obviously, there could be a thousand of objects sharing the same function together so this keyword is the way JS offers us to solve this issue

Understand the context and this

const myTruck = {
    speed:50
}
function drive() {
   console.log(`Driving at ${this.speed}`);
}

Binding the current implementation of a function to the current context invokes function

//Bind first and execute later
const boundDrive = drive.bind(myTruck);
boundDrive();
// Driving at 50
drive.call(myTruck);
// Driving at 50;
drive.apply(myTruck);
// Driving at 50

Difference between bind,call and apply

  • bind creates a new function with a specified "this" value

  • call invokes a function immediately with a specified "this" value and individual arguments

  • apply invokes a function immediately with "this" value and an array of arguments

๐Ÿ’ก
When we write the function using this keyword,. We imply that in the feature that someone can borrow this function from us :)
const person = {
  name: "John",
  sayHello: function(greeting) {
    console.log(`${greeting}, ${this.name}!`);
  }
};

const anotherPerson = {
  name: "Jane"
};

// Using bind
const greetJohn = person.sayHello.bind(person, "Hello");
greetJohn(); // Outputs: Hello, John!

// Using call
person.sayHello.call(anotherPerson, "Hi"); // Outputs: Hi, Jane!

// Using apply
person.sayHello.apply(anotherPerson, ["Hola"]); // Outputs: Hola, Jane!

Borrow Array.prototype.map to use with string

Array.prototype.map = function(callback, thisArg) {
    const newArray = [];
    //Whoever calls me must have a length property
    for (let i = 0; i < this.length; i++) {
        if (this.hasOwnProperty(i)) {
            newArray[i] = callback.call(thisArg, this[i], i, this);
        }
    }

    return newArray;
};

// Example to use myMap with a string
function splitter(string) {
  return Array.prototype.map.call(string, (x) => x);
}

console.log(splitter('Hello World'));
๐Ÿ’ก
Most of the utilities we found in JS (array, object, JSON , etc) will respond heavily to the "this" mechanism

Why can we call the bind,call, apply on function?

It turns out that everything in JS is an object. Keep in mind that when we create a function in JS, it has a special power that lets us do something significant.

๐Ÿ’ก
Is it weird that we can invoke a property on a function in Javascript?

Prototype in JS

We can simplify the Object.create function with prototype in JS and it will work the same

function UserCreator(name,score) {
    this.name = name;
    this.score = score;
}
// we create an empty object and assign it value with name and score


UserCreator.prototype.increment = function () {
    this.score++;
}

UserCreator.prototype.login = function () {
    console.log("login");
}

const user1 = new UserCreator("Vince",100);
๐Ÿ’ก
In JS , we can execute a function by doing (); and we can even create an object with function with the new keyword

Note that the prototype property also exists in an array. Think back every time we use array methods like push(), join(), etc. All these built-in array-related functions are actually stored inside an array at Array.prototype

The new keyword

If you notice that we don't have to manually create an object and return an object. The new keyword when calling a function will automatically create an object and return an object for us

The class 'syntatic sugar'

With that solution using the prototype keyword, it's very implicit, but it exposes so many low-level details to the users. We want some magic to somehow replicate the traditional language like Java and C++ do. So welcome to the syntatic sugar class keyword of javascript

class UserCreator {
    constructor(name,score) {
        this.name = name;
        this.score = score;
    }
    increment() {
        this.score++;
    }
    login() {
        console.log("login");
    }
}
๐Ÿ’ก
This is still working the same behind the hood but is more elegant visually

The static keyword

In the previous examples, you saw how to define object methods (prototype methods), accessible to all object instances. In addition to such methods, classical object-oriented languages such as Java and C# use static methods, which are defined at the class level. Check out the following example:

๐Ÿ’ก
Static functions doesn't based on context
class Ninja{
  constructor(name, level){
    this.name = name;
    this.level = level;
  }

  swingSword() {
    return true;
  }

  static compare(ninja1, ninja2){
    return ninja1.level - ninja2.level;
  }
}
// How to use it
Ninja.compare(new Ninja("Vince",100), new Ninja("MA",1000) );
// Static method is used at the class level

Behind the scene, it's also another syntatic sugar for class-based behaviors

function Ninja(name,level) { };
Ninja.compare = (ninja1,ninja2)=> { }

There're a lot of example out there using static methods (utils for one-time use)

  • Object.keys, Object.values, Object.freeze, Object.defineProperty,etc

  • Array.isArray,Array.from

Inheritance in JS

class Person {
   constructor(name) {
      this.name=name;
   }
   dance() {
    return true;
   }
}

class Ninja extends Person {
   constructor(name,weapon) {
       super(name);
       this.weapon=weapon;
    }
    wieldWeapon() {
       return true;
    }
}

const ninja = new Ninja("Bob","knife");

//Let me prove to you classes is just a prototype in JS
Person.prototype.isPrototypeOf(ninja);

Behind the scene

// Equivalent ES5 code
function Person(name) {
   this.name = name;
}

Person.prototype.dance = function () {
   return true;
};

//Assign properties
function Ninja(name, weapon) {
   Person.call(this, name);
   this.weapon = weapon;
}

//Link Ninja prototype to Person prototype, otherwise it doesn't work
Ninja.prototype = Object.create(Person.prototype);


Ninja.prototype.constructor = Ninja;
Ninja.prototype.wieldWeapon = function () {
   return true;
};

Encapsulation

class Mammal {
    constructor(sound) {
      this._sound=sound;
   }
   talk() {
     return this._sound;
   }
}

class Dog extends Malmal {
    constructor(sound) {
        super(sound);
    }
}

const funky = new Dog("WOOO");
funky._sound; // we can still access this
๐Ÿ’ก
As we already know, JS doesn't have class. JS fakes classes so there's no such things as a private field in JS :(

TS stories

Typescript adds public, protected and private field modifiers that seem to provide some enforcement

class Diary {
  private secret = 'cheated on my English test';
}

const diary = new Diary();
diary.secret;
// Property secret is private and only accessible within class 'Diary'
๐Ÿ’ก
Note to take here: private is a feature of TS and it will go away at run time

Don't reply on private to hide information since it's only there to prevent users from accessing it but users can choose not to obey the rules

(diary as any).secret;

Inheritance in real-life

At this time, we already know the value of inheritance system, so let's inherit some common methods that we don't have to write from scratch

  • Array methods

  • Object methods

  • EventTarget Interface

  • Event interface

  • And so on. Even we can build our own class with our business logic and let others inherit from it

๐Ÿ’ก
Inheritance is nice, but it does come with a price. For example, in the case of an object vs. a map data structure in Javascript,. If we want to create a truly dictionary object without any irrelevant methods, I suggest using a map data structure instead

DOM with the inhertance concept?

Keep in mind that these are the properties values

Its prototype

Event with inheritance concept?

Its prototype

Inheritance vs composition

//Instead of building from prototype chain , we seperate our 
//common functions
const barker = (state)=> ({
    //Return a new object with bark method
    bark:()=> console.log("Woof, I'm" + state.name);
})

const driver = (state)=> ({
    //Return a new object with drive method
    drive:()=> state.position = state.position + state.speed
})

//Instead of forcing users to inherit these fns we give them
//the ability to compose it

const muderRobotDog = ()=>{
    let state = {
        name,
        speed,
        position:0
     };
    return Object.assign({} , barker(state) , driver(state) );
}

const murderRobot = murderRobotDog();
murderRobot.bark();
murderRobot.drive();
๐Ÿ’ก
Take a moment to think about the advantage of a prototype versus allowing users to freely combine our common functions.
ย