JavaScript

Prototypes

Co-created the Self programming language with Randall Smith.

function Person() {
}

const p = new Person()

Object.getPrototypeOf(p)
Object.getPrototypeOf(Person)
Object.getPrototypeOf(Object)
Object.getPrototypeOf(Function.prototype)
Object.getPrototypeOf(Object.prototype)
Object.getPrototypeOf(Function)

What's the output?

 JavaScript is a prototypal language

  • Classes are not present
  • Not traditionally Object Oriented
  • Everything is public*
  • Objects inherits from another objects

*There is a new feature in the latest JS version where properties can be private.

What is a Prototype?

Every object is born referencing to a prototype object(parent) by a secret property [[Prototype]] or __proto__.

There are two prototypes in JS

  • Prototype Object on Functions
  • Object prototypes (aka prototypes of objects)

Even when you don't specify the prototype, a default prototype is set for every object.

const obj = {};

console.log(obj.toString) 
// [Function: toString]

A prototype is a working object instance.

  • Prototype-based programming is a style of object-oriented programming in which behaviour reuse (aka inheritance) is performed via a process of reusing existing objects via delegation that serve as prototypes.
  • This model can also be known as prototypal, prototype-oriented, classless, or instance-based programming.
  • Delegation is the language feature that supports prototype-based programming.

Prototype-based programming

Prototype Chains

  • The prototype relationship between two objects is about inheritance.

  • An object specifies its prototype via the internal property [[Prototype]]

  • Every object has this property, but it can be null.

  • The chain of objects connected by the [[Prototype]] property is called the prototype chain.

Reading a property

(Rough) Algorithm —

  • Let CurrentObject = obj. Let CurrentKeyName = middleName.
  • If CurrentObject has CurrentKeyName, return associated value.
  • Else, let CurrentObject = parent of CurrentObject.
  • Check for CurrentKeyName again in CurrentObject. If found, return value.
  • If CurrentObject has no parent and does not have CurrentKeyName, return undefined.

Goal: Read middleName from the object obj. (Note that middleName does not exist in obj )

Create, Update, Delete a property

  • When you do changes to a property of an object, it always affect the current object only.
    • If the property doesn’t exist, property is added to the object.
  • It won’t look up to the prototype chain.

Accessing the Prototype

Object.getPrototypeOf

const prototype1 = {};
const object1 = Object.create(prototype1);

Object.getPrototypeOf(object1) === prototype1;
// expected output: true

Reflect.getPrototypeOf(target)

This method is almost the same method as Object.getPrototypeOf

__proto__

Object#isPrototypeOf()

parent.isPrototypeOf(obj)
obj.__proto__

Modifying existing prototypes

Object.setPrototypeOf()

Object.setPrototypeOf(obj, prototype)

The Object.setPrototypeOf() method sets the prototype (i.e., the internal [[Prototype]] property) of a specified object to another object or null.

__proto__

const shape = {};
const circle = {}

// Set the object prototype.
// DEPRECATED. This is for example purposes only. 
// DO NOT DO THIS in real code.
shape.__proto__ = circle;

Creating an object with a new prototype

var proto = {
    describe: function () {
        return 'name: '+this.name;
    }
};

var obj = Object.create(proto);
obj.name = 'obj';

obj.describe();
  • Whenever you access a property via obj, JavaScript starts the search for it in that object and continues with its prototype, the prototype’s prototype, and so on.

  • a property in an object overrides a property with the same key in a “later” object: the former property is found first.

Object.create

Constructor Functions

When a function is used as a constructor (with the new keyword), its this is bound to the new object being constructed.

function C() {
  this.a = 37;
}

var o = new C();
console.log(o.a); // 37


function C2() {
  this.a = 37;
  return {a: 38};
}

o = new C2();
console.log(o.a); // 38

When a function is executed with the new keyword, it does the following steps:

  • A new empty object is created and assigned to this.
  • The prototype of the this object is changed <FnName>.prototype
  • The function body executes. Usually it modifies this, adds new properties to it.
  • The value of this is returned.
    • You can override the return value by returning another object
function Person(name, age) {
  this.age = age;
  this.name = name;
}
function Person(name, age) {
  // this = {}
  this.age = age;
  this.name = name;
}
function Person(name, age) {
  // this = {}
  // Object.setPrototypeOf(this, Person.prototype)
  this.age = age;
  this.name = name;
}
function Person(name, age) {
  // this = {}
  // Object.setPrototypeOf(this, Person.prototype)
  this.age = age;
  this.name = name;
  /*
  let userHasWrittenReturn = false;

  if (userHasWrittenReturn) {
    let returnValue be user's return value;
    if (returnValue is object) {
      return returnValue;
    }
    return this;
  }
  return this;
  */
}
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const p1 = new Person('A', 25);
const p2 = new Person('B', 32);

A constructor function should be called with new

new.target (pseudo property)

use strict

instanceof

  •  In constructors and functions invoked using the new operator, new.target returns a reference to the constructor or function.
  • In normal function calls, new.target is undefined.
  if (!(this instanceof Person)) {
    throw new Error('should be called with new');
  }

The instanceof operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object.

Use ES6 classes

Psuedoclassical Inheritance

  • The Pseudoclassical pattern tries to replicate inheritance in a way that is familiar to those who come from a Java or C like background.
  • By using Pseudoclassical inheritance, we attempt to recreate classic programming language’s behavior by using class wide inheritance and where objects are instances of those classes.

A pattern that uses a constructor function and the new operator, combined with a prototype added to the constructor is said to be Pseudoclassical.

  •  Invoke a constructor function.
  •  Point a child’s prototype to the parent’s prototype for inheritance to occur.

