DEV Community

Cover image for Advanced js: Prototype vs. Class
Manuel Artero Anguita 🟨
Manuel Artero Anguita 🟨

Posted on • Updated on

Advanced js: Prototype vs. Class

Usually we trend to think in terms of classes & instances.

Let's do a quick review to prototype and class theory.

We use regular functions to create the illusion of classes

function foo() {
  console.log('this is just a regular function');
  this.foo = 'foo';
} // « undefined

const f = new foo(); // « Object { foo: 'foo' }
// this is just a regular function

f.foo; // « 'foo'
Enter fullscreen mode Exit fullscreen mode

Then, the way of creating class methods is adding a property to foo's prototype

foo.prototype.sayHi = () => {
  console.log('hi');
  return 1;
}; // « function sayHi()

f; // « Object { foo: 'foo' }

f.sayHi(); // « 1
// hi
Enter fullscreen mode Exit fullscreen mode

This is so standard that ES5 (and ts before them) introduced syntactic sugar:

class goo {
  constructor() {
    this.goo = 'goo';
  }
  sayHi() {
    console.log('hi');
    return 1;
  }
} // « undefined

g = new goo(); // « Object { goo: "goo" }
g.sayHi(); // « 1
// hi
Enter fullscreen mode Exit fullscreen mode

The truth is...

There are no classes. Neither instances. While in class-oriented languages, multiple copies (instances) of a class can be made, like stamping or copying behavior out from a mold. In js we have just regular friendly objects.

...js is prototype-oriented.

What we're truly doing on the examples above is creating a new object and linking them to another plain, regular object.

This linked object is the prototype.

We are linking each new object to the prototype of the function we've called.

There are plain js-objects in memory; no more, no less.

function foo() {
  this.foo = 'foo';
}
foo.prototype.sayHi = () => console.log('say hi');

f = new foo();

/*

 -------------  linked to   -----------------  linked to  ------------------  linked to
| foo: string | ---------> | sayHi: Function | ---------> | { toString... } | ---------> null
 -------------              -----------------             ------------------
  ^                           ^                              ^
  f                          foo.prototype                 Object.prototype

*/
Enter fullscreen mode Exit fullscreen mode
foo.prototype; // Object { sayHi: Function }
Object.getPrototypeOf(f) == foo.prototype; // « true

Object.prototype; // « Object { ... }
Object.getPrototypeOf(foo.prototype) == Object.prototype; // « true

Object.getPrototypeOf(Object.prototype); // « null
Enter fullscreen mode Exit fullscreen mode

Thing is, IMO this is even more powerful (and elegant) than classic instances. Since everything are in-memory objects, we could even modify the thing in execution time:

function player() {}
player.prototype.sayHi = () => console.log('hi');

a = new player();
b = new player();
a.sayHi(); // « 'hi'
b.sayHi(); // « 'hi'

// we can modify the behaviour in execution time

player.prototype.sayHi = () => console.log('hello');

a.sayHi(); // « 'hello'
b.sayHi(); // « 'hello'
Enter fullscreen mode Exit fullscreen mode

Applying the theory

With this in mind. We (our team) challenge to stop thinking in terms of classes & instances, and start thinking on prototype objects.

Imagine you need two objects, o1 and o2, each one overriding some default behavior/values. This is how we'd code that:

const defaultBehaviour = {
  name: 'guest',

  sayHi() {
    return 'hi ' + this.name;
  },

  foo() {
    return 42;
  },
};

const o1 =  Object.setPrototypeOf({ name: 'Jane Doe' }, defaultBehaviour);

o1.sayHi(); // « hi Jane Doe
o1.foo(); // « 42

const o2 = Object.setPrototypeOf({ foo: () => 100 }, defaultBehaviour);

o2.sayHi(); // « hi guest
o2.foo(); // « 100
Enter fullscreen mode Exit fullscreen mode

While the class sugar-syntax is perfectly valid. Let's remove the mask and claim the true behavior of js 🦾

--

I can't recommend hard enough to read Kyle Simpson.

--

Cover image from undraw.co

Thanks for reading 💚.

Top comments (0)