Prototypes
Prototypes, Prototypal inheritance, Built-in prototypes
// Prototype - objects that have a special hidden property [[Prototype]] (as named in the specification), that is either null or references another object
// Prototypal inheritance - objects inheriting properties from other objects by pointing their [[Prototype]] property to other object (parent)
// multiple inheritance isn't allowed and the property can only store one ref
let animal = {
eats: true
}
let rabbit = {
jumps: true
}
rabbit.__proto__ = animal // sets rabbit.[[Prototype]] = animal
alert(rabbit.eats) // true
// methods also get inherited
let animal = {
eats: true,
walk() {
alert("Animal walk")
}
}
let rabbit = {
jumps: true,
__proto__: animal
}
// walk is taken from the prototype
rabbit.walk() // Animal walk
// note that the __proto__ is outdated and is just a getter/setter for [[Prototype]] property
// writing doesn't use prototype, only reading does
let animal = {
eats: true
}
let rabbit = {
jumps: true,
__proto__: animal
}
rabbit.eats = false // writing to rabbit (creates new property in rabbit)
alert(rabbit.eats) // false
alert(animal.eats) // true (unaffected)
// Accessor properties are an exception, as assignment is handled by a setter function. So writing to such a property is actually the same as calling a function. Hence the getter/setter function is called from the parent even if we used child object to set the property upon.
// No matter where the method is found: in an object or its prototype. In a method call, this is always the object before the dot.
// animal has methods
let animal = {
walk() {
if (!this.isSleeping) {
alert(`I walk`)
}
},
sleep() {
this.isSleeping = true
}
}
let rabbit = {
name: "White Rabbit",
__proto__: animal
}
// modifies rabbit.isSleeping
rabbit.sleep()
alert(rabbit.isSleeping) // true
alert(animal.isSleeping) // undefined (no such property in the prototype)
// the for..in loop iterates over both its own and its inherited properties.
// all other key/value-getting methods (Object.entities(), Object.keys(), Object.values()) only operate on the object's own properties only.
/*
Built-in prototypes:
Every object in JS eventually inherits from the base prototype - Object.prototype
Arrays inherit from Array.prototype
Functions inherit from Function.prototype
Dates inherit from Date.prototype
Numbers inherit from Number.prototype
*/
let arr = [1, 2, 3]
alert( arr.__proto__ === Array.prototype ) // true
alert( arr.__proto__.__proto__ === Object.prototype ) // true
let obj = {}
alert(obj) // [Object object] <-- result of Object.prototype.toString
let arr = [1, 2, 3]
alert(arr) // 1,2,3 <-- result of Array.prototype.toString (closer parent of array object)
// Native prototypes - primitives are also converted to Wrapper objects and methods are called on them, these wrapper objects get their methods from Number.prototype, String.prototype, Boolean.prototype
// adding function to native prototype
String.prototype.show = function() {
alert(this)
}
"BOOM!".show() // BOOM!
// not recommended unless we're creating a Polyfill
// borrowing methods from prototypes
let obj = {
0: "hello",
1: "world",
length: 2,
}
obj.join = Array.prototype.join
alert( obj.join(',') ) // hello,world
// __proto__ isn't recommended in modern JS, don't get/set it directly rather use prototype methods:
Object.getPrototypeOf(obj) // returns the [[Prototype]] of obj
Object.setPrototypeOf(obj, proto) // sets the [[Prototype]] of obj to proto
// the only case where __proto__ can be specified manually is when creating the object, and not editing it afterwards
let child = { __proto__: parent }
let child = Object.create(parent) // JS provided function for this
// "very plain" objects - unless we set __proto__ explicitly to null, until then we can't have a key called "__proto__" in object
let obj = { __proto__: null }
// or Object.create(null)
obj.__proto__ = 'foobar'
alert( obj.__proto__ ) // foobar
Prototypes in Constructors (F.prototype)
// for object created via a contructor, set FuncName.prototype property to parent object
let animal = {
eats: true
}
function Rabbit(name) {
this.name = name
}
Rabbit.prototype = animal
let rabbit = new Rabbit("White Rabbit") // rabbit.__proto__ == animal
alert( rabbit.eats ) // true
// works only when new is used to create object using constructor - new F() sets [[Prototype]]
// every function has the "prototype" property even if we don't supply it
// the default "prototype" is an object with the only property constructor that points back to the function itself
function Rabbit() {}
/* by default, the prototype property points to the function object itself
Rabbit.prototype = { constructor: Rabbit }
*/