let proto = {
whoami() { console.log('I am proto'); }
};
let obj = {
whoami() {
Object.getPrototypeOf( obj ).whoami.call( this ); // super.whoami();
console.log('I am obj');
}
};
Object.setPrototypeOf( obj, proto );
This code lets obj inherit the proto.
I got confused about this code Object.getPrototypeOf( obj ).whoami.call( this );
which is equvalent to super.whoami()
. Can anyone explain how this code works?
You may want to first read about the following topics in my answer below before skipping to the explanation:
this
contextIn JavaScript, objects can have prototypes. An object inherits all the properties of its prototype. Since prototypes themselves are objects, they too can have prototypes. This is called the prototype-chain.
Accessing an object's property will cause JS to first try to find it on the object itself, and then on each of its prototypes consecutively.
Let's define both a person and their prototype human
:
const human = { isSelfAware: true };
const person = { name: "john" };
Object.setPrototypeOf(person, human);
If we try to access person.name
, then JS will return the property of person
because it has this property defined.
If we however try to access person.isSelfAware
, JS will return the property of human
because person
doesn't have the property defined but its prototype does.
If an object has a similarly named property as one of its prototypes, the prototype's property is shadowed, because JS will return the earliest defined property.
Shadowing is useful because objects can define different property values without affecting the prototype.
const lightBulbProto = { serialNumber: 12345, isFunctional: true };
const oldLightBulb = { isFunctional: false };
const newLightBulb = {};
Object.setPrototypeOf(oldLightBulb , lightBulbProto);
Object.setPrototypeOf(newLightBulb, lightBulbProto);
By default, all light bulbs are functional. But now we defined an old light bulb to be non-functional (isFunctional: false
).
This doesn't affect newLightBulb
because we didn't define the functionality on the prototype, but on the specific object oldLightBulb
.
First off: By "function" I only mean functions and function expressions function() {}
. Method definitions are syntactic sugar for function expressions, so they are considered the same. Arrow function expressions () => {}
are excluded.
If a function is deterministic (same input results in same output) and self-enclosed (no side-effects), it is a pure function; otherwise it is an impure function.
Pure functions are usually easy to understand and debug, since all relevant code is contained in its definition.
const array = [];
function addToArray(o) { // Impure function
array.push(o);
}
function add(a, b) { // Pure function
return a + b;
}
JS tries to bundle your function with the smallest possible scope. If your function uses variables outside of its own scope, a closure is created at function creation during runtime. This means, the surrounding scope is kept alive for as long as the dependent function is accessible.
Closures aren't inherently bad, but may be confusing or cause of a memory-leak.
createGet
initializes object
in its scope, and then returns a getter for the object:
function createGet() {
const object = {};
return function() {
return object;
}
}
let get1 = createGet();
let get2 = createGet();
// They are independent!
get1().name = "get1";
get2().number = 2;
console.log(get1()); // { "name": "get1" }
console.log(get2()); // { "number": 2 }
// Release the closures!
get1 = undefined;
get2 = undefined;
The code looks as if each getter function returns the same object. This is not true, because every call of createGet
initializes a different object each time. Because of this, each getter function refers to a different object, making each getter independent.
Since closures may bundle a different scope at every creation, they may be a memory-leak. Only when no further reference to a closure exists, it may be garbage-collected. In our example we do this with these lines:
get1 = undefined;
get2 = undefined;
Apart from the default "sloppy mode", JS also features a strict mode.
this
contextThe current context is effectively the value of this
in the current scope.
The context of a function is determined by how the function is called. A function may be called standalone (e.g. aFunc()
) or as a method (e.g. obj.aMethod()
).
If a function is called on its own, it inherits the surrounding context. If there is no surrounding function context, the surrounding context will either be globalThis
in "sloppy mode", or be undefined
in strict mode.
If the function is called as a method obj.aMethod()
, this
will refer to obj
.
Additionally, the first argument of the functions Function.prototype.bind
, Function.prototype.call
and Function.prototype.apply
can specify the context of the underlying function.
console.log("Global context:");
(function() {
console.log("- Sloppy: this == globalThis? " + (this === globalThis));
// In browsers: globalThis == window
})();
(function() {
"use strict";
console.log("- Strict: this == undefined? " + (this === undefined));
})();
const compareThis = function(o) {
return this === o;
};
const obj = { compareThis };
console.log("As function:");
console.log("- Default: this == obj? " + compareThis(obj));
console.log("- With call(obj): this == obj? " + compareThis.call(obj, obj));
console.log("As method:");
console.log("- Default: this == obj? " + obj.compareThis(obj));
console.log("- With call(undefined): this == obj? " + obj.compareThis.call(undefined, obj));
.as-console-wrapper{max-height:unset !important}
First, two objects (obj
and proto
) are initialized. proto
will be the prototype of obj
.
Because both objects have the property whoami
defined, the prototype's property will be shadowed. Since we still want to access the prototype's property, we have to access it directly. To do this, we first get the prototype of obj
(Object.getPrototypeOf(obj)
, or "super
"), and then access its property (.whoami
, or "super.whoami
").
Since whoami
was originally called on obj
, it would make sense to use obj
as the context. For this reason, we call whoami.call(this)
(with this
equal to obj
; effectively "super.whoami()
") on the prototype's whoami
method, because if we didn't the method would use the prototype as its context.
Because obj
is part of the surrounding lexical scope instead of the scope of obj.whoami
, whoami
creates a closure because of it using the identifier obj
in Object.getPrototypeOf(obj)
. Ideally we would get the prototype using this
instead, to not have an unnecessary closure: Object.getPrototypeOf(this)
.