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
- 707