You Don’t Know JS: this & Object Prototypes
The Object Type
The object type can be used with both dot notation
object.value or bracket notation
object['value']. The dot notation is preferred because it’s shorter and easier to read but the bracket notation could be used if the key has special characters in it, for instance
An array is an object that hold values not particularly in named properties/keys, but rather in numerically indexed positions.
var arr = [ 'hello world', 42, true ]; arr; // hello world]
There are four rules for how to determine the
this binding depending on the call-site.
- Default binding – The catch-all rule. Standalone function invocation.
- Implicit binding
- Explicit binding
The first common temptation is to assume this refers to the function itself. That’s a reasonable grammatical inference, at least.
Every time you feel yourself trying to mix lexical scope look-ups with this, remind yourself: there is no bridge.
We said earlier that this is not an author-time binding but a runtime binding. It is contextual based on the conditions of the function’s invocation. this binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called.
When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference, which will be used for the duration of that function’s execution.
this is actually a binding that is made when a function is invoked, and what it references is determined entirely by the call-site where the function is called.
Intentionally mixing strict mode and non-strict mode together in your own code is generally frowned upon. Your entire program should probably either be strict or non-strict. However, sometimes you include a third-party library that has different strictness than your own code, so care must be taken over these subtle compatibility details.
Arrays assume numeric indexing, which means that values are stored in locations, usually called indices, at positive integers, such as 0 and 42:
Use objects to store key/value pairs, and arrays to store values at numeric indices.
But what if you want to iterate over the values directly instead of the array indices (or object properties)? Helpfully, ES6 adds a for..of loop syntax for iterating over arrays (and objects, if the object defines its own custom iterator):
Class/inheritance describes a certain form of code organization and architecture — a way of modeling real world problem domains in our software.
Another key concept with classes is polymorphism, which describes the idea that a general behavior from a parent class can be overridden in a child class to give it more specifics.
Functions aren’t constructors, but function calls are “constructor calls” if and only if new is used.
As we’ve now seen, the [[Prototype]] mechanism is an internal link that exists on one object that references some other object. This linkage is (primarily) exercised when a property/method reference is made against the first object, and no such property/method exists. In that case, the [[Prototype]] linkage tells the engine to look for the property/method on the linked-to object. In turn, if that object cannot fulfill the lookup, its [[Prototype]] is followed, and so on. This series of links between objects forms what is called the “prototype chain.”
Object.create(..) creates a new object (bar) linked to the object we specified (foo), which gives us all the power (delegation) of the [[Prototype]] mechanism, but without any of the unnecessary complication of new functions acting as classes and constructor calls, confusing .prototype and .constructor references, or any of that extra stuff.
Functions called with new are often called “constructors,” despite the fact that they are not actually instantiating a class as constructors do in traditional class-oriented languages.
We need to try to change our thinking from the class/inheritance design pattern to the behavior delegation design pattern.
Basically, think about needing behaviors from two sibling/peer objects (XYZ and Task) to perform task “XYZ.” But rather than needing to compose them together, via class copies, we can keep them in their separate objects, and we can allow the XYZ object to delegate to Task when needed.
In general, with [[Prototype]] delegation, you want state to be on the delegators (XYZ, ABC), not on the delegate (Task).
This is an extremely powerful design pattern, very distinct from the ideas of parent and child classes, inheritance, polymorphism, etc. Rather than organizing the objects in your mind vertically, with parents flowing down to children, think of objects side by side, as peers, with any direction of delegation links between the objects as necessary.
Ask yourself: if I can get the same functionality with OLOO-style code as I do with class-style code, but OLOO is simpler and has less things to think about, isn’t OLOO better?
OLOO better supports the principle of separation of concerns, where creation and initialization are not necessarily conflated into the same operation.
We didn’t need a base Controller class to “share” behavior between the two, because delegation is a powerful enough mechanism to give us the functionality we need.
Another common, but perhaps less robust, pattern for type introspection, which many devs seem to prefer over instanceof, is called “duck typing.” This term comes from the adage, “if it looks like a duck, and it quacks like a duck, it must be a duck.”
Classes and inheritance are a design pattern you can choose, or not choose, in your software architecture. Most developers take for granted that classes are the only (proper) way to organize code, but here we’ve seen there’s another less-commonly talked about pattern that’s actually quite powerful: behavior delegation.
First, the class syntax may convince you a new “class” mechanism exists in JS as of ES6. Not so. class is, mostly, just syntactic sugar on top of the existing [[Prototype]] (delegation!) mechanism.
class does a very good job of pretending to fix the problems with the class/inheritance design pattern in JS. But it actually does the opposite: it hides many of the problems and introduces other subtle but dangerous ones