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!

Made with Slides.com