WHAT’S WRONG WITH JAVASCRIPT? PT2
Hi guys, welcome back. In part 1 of this series we examined the misconceptions surrounding ‘Hoisting’ and why these misconceptions could lead some devs to the conclusion that JS was fundamentally flawed even though it is useful for web development.
In this post we will be examining another feature of JS which has led many devs to conclude that JS is flawed, the feature is ‘this’.
THIS: Consider the following code snippet:
var mike = {
firstName: ‘Mike’,
lastName: ‘Hike’,
greet: function () {
return this.firstName +’ ‘+ this.lastName;
}
};mike.greet();//’Mike Hike’var greetMike = mike.greet;
/*assigning the value of the ‘greet’ method of the ‘mike’ object to ‘greetMike’ variable*/greetMike();//’undefined undefined’
What just happened?
When we ran ‘mike.greet()’ we got what we expected — Mike’s full name, but now that we copied that same method to another variable and ran it expecting to get the same result we get ‘undefined undefined’ instead… crazy JS! This has to be a flaw right? I guess you know by now that there’s more to what happened than meets the eye.
So what’s really happening?
In JS, every function requires an object to provide it with the context in which it is operating. The object which provides this context is determined by the ‘this’ variable. Unfortunately for many, the value returned by the ‘this’ binding is a ‘run time’ operation rather than a ‘write time’ or ‘lexical’ operation. What this means is that the only way to determine what ‘this’ refers to is to examine what object provides the function’s context at run time.
Furthermore, in JS, objects don’t really have ‘methods’ which they ‘own’ unlike other languages, rather they have access to functions which when called in the context of the object are executed as ‘methods’ with the ‘this’ binding pointing to the object.
Thus “greet: function () {..}” simply binds the member of the ‘mike’ object identified with the ‘greet’ label to the function which provides it’s value.
With this in mind lets examine what happened with ‘mike.greet()’. (the following is a simplified explanation which conveys meaning without delving into deep technical implementation)
1. The JS Engine encounters the ‘mike’ identifier, it searches its memory for the presence of the ‘mike’ identifier and it notes that ‘mike’ is an object. All JS objects have ‘members’ (i.e. properties and methods) which can be accessed with either ‘.’ (i.e. the member access operator) or ‘[]’ (i.e. the computed member access operator). So when the JS Engine encounters ‘.’ it knows that any identifier that follows is a member of ‘mike’ i.e. the ‘mike’ object is providing the ‘context’ for the member which follows the ‘.’.
When the Engine encounters ‘greet’ it doesn’t search the whole of it’s memory, rather it searches the portion of memory assigned to the ‘mike’ object, in essence the ‘mike’ object is the value of the ‘greet’ method’s ‘this’ binding. Therefore, within the ‘greet’ function, ‘this’ points to the ‘mike’ object and ‘this.firstName’ is equivalent to mike.firstName etc.
This is why mike.greet() works as expected.
2. What about “var greetMike = mike.greet;”?
Well if you ran ‘greetMike;’, you would see something like ‘ function () {return this.firstName +’ ‘+ this.lastName;}’… this would seem to imply that since it is essentially the same function as ‘mike.greet’ it should yield the same result when invoked just as ‘mike.greet();’.
The reality is that what really happened is that the value of the ‘mike.greet’ member which is a function was assigned to ‘greetMike’ but the ‘context’ in which ‘mike.greet()’ and ‘greetMike()’ are executed is very different.
Remember from point 1 that ‘mike.greet()’ is executed in the context of the ‘mike’ object and so ‘this’ points to the ‘mike’ object… well, consider ‘greetMike()’. What context is it executed in? What object is it a member of? No, not ‘mike’ because what was assigned to ‘greetMike’ was merely the function which is the value of the ‘greet’ member of the ‘mike’ object, and not the entire ‘mike’ object.
The answer is that since no object is explicitly providing context i.e. there’s no object identifier preceding a ‘.’ before ‘greetMike’ we need to consider the scope within which ‘greetMike’ is being invoked.
In JS, the default scope is the global scope i.e. the scope provided by the ‘window’ object. So unless the scope is explicitly defined by a function, or context is explicitly provided by an object, the scope and context of any JS function is the ‘default’ one which is the ‘window’ object i.e. ‘this’ points to the window object.
And now, we can see why the value returned by ‘greetMike()’ is completely correct. On the ‘window’ object there is no ‘firstName’ or ‘lastName’ property (unless created by bad programming practices which have polluted the global scope) and thus when asked to return window.firstName and wnidow.lastName, poor ‘greetMike’ rightly returns ‘undefined undefined’.
With this piece of information clarified, we can see that the ‘strange’ behavior of ‘this’ in JS is not a flaw but an intentional design feature of the JS language.
The question then remains, how do we control the ‘this’ binding? And what are the rules governing ‘this’ binding?
An in-depth explanation is beyond the scope of this article but we will briefly examine these two questions.
1. How to control the ‘this’ binding. JS functions offer 3 built-in methods with which the ‘this’ binding can be controlled. a) Function#call b) Function#apply and c) Function#bind.
Call: This method takes 2 arguments and the first argument determines the value of the ‘this’ binding for the function. When invoked with just one argument, it dynamically binds ‘this’ to the supplied ‘object’ e.g.
greetMike.call(mike);//”Mike Hike”
Apply: Works similar to ‘Call’ when used with just one argument e.g.
greetMike.apply(mike);//”Mike Hike”
Bind: Also works similarly to Call and Apply when only one argument is supplied, the difference is that it creates a new function which has ‘this’ permanently bound to the supplied object e.g.
var greetMike = mike.greet.bind(mike);
greetMike();//”Mike Hike”
2. There are 4 rules which determine how ‘this ‘ is bound in JS.
a) Implicit binding: This occurs when an object explicitly provides the ‘context’ for the function e.g mike.greet();
Implicit binding works fine when used directly, but it fails when used in any form of async operation i.e. the ‘this’ binding is lost in async operations (we’ll explore this in a future post).
b) Explicit binding: This occurs when any of call, apply or bind is used to explicitly set the ‘this’ binding of a function. Explicit binding works across ‘async’ operations.
c) Function Constructor binding: This occurs when an object is created by invoking a function as a Constructor i.e. using the ‘new’ keyword e.g.
var tom = new Person();
In this case, ‘this’ points to the object returned by the function constructor, in the case of this example ‘tom’.
d) Default binding: If none of the preceding 3 conditions hold, then the ‘default’ binding rule is used i.e. ‘this’ points to the ‘window’ or global object. Note that in ‘strict mode’ this will throw an error as binding ‘this’ to the global object is not allowed.
I trust this article has helped clarify the JS ‘this’ mechanism and has served to prove that with respect to the behavior of ‘this’, JS is not flawed in any way.
Thanks for reading, please leave your comments below.