Is there inheritance
or delegation?

Dominik Lubański

smalluban

SUBJECT

  • People learn frameworks, not JavaScript
     
  • Deep understanding of language itself
    is critical for being a master of developer tools
     
  • [[Prototype]] is the most common mechanism built-in JS, but it is not well understood

EXAMPLE #1

const parent = { a: 1 };
const child = Object.create(parent);
child.a++;
console.log(child.a, parent.a);
// 1, 1
console.log(child.a, parent.a);
// 2, 1

EXAMPLE #2

const parent = { a: { b: 1 } };
const child = Object.create(parent);
child.a.b++;
console.log(child.a.b, parent.a.b);
// 1, 1
console.log(child.a.b, parent.a.b);
// 2, 2

EXAMPLE #3

const parent = { 
  get a() { return parent._a || 1 }, 
  set a(val) { parent._a = val },
};
const child = Object.create(parent);
child.a++;
console.log(child.a, parent.a);
// 1, 1
console.log(child.a, parent.a);
// 2, 2

QUESTION

IS THERE INHERITANCE OR DELEGATION?

JavaScript is a bit confusing for developers experienced in class-based languages (like Java or C++), as it is dynamic and does not provide a class implementation per se.

MDN

JS creates a link between two objects, where one object can essentially delegate property/function access to another object.

  Kyle Simpson / You-Dont-Know-JS

"Delegation" is a much more accurate term for JavaScript's object-linking mechanism.

[[PROTOTYPE]]

Every object has an internal property called [[Prototype]], which links to other object or null:

const a = {};
// or
const a = Object.create(Object.prototype);

// a --> Object.prototype --> null

Object.getPrototypeOf(a)); 
// Object.prototype

a.__proto__; 
// Object.prototype

a.__proto__.__proto__;
// null

[[PROTOTYPE]]

[[GET]]

EXAMPLE #1

const parent = { a: 1 };
const child = Object.create(parent);

EXAMPLE #2

const parent = { a: { b: 1 } };
const child = Object.create(parent);
// child.a; 
child.[[GET]]('a', child);
// child.a.b
tempA = child.[[GET]]('a', child);
tempA.[[GET]]('b', tempA);

object.[[GET]](property, receiver)

  1. ​​Let desc be object.getOwnPropertyDescriptor(property)
  2. If desc is undefined:
    1. Let parent be Object.getPrototypeOf(object)
    2. If parent is null, return undefined.
    3. Return parent.[[GET](property, receiver)
  3. If desc is data descriptor return desc.value
  4. If desc is accessor return desc.get.call(receiver)

[[SET]]

EXAMPLE #1

const parent = { a: 1 };
const child = Object.create(parent);

EXAMPLE #2

const parent = { a: { b: 1 } };
const child = Object.create(parent);
// child.a++; 
tempA = child.[[GET]]('a', child);
child.[[SET]]('a', tempA + 1, child);
// child.a.b++
tempA = child.[[GET]]('a', child);
tempB = tempA.[[GET]]('b', tempA);
tempA.[[SET]]('b', tempB + 1, tempA);

object.[[SET]](property, value, receiver)

  1. ​​Let desc be object.getOwnPropertyDescriptor(property)
  2. If desc is undefined:
    1. Let parent be Object.getPrototypeOf(object)
    2. If parent is not null, return
      parent.[[SET](property, value, receiver)
    3. Else let desc be empty data descriptor
  3. If desc is data descriptor
    1. ​If desc.writable is false, return false
    2.  receiver.[[DefineOwnProperty]](property, value) or CreateDataProperty(receiver, property, value)
  4. If desc is accessor return desc.set.call(receiver, value)

simplified!

Constructor

function Library() {
  this.value = 1;
};
Library.prototype.getValue = function getValue() {
  return this.value;
};
const library = new Library();
library.getValue(); // 1

Many libraries uses constructor pattern:

Object.getPrototypeOf(library) === Library.prototype;
// library 
//   -> Library.prototype -> Object.prototype -> null;

Constructor

function Library() {};

typeof Library.prototype; 
// "object"

Object.getPrototypeOf(Library.prototype); 
// Object.prototype

Library.prototype.constructor === Library; 
// true

Library.prototype is special a property, which reference to object used as a prototype of constructor instance:

Constructor

function Library() {};

typeof Library === 'function'; // true
Library instanceof Object; // true

Function has own type, but it is still an ordinary object:

// Prototype chain:
// Library -> Function.prototype -> Object.prototype

Object.getPrototypeOf(Library); 
// Function.prototype

Object.getPrototypeOf(Function.prototype); 
// Object.prototype

Constructor

// when calling with 'new'
function Library() {
  this = Object.create(Library.prototype);
  this.value = 1;

  return this;
};
function Library() {
  this.value = 1;
};

new operator calls function with special behavior:

Constructor

Constructor

  • With constructor pattern logic part can be easily shared between instances.
     
  • Sharing references to functions is safe as they are called with proper context
     
  • Sharing data values and accessor can cause unexpected result

Constructor

function Library() { // ... };
Library.prototype.options = { value: 1 };

const a = new Library();
const b = new Library();

// User assumes that he changes only own options
a.options.value++;  

a.options.value === b.options.value;
// true

Sharing reference to object in prototype:

CLASS SYNTAX

...the class keyword is introduced in ES6, but is syntactical sugar, JavaScript remaining
prototype-based

MDN

CLASS SYNTAX

class Library {
  constructor() {
    this.value = 1;
  }

  getValue() {
    return this.value;
  }
}
function Library() {
  this.value = 1;
};
Library.prototype.getValue = 
  function () {
    return this.value;
  };

class syntax still uses prototype:

typeof Library; // "function"
typeof Library.prototype.getValue; // "function"

CLASS SYNTAX

  • Simpler syntax than constructor pattern
     
  • Prevent anti-patterns like sharing data values - only functions in prototype
     
  • Constructor throws error when called without new operator
     
  • extends pattern built into semantics

HOMEWORK

THANK YOU!

Is there inheritance or delegation?

By Dominik Lubański

Is there inheritance or delegation?

In our daily work we often use prototype mechanism built-in JavaScript. But are you sure you understand it well? Let's find out!

  • 1,661