ES6 Classes

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
function Rectangle(height, width) {
  this.height = height;
  this.width = width;
}
  • ES6 classes are "special functions".
  • They can be thought of as syntactic sugar to constructor functions.*

Distinguishing .prototype and [[Prototype]]

  • [[Prototype]] is an internal property present on all objects.
    • It is a special property.
    • It's value may either be another object's reference or null.
  • .prototype is an object
    • It is present only on functions (defined using the `function` keyword)
    • It contains (generally) values and methods shared by all objects of that "class"
    • It is not special in the sense of [[Prototype]]

Prototypal Inheritance

function makePerson(name, age) {
  return {
    name,
    age
  }
}

const p1 = makePerson('A', 25);
const p2 = makePerson('B', 32);

Prototypal Inheritance

function makePerson(name, age) {
  return {
    name,
    age,
    nameInUpper() {
      return this.name.toUpperCase();
    },
  };
}

const p1 = makePerson('a', 25);
const p2 = makePerson('b', 32);

p1.nameInUpper();

Prototypal Inheritance

function nameInUpper() {
  return this.name.toUpperCase();
}

function makePerson(name, age) {
  return {
    name,
    age,
  };
}

const p1 = makePerson('a', 25);
const p2 = makePerson('b', 32);

console.log(nameInUpper.call(p1));

Prototypal Inheritance

const PersonSharedMethods = {
  nameInUpper() {
    return this.name.toUpperCase();
  },
};

function makePerson(name, age) {
  return {
    name,
    age,
    nameInUpper: PersonSharedMethods.nameInUpper,
  };
}

const p1 = makePerson('a', 25);
const p2 = makePerson('b', 32);

p1.nameInUpper();

Prototypal Inheritance

function makePerson(name, age) {
  const newPerson = Object.create(makePerson.sharedMethods);
  newPerson.name = name;
  newPerson.age = age;
  return newPerson;
}

makePerson.sharedMethods = {
  nameInUpper() {
    return this.name.toUpperCase();
  },
};

const p1 = makePerson('a', 25);
const p2 = makePerson('b', 32);

console.log(p1.nameInUpper());

Prototypal Inheritance

function makePerson(name, age) {
  const newPerson = Object.create(makePerson.prototype);
  newPerson.name = name;
  newPerson.age = age;
  return newPerson;
}

makePerson.prototype = {
  nameInUpper() {
    return this.name.toUpperCase();
  },
};

const p1 = makePerson('a', 25);
const p2 = makePerson('b', 32);

console.log(p1.nameInUpper());
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const p1 = new Person('A', 25);
const p2 = new Person('B', 32);

 Two important rules 

An object (by default) always inherits from it's creator function (or class ) `.prototype` object.

  • For {}, the creator is Object function. Hence, it inherits from Object.prototype
  • For [], the creator is the Array function. Hence, it inherits from Array.prototype
  • For a Person instance p, the creator function is Person function. Hence, it inherits from Person.prototype.

Object.prototype's prototype is null.

1

2

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.ageInBinary = function() {
  return this.age.toString(2);
}

const p1 = new Person('A', 25);
const p2 = new Person('B', 32);
function Person() {
}

const p = new Person()

Object.getPrototypeOf(p)
Object.getPrototypeOf(Person)
Object.getPrototypeOf(Object)
Object.getPrototypeOf(Function.prototype)
Object.getPrototypeOf(Object.prototype)
Object.getPrototypeOf(Function)
Person.prototype
Function.prototype
Function.prototype
Object.prototype
null
Function.prototype

Inheritance and Subclassing

In prototype-based languages, inheritance is the mechanism of basing an object or class upon another object.

Classes in ES5

function Person(name) {
  this.name = name;
}

Person.prototype.printName = function () {
  console.log(this.name);
};

function Employee(name, id) {
  Person.call(this, name); //! acting as super() call
  this.id = id;
}

Object.setPrototypeOf(Employee.prototype, Person.prototype);

Employee.prototype.printId = function () {
  console.log(this.id);
};

const e = new Employee('arfat', 42);
e.printId();

e.printName();

Classes in ES6

ES6 classes are not something that is radically new: They mainly provide more convenient syntax to create old-school constructor functions.

class Person {
    constructor(name) {
        this.name = name;
    }
    toString() {
        return `Person named ${this.name}`;
    }
    static logNames(persons) {
        for (const person of persons) {
            console.log(person.name);
        }
    }
}
class Employee extends Person {
    constructor(name, title) {
        super(name);
        this.title = title;
    }
    toString() {
        return `${super.toString()} (${this.title})`;
    }
}

const jane = new Employee('Jane', 'CTO');
console.log(jane.toString());

No separator (, or ;)

Subclassing

  • The extends clause lets you create a subclass of an existing constructor (which may or may not have been defined via a class)
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y); // (A)
        this.color = color;
    }
    toString() {
        return super.toString() + ' in ' + this.color; // (B)
    }
}
  • There are two ways of using super:
    • A class constructor uses it like a function call (super(···)), in order to make a super constructor call (line A).
    • Method definitions (in object literals or classes, with or without static) use it like property references (super.prop) or method calls (super.method(···)), in order to refer to super properties (line B).
  • The prototype of a subclass is the superclass
> Object.getPrototypeOf(ColorPoint) === Point
true
  • In a derived class, you must call super() before you can use this:
class Foo {}

class Bar extends Foo {
    constructor(num) {
        const tmp = num * 2; // OK
        this.num = num; // ReferenceError
        super();
        this.num = num; // OK
    }
}
  • Overriding the result of a constructor
class Foo {
    constructor() {
        return Object.create(null);
    }
}
console.log(new Foo() instanceof Foo); // false

Prototypal Inheritance

By Arfat Salman

Prototypal Inheritance

  • 647