JavaScript: The Hard Parts
JavaScript is a special language. It was designed in a hurry in its early days; it is an embedded scripting language — the language itself doesn’t even have a standard module system or standard I/O constructs. On the other hand, it supports both the object-oriented programming and functional programming paradigms, is the only language reliably available in web browsers, and got hugely popular over the last several years.
The unusual thing about JavaScript is that, unlike most if not all of the other
currently-popular OOP-friendly languages, JavaScript uses prototype-based
inheritance, instead of class-based (“classical”) inheritance. JavaScript’s
this
and new
keywords also mean completely different things as compared to
the same keywords in, say, Java. For me, when I started learning JavaScript,
these were the hardest parts of JavaScript, so I decided to write this article
to explain how JavaScript’s prototypes, this
, and new
works.
I’m assuming you are a programmer and know a classical OOP language like Java, C++, or Python. The version of JavaScript I’m using is ECMAScript 5; however, I am mostly using NodeJS v5.6.0 to check the validity of my examples.
First, in case you haven’t used JavaScript much, let’s look at a few things you can (or can’t) do in JavaScript.
var left = { prop: 'value' };
var right = { prop: 'value' };
var leftRef = left;
console.log(left == right);
// false
console.log(left == leftRef);
// true
console.log(left === leftRef);
// true
var a = [1, 2, 3];
a.forEach(function(e) {
console.log(e);
});
// 1
// 2
// 3
a.push(4);
console.log(a);
// [1, 2, 3, 4]
[5, 6].forEach(a.push);
// TypeError: Array.prototype.push called on null or undefined
console.log(a);
// [1, 2, 3, 4]
function MyClass(s) {
this.state = s;
}
MyClass.prototype.tellState = function() {
console.log("My state is:", this.state);
};
var o = new MyClass(42);
console.log(o.state);
// 42
console.log(o instanceof MyClass);
// true
console.log(o instanceof Object);
// true
o.tellState();
// My state is: 42
Prototypes
Ok, so you’ve already heard about JavaScript’s “prototype chain”, and how name look-up traverses the chain. But how does everything work in concrete terms?
Here are a few prototype-related phrases or keywords:
- “The
[[Prototype]]
property”, e.g. as used in the ES5 spec; - the
__proto__
property on objects, e.g. in__proto__
vs.prototype
in JavaScript (StackOverflow); - the
prototype
property on constructor functions, where- “constructor functions” are those functions written to be used with
new
.
- “constructor functions” are those functions written to be used with
It turns out that among the above, [[Prototype]]
and __proto__
actually
refer to the same concept — the “prototype” of an object, where you can try to
look properties up in case the object itself doesn’t have that property. The
prototype
property (of constructor functions, usually), on the other hand, is
not the “prototype” of the object holding that property.
We say an object “has a property” if that property can be found in that object or anywhere on that object’s prototype chain. We say an object “has an own property” if we can find that property without traversing the object’s prototype chain.
To check whether a given object “has a property”, we can use in
:
var o = {};
console.log('toString' in o);
// true
console.log(o.toString);
// [Function: toString]
console.log(o.toString());
// [object Object]
To check whether a given object “has an own property”, we can use
.hasOwnProperty
:
var o = {};
console.log(o.hasOwnProperty('toString'));
// false
o.name = "Just A. Object";
console.log(o.hasOwnProperty('name'));
// true
console.log(o.hasOwnProperty('hasOwnProperty'));
// false
As an aside, .hasOwnProperty
is available on a fresh, default object because
hasOwnProperty
is actually a property of Object.prototype
, which the
prototypes of all usual objects are set to.
Since ES5, there are a few nice “meta-programming” functions that allow us to do things with the prototypes of objects:
Object.create
Object.getPrototypeOf
Object.setPrototypeOf
See these functions in action here:
// Use our new prototype-related functions on ordinary objects:
var o = {};
console.log(Object.getPrototypeOf(o) === Object.prototype);
// true
console.log(o.hasOwnProperty === Object.prototype.hasOwnProperty);
// true
// Define an object which we can use as a prototype:
var p = { name: "I'm the prototype!" };
// Create a new object, and use the argument object as its [[Prototype]]
var c = Object.create(p);
console.log(Object.getPrototypeOf(c) === p);
// true
console.log(Object.getPrototypeOf(Object.getPrototypeOf(c)) ===
Object.prototype);
// true
// The __proto__ property is what Object.getPrototypeOf reads,
// but is not the recommended way of accessing an object's prototype.
// Use Object.getPrototypeOf instead.
console.log(c.__proto__ === p);
// true
// We can see that `c` inherited a property:
console.log(c.hasOwnProperty('name'));
// false
console.log('name' in c);
// true
console.log(c.name);
// I'm the prototype!
// We can shadow the inherited property:
c.name = "I'm the child!";
console.log(c.name);
// I'm the child!
// If we delete the object's own property, the prototype's one
// will "shine through" again.
delete c.name;
console.log(c.name);
// I'm the prototype!
// We can also forfeit our inheritance.
Object.setPrototypeOf(c, Object.prototype);
console.log('name' in c);
// false
// And to take it further, we can uninherit from Object.prototype too:
Object.setPrototypeOf(c, null);
console.log('hasOwnProperty' in c);
// false
Constructor functions
Constructor functions are functions whose author wrote them with the intention
that the function is called using new
. For now, let’s worry so much about what
exactly new
does, but focus on the constructor function and the object it
“constructs”.
function Thing() {
}
var t = new Thing();
console.log(t instanceof Thing);
//true
How does JavaScript work out that t
is an “instance” of Thing
? (Whatever
“instance” means in a class-less, prototype-based language.) It turns out what
instanceof
does is to traverse the prototype chain.
// Continuing from above snippet,
console.log(Object.getPrototypeOf(t) === Thing.prototype);
// true
// Note that the prototype of `Thing` is **not** `Thing.prototype`
console.log(Object.getPrototypeOf(Thing) === Thing.prototype);
// false
// Instead, since `Thing` is a constructor function, it is a function, and hence
// its prototype is `Function.prototype`:
console.log(Object.getPrototypeOf(Thing) === Function.prototype);
// true
// And hence Thing has properties defined in `Function.prototype`, e.g. "bind":
console.log('bind' in Thing);
// true
As seen from the above examples, it seems like the prototype
property on
constructor functions is more like some sort of meta-data that the mechanism for
new
reads off and Does The Right Thing with. If we put some properties into
Thing.prototype
, then all Thing
s will be able to see that property.
function Thing() {
}
Thing.prototype.category = "Usual things";
var t = new Thing();
console.log(t.hasOwnProperty('category'));
// false
console.log('category' in t);
// true
console.log(t.category);
// Usual things
One cool thing is since everything is dynamic, even if we retroactively add new
properties into the prototype
property of a constructor function, objects that
we’ve already created beforehand using that constructor function can also access
the new property.
// Continuing from the above snippet,
console.log('exists' in t);
// false
// Let's say that things should perhaps by default not exist:
Thing.prototype.exists = false;
console.log('exists' in t);
// true
console.log(t.exists);
// false
We can also add properties that are functions to Thing.prototype
to achieve
very classical-OOP-reminiscient effects:
// Continuing from the above snippet,
function isExistant() {
return this.exists;
}
Thing.prototype.isExistant = isExistant;
console.log(t.isExistant());
// false
But of course we’ve jumped the gun — we still don’t know what this
does yet.
So let’s move on to studying this
.
The “this” keyword
If you know about the special arguments
object available inside function
definitions, explaining this
would be a bit easier.
function getArgList() {
return arguments;
}
var args = getArgList('hello?', 'lol');
console.log(args[0]);
// hello?
console.log(args[1]);
// lol
console.log(args.length);
// 2
The thing to pay attention to here is that the value that is stored in the
arguments
object inside the body of a function depends on how the function is
called. If the function is called with two arguments, then arguments
will hold
two things. If the function is called without any arguments, then
arguments.length
will be 0
.
Think of this
as another special variable like arguments
which:
- is available inside the body of a function, and
- holds a value that is dependent on how the function is called.
Strictly speaking, the this
keyword holds a value even if it is accessed from
outside any functions, and there are weird, stupid things like how it sometimes
refers to the global object, depending on whether “strict mode” is active for
that context; for now, let’s focus on using this
inside the body of function
definitions, without strict mode.
The thing about this
is that its value, instead of depending on the arguments
that are passed to the function when the function is called, depends on the
syntax of the function call expression. I know, it’s weird, right? It makes me
think a little of dynamically-scoped languages, where what a variable name is
not looked-up where the function is defined, but rather where it is called. Some
would call it “very late binding”, where this
is not bound even at
run-time, but only bound at call-time. Anyway, let’s see some examples.
function getThis() {
return this;
}
console.log(this === getThis());
// (This is weird. In Chrome, it prints true;
// in NodeJS as a script, it prints false.
// Let's not worry about it, because that's not how `this` can be useful.)
// Regardless of whether a function's definition uses `this`, we can always
// use it as a property of an object. The property will hold a reference to the
// function.
var o = { g: getThis };
console.log(o.g === getThis);
// true
// If we use a property-lookup dot when calling the function, `this` will be set
// to the object value among whose property we've found the function we're
// calling.
console.log(o === o.g());
// true
// However, the `this` will be lost if we don't call the function in the same
// expression as the one where we looked it up.
var gRef = o.g;
console.log(o === gRef());
// false
// Which is actually obvious if you think about it --- `gRef` and `getThis` is
// actually just two references to the same function, which isn't inherently
// related to any particular object (for the purpose of `this`).
console.log(gRef === getThis);
// true
// It doesn't really matter how the property lookup happened --- with a dot or
// with square brackets.
console.log(o === o['g']());
// true
// Even if the expression that gave the object where we performed the property
// (method) look-up isn't even a lvalue (left-hand value), `this` still refers
// to the object that holds the function property we're calling.
function getAnO() {
return o;
}
console.log(o === getAnO().g());
// true
// Or if we nest objects, `this` is still the object that holds the function
// property we're calling.
var outer = { inner: o };
console.log(o === outer.inner.g());
// true
// There is no difference in how `this` works between using a pre-defined, named
// function as the property of an object, or to use an anonymous function to
// define the function property (i.e. method) inside an object literal.
var john = {
name: 'John',
selfIntroduce: function() {
console.log("Hi, I'm " + this.name + ".");
}
};
john.selfIntroduce();
// Hi, I'm John.
var ben = {
name: 'Ben',
selfIntroduce: john.selfIntroduce
};
ben.selfIntroduce();
// Hi, I'm Ben.
Similar to how we have Object.create
, Object.getPrototypeOf
etc. for dealing
with prototypes, we have the following functions to deal with this
:
Function.prototype.bind
Function.prototype.call
Function.prototype.apply
These functions basically let you explicity specify which object to pass as
this
to a given function.
function getThis() {
return this;
}
var o = {};
// Make a new function whose `this` is always bound to the given object.
// This is like partially applying the `getThis` function with just the `this`
// parameter.
var getThisBoundToO = getThis.bind(o);
console.log(getThisBoundToO() === o);
// true
// `.call` and `.apply` is like immediately calling the function that
// `.bind` returns.
function getName(id) {
return "Person " + id + ": " + this.name;
}
var john = { name: 'John' };
var getJohnsName = getName.bind(john);
console.log(getJohnsName(1));
// Person 1: John
getName.call(john, 2);
// Person 2: John
getName.apply(john, [3]);
// Person 3: John
getName.bind(john)(4);
// Person 4: John
I’ll leave you to read the docs on MDN for the details of, or difference
between, call
and apply
. I’ll also just mention that in ECMAScript 6,
Arrow Functions also gives new ways of dealing with the this
parameter.
By now, we can understand why the example from the very first snippet of this post of pushing elements into an array — where we were trying to pass a method of an object as a function to a higher-order function — didn’t work as naively expected.
var a = [1, 2, 3];
console.log('push' in a);
// true
console.log(a.hasownproperty('push'));
// false
console.log(a.push === Array.prototype.push);
// true
// Here was the unexpected error from before:
[4, 5].forEach(a.push);
// TypeError: Array.prototype.push called on null or undefined
// Now we know that this error is no different from the follow, which
// is clearly nonsense and should be an error (Because `Array` doesn't
// know about particular array instances).
[4, 5].forEach(Array.prototype.push);
// TypeError: Array.prototype.push called on null or undefined
// However, if we perform the property look-up in the same expression
// as calling that function property (i.e. method), the function will
// be able to find the array instance via `this`.
[4, 5].forEach(function(e) {
// Notice we're immediately calling the function after finding it.
a.push(e);
});
console.log(a);
// [1, 2, 3, 4, 5]
// Alternatively, we can use our fresh knowledge of the several fancy
// `this`-manipulating functions like `.bind`:
[6, 7].forEach(a.push.bind(a));
console.log(a);
// [1, 2, 3, 4, 5, 6, 0, [6, 7], 7, 1, [6, 7]]
Oops! It turns out that Array.prototype.forEach
passes more than
just one argument to its argument function (Array.prototype.push
in
this case), so .push
pushed all of those arguments in. For details,
read the docs for Array.prototype.forEach
.
The “new” keyword
Now that we know how this
works, and how prototypes can be set up, we are
equipped to learn about the mechanism for new
! What does the new
keyword do?
Well, we can just read section 13.2.2 of the ES5 spec. (I found this
when reading StackOverflow about returning values from constructor
functions.) I personally think of new
as just syntactic sugar,
since its behaviour can be implemented in JavaScript itself. Before we dive deep
into the quirks and details of new
as per the spec, here’s overly-simple
implementation of new
as a JavaScript function, using constructs that we’ve
learnt so far, that does what we’ve seen new
do so far:
function overlySimpleNew(constructor, args) {
var newObject = Object.create(constructor.prototype);
constructor.apply(newObject, args);
return newObject;
}
This implementation already does a lot of what new
does. See it in action:
// Define a constructor function
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.selfIntroduce = function() {
console.log("Hi, I'm " + this.firstName + " " + this.lastName + ".");
};
// Use the constructor function with the `new` keyword
var john = new Person('John', 'Doe');
john.selfIntroduce();
// Hi, I'm John Doe.
console.log(john instanceof Person);
// true
console.log('hasOwnProperty' in john);
// true
// Do the same thing with our hand-rolled `overlySimpleNew`:
var ben = overlySimpleNew(Person, ['Ben', 'Smith']);
ben.selfIntroduce();
// Hi, I'm Ben Smith.
console.log(ben instanceof Person);
// true
console.log('hasOwnProperty' in ben);
// true
Although the above implementation is about right, it doesn’t do all of what
the spec specifies. There are many weird, corner cases of how new
might be
used that we haven’t covered. Providing A Return Value In A JavaScript
Constructor by Ben Nadel demonstrates many of such cases. So
let’s read the spec now, and see how to implement a basically-compliant new
.
Go read the spec now: 13.2.2 [[Construct]]. Here’s what that
section says new
should do, implemented as a function:
function BasicallyCompliantNew(constructor, args) {
var obj = {};
var prototype = constructor.prototype;
if (typeof prototype === 'object') {
Object.setPrototypeOf(obj, prototype);
} else {
Object.setPrototypeOf(obj, Object.prototype);
}
var result = constructor.apply(obj, args);
// Functions are objects too.
if ([ 'object', 'function' ].indexOf(typeof result) >= 0) {
return result;
} else {
return obj;
}
}
Basically, as compared to the overly-simple implementation, this
basically-compliant implementation takes care of the case where the constructor
function’s prototype
property holds a non-object value (which it usually
doesn’t, because by default it’s effectively Object.create(Object.prototype)
),
or where the constructor function returns an object value (which is usually
doesn’t, because it doesn’t need to return anything).
Read the spec or the code above slowly. Have a think through — this is all
there is to new
.
Simulating classical OOP
Classical OOP wants the following things:
- Encapsulation
- Inheritance
- Polymorphism
Although there are no notion of “private” object members in JavaScript, by using closures cleverly, we can achieve that effect. Since closures are not a JavaScript-specific thing, I won’t explain it in this post. For some patterns of using closures in the context of JavaScript, go read Douglas Crockford’s book JavaScript: The Good Parts.
Polymorphism is obvious. Everything is dynamic, so the dispatch of method-calls is dynamic. Duck typing. Yada yada. Easy.
[{
getName: function() {
return 'John Doe';
}
}, {
name: "Ben Smith",
age: 43,
getName: function() {
return this.name;
},
selfIntroduce: function() {
// ...
}
}].forEach(function(person) {
// We clearly don't care what is the type of `person`, so long as
// it has a `name` property that holds a function that returns a
// string. Which function to dispatch control to is decided at the time
// the property look-up, which is at run-time.
console.log(person.getName() + " is here");
};
The not-so-obvious one of the three OOP things is inheritance — because, well, JavaScript doesn’t have classes. Here’s how we achieve similar outcomes using prototypes and constructor functions:
function Thing(state) {
this.state = state;
}
Thing.prototype.getState = function() {
return this.state;
};
Thing.prototype.tellState = function() {
console.log("The state of the thing is:", this.state);
};
var t = new Thing('thing-state');
t.tellState();
// The state of the thing is: thing-state
console.log(t.getState());
// thing-state
// SubThing is meant to be a sub-type of Thing.
function SubThing(state, extraState) {
// Call the super-class constructor
Thing.call(this, state);
this.extraState = extraState;
}
// Specify inheritance relationship by linking up the prototypes.
Object.setPrototypeOf(SubThing.prototype, Thing.prototype);
// We can override (i.e. shadow) methods from the super-class.
SubThing.prototype.tellState = function() {
console.log("The state of the sub-thing is:",
this.state, ",", this.extraState);
}
var st = new SubThing('sub-thing-state', 'extra-state');
st.tellState();
// The state of the sub-thing is: sub-thing-state , extra-state
console.log(st instanceof SubThing);
// true
console.log(st instanceof Thing);
// true
console.log(st.getState === Thing.prototype.getState);
// true
I’m quite new to JavaScript, so I don’t know what way of using the prototype-related building blocks to simulate classical OOP is more idiomatic. For instance, some people seem to prefer something like
SubThing.prototype = new Thing();
… instead of the
Object.setPrototypeOf(SubThing.prototype, Thing.prototype);
… which I used. My reason for doing what I did is that at time of defining the SubThing “class”, I may not have enough information to instantiate a Thing, depending on what arguments the Thing constructor takes.
The point here is, although the above code sample may not be idiomatic, it nonetheless certainly can get the “simulate classical OOP” job done, nicely, using JavaScript’s prototype mechanisms.
The end
So there you have it — an explanation of what I think is JavaScript’s hardest
parts: prototypes, this
, and new
. Equipped with this knowledge, you are
ready to learn all the other parts of JavaScript, like ES6 classes (which are
allegedly still prototype-based) or ES6 arrow functions (which makes this
different). You are now JavaScript-ready!