Mysteries of the JavaScript Arcana: JavaScript Quirks Demystified
Beware of any assumptions,
distrust any preconceptions,
forgo your experience,
and think with the mind of a beginner.
- Appa Ojnh
The White Sage
A Couple of Tips About JavaScript Quirks and Gotchas
While JavaScript looks a lot like a C-like language, it does not behave like one in many ways. This, I would say, is the biggest reason why C# developers get so confused when they come to JavaScript.
If you’ve followed the book closely, you may have noticed that I have decided to call these unexpected behaviors the JavaScript Arcana. You have already seen several examples of these shadowy features thus far. Let’s make a quick summary of them:
- Function scope and variable hoisting
- Array-like objects
- Function overloading
We’ll start this chapter by making a short review of the quirks that you’ve already learned (repetition is a great tool for learning). And we’ll continue by diving deeper into these other parts of the JavaScript Arcana:
- The sneaky
thiskeyword - Global scope as a default
- Type coercion madness
- JavaScript strict mode
We will focus particularly in the obscure behavior of the this keyword, our most dangerous foe. I expect that what you will learn in this chapter will save you from unmeasurable frustration in the future.
A Quick Refresher of the JavaScript Arcana 101
In The Basics of JavaScript Functions we saw how JavaScript has function scope. That is, as opposed to C# where every block of code creates a new scope, in JavaScript it is only functions that create new scopes. Every time you declare a variable through the var keyword it is scoped to its containing function. You also learned the concept of hoisting and how the JavaScript runtime moves your variable declarations to the top of a function body. Finally, you discovered how ES6 brings the let and const keywords that give you the ability to declare block-scoped variables and forget about the headaches of hoisting and function-scoped variables.
In Function Patterns: Arbitrary Arguments you learned about the arguments object. It can be accessed within every function to retrieve the arguments being passed to that function at runtime. You saw how the arguments object, although it looks like an array, it is actually what we call an array-like object. Array-like objects can be enumerated, indexed and have a length property but they lack all array methods. You also discovered how to convert these objects to actual arrays using Array.prototype.slice (or Array.from) and how the new ES6 rest operator solves the arguments issue completely.
In Function Patterns: Overloading you learned how you cannot overload JavaScript functions or methods in the same way that you do in C#. Instead, you can use several patterns to achieve the same effect: Argument inspection, options objects, ES6 default arguments or functional programming with polymorphic functions.
Now that we’ve warmed up to JavaScript weirdest features let’s take a look at the behavior of this.
This, Your Most Dangerous Foe
One of the most common problems when a C# developer comes to JavaScript is that it expects this to work exactly as it does in C#. And She or He or Zie will write this common piece of code unaware of the terrible dangers that lurk just one HTTP call away…
1 function UsersCatalog(){
2 this.users = [];
3 getUsers()
4
5 function getUsers(){
6 $.getJSON('https://api.github.com/users')
7 .success(function updateUsers(users){
8 this.users.push(users);
9 // BOOOOOOOM!!!!!
10 // => Uncaught TypeError:
11 // Cannot read property 'push' of undefined
12 });
13 }
14 }
15 var catalog = new UsersCatalog();
In this code example we are trying to retrieve a collection of users from the GitHub API. We perform an AJAX15 request using jQuery getJSON and if the request is successful the response is passed as an argument to the updateUsers function.
The example throws an exception cannot read property 'push' of undefined which is the JavaScript version of our well known nemesis: The NullReferenceException (we meet again). Essentially, when we evaluate the updateUsers function, the this.users expression takes the value of undefined. When we try to execute this.users.push(users) we’re basically calling the method push on nothing and thus the exception being thrown.
In order to understand why this is happening we need to learn how this works in JavaScript. In the next sections we will do just that. By the end of the chapter, when we have demystified this and become this-xperts, you’ll be able to understand what is the cause of the error.
JavaScript Meets This
So this in JavaScript is weird. Unlike in other languages, the value of this in JavaScript depends on the context in which a function is invoked. Repeat. The behavior of this in JavaScript is not 100% stable nor reliable at all times, it depends on the context in which a function is invoked.
This essentially means that depending on how you call a function, the value of this inside that function will vary. We can distinguish between these four scenarios:
-
thisand objects -
thisunbound -
thisexplicitly -
thisbound
This And Objects
In the most common scenario for an OOP developer we call functions as methods. That is, we call a function that is a property within an object using the dot notation.
If we have a hellHound spawned in the pits of hell with the ferocious ability of breathing fire:
1 // #1. A function invoked in the context of an object (a method)
2 var hellHound = {
3 attackWithFireBreath: function(){
4 console.log(this + " jumps towards you and unleashes " +
5 "his terrible breath of fire! (-3 hp, +fear)");
6 },
7 toString: function (){ return 'Hellhound';}
8 }
When we call its attackWithFireBreath method using the dot notation this will take the value of the object itself:
1 hellHound.attackWithFireBreath();
2 // => Hellhound jumps towards you and unleashes
3 // his terrible breath of fire! (-3 hp, +fear)
4 // 'this' is the hellHound object
Nothing strange here. This is the version of this we know and love from C#. Things get a little bit trickier in the next scenario.
This Unbound
In JavaScript you can do crazy things. Things like invoking a method without the context of the object in which it was originally defined. Since functions are values we can just save the attackWithFireBreath method within a variable:
1 // #2. A function invoked without the context of its object
2 var attackWithFireBreath = hellHound.attackWithFireBreath;
And invoke the function via the newly created variable:
1 attackWithFireBreath();
2 // => [object Window] jumps towards you and unleashes
3 // his terrible breath of fire! (-3 hp, +fear)
Ooops! What did just happen here? this is no longer the hell hound but the Window object. You may be asking yourself: What? And here comes the weird part that you need to remember: Whenever you invoke a function without an object as context the this automatically becomes the Window object.
The Window16 object in JavaScript represents the browser window and contains the document object model (also known as DOM) an object representation of the elements within a website.
As a cool exercise, you can now take that free function and add it to another object zandalf different from the original:
1 // we could add the same method to another object:
2 var zandalf = {
3 toString: function(){return 'zandalf';}
4 };
5 zandalf.attackWithFireBreath = attackWithFireBreath;
Then call it as a method with the dot notation:
1 zandalf.attackWithFireBreath();
2 // => zandalf jumps towards you and unleashes
3 // his terrible breath of fire! (-3 hp, +fear)
4 // => 'this' is the jaime object
And again, when we invoke the original function in the context of an object, even when it is another one different from the original, this takes the value of that object.
Let’s make a summary of what you’ve seen up until now:
- Call a function in the context of an object and
thiswill take the value of the object - Call a function without context and
thiswill take the value of theWindowobject. Unless you are in strict mode in which case it will take the value ofundefined.
This Explicitly
All functions in JavaScript descend from the Function prototype. This prototype provides two helpful methods that allow you to explicitly set the context in which to execute a function: call and apply.
Take the attackWithFireBreath function from the last example. This time, instead of calling it directly, we use its call method and pass the object zandalf as an argument:
1 attackWithFireBreath.call(zandalf);
2 // => zandalf...
3 // => 'this' is zandalf
The object zandalf becomes the context of the function and thus the value of this. Likewise, if we call the apply method on the same function and pass an object hellHound as argument:
1 attackWithFireBreath.apply(hellHound);
2 // => hell hound...
3 // => 'this' is hellHound
We can verify how the object hellHound becomes the context of the function and the value of this.
But, what happens if the original function has paremeters? Worry not! Both call and apply take additional arguments that are passed along to the original function. Take this function attackManyWithFireBreath that unleashes a terrible breath of fire on many unfortunate targets:
1 function attackManyWithFireBreath(){
2 var targets = Array.prototype.slice.call(arguments, 0);
3 console.log(this + " jumps towards " + targets.join(', ') +
4 " and unleashes his terrible breath of fire! (-3 hp, +fear)");
5 }
The call method let’s you specify a list of arguments separated by commas in addition to the value of this:
1 attackManyWithFireBreath.call(hellHound, 'you', 'me', 'the milkman');
2 // => Hellhound jumps towards you, me, the milkman and unleashes
3 // his terrible breath of fire! (-3 hp, +fear)
Likewise, apply takes an array of arguments:
1 attackManyWithFireBreath.apply(hellHound, ['me', 'you', 'irene']);
2 // => Hellhound jumps towards me, you, irene and
3 // unleashes his terrible breath of fire! (-3 hp, +fear)
And that’s how you can set the value of this explicitly. Let’s recapitulate what we’ve learned so far:
- Call a function in the context of an object and
thiswill take the value of the object - Call a function without context and
thiswill take the value of theWindowobject. Unless you are in strict mode in which case it will take the value ofundefined. - Call a function using
callandapplypassing the context explicitly as an argument andthiswill take the value of whatever you pass in.
This Bound
As of ES5, the Function prototype also provides a very interesting method called bind. bind lets you create new functions that always have a fixed context, that is, a fixed value for this 17.
Let’s use bind to set a fixed value for this in our original attackWithFireBreath function. bind will return a new function attackBound that will have this with a value of our choosing. In this case, it will be hellHound:
1 // As of ES5 we can bind the context of execution of a function
2 // FOR EVER
3 attackBound = attackWithFireBreath.bind(hellHound);
After using bind, the value of this is bound to the hellHound object even if you are not using the dot notation:
1 attackBound();
2 // => Hellhound jumps towards you and unleashes
3 // his terrible breath of fire! (-3 hp, +fear)
4 // `this` is Hellhound even though I am not using the dot notation
Moreover, if you assign the attackBound method to another object and call it using the dot notation, the attackBound method is executed in the context of the original object hellHound. That is, after binding a function to a context with bind, the context will remain the same even after assigning the function to another object:
1 // the function is bound even if I give the function to another obje\
2 ct
3 zandalf.attackBound = attackBound;
4
5 zandalf.attackBound();
6 // => Hellhound ...
7 // `this` is Hellhound even though I am using dot notation
8 // with another object
Once a function is bound it is not possible to un-bound it nor re-bind it to another object:
1 // You cannot rebind a function that is bound
2 var attackReBound = attackBound.bind(zandalf);
3
4 attackReBound();
5 // => Hellhound ...
6
7 attackBound();
8 // => hellHound ...
But you can always use the original unbound function to create new bound versions through subsequent calls to bind with different contexts:
1 // But you can still bind the original
2 var attackRebound = attackWithFireBreath.bind(zandalf);
3 attackRebound();
4 // => zandalf...
Concluding This
In summary, this can take different values based on how a function is invoked. It can:
- Be an object if we call a function within an object with the dot notation
- Be the
Windowobject orundefined(strict mode) if a function is invoked by itself - Be whichever object we pass as argument to
callorapply - Be whichever object we pass as argument to
bind.
If now that you are a this-xpert we go back to the original example you will be able to spot the problem at once. Since the updateUsers function is a callback, it is not invoked in the context of the UsersCatalog object. Callbacks are invoked as normal functions, and thus in the context of the Window object (or undefined in in strict mode). Because of this, the value of this within updateUsers wouldn’t be catalog but undefined18.
Because this is not the catalog object, it doesn’t have a users property and thus the resulting cannot read property of undefined error:
1 function UsersCatalog(type){
2 this.users = [];
3 getUsers()
4
5 function getUsers(){
6 $.getJSON('https://api.github.com/users')
7 .success(function(users){
8 this.users.push(users);
9 // BOOOOOOOM!!!!!
10 // => Uncaught TypeError:
11 // Cannot read property 'push' of undefined
12 // 'this' in this context is the jqXHR object
13 // not our original object
14 });
15 }
16 }
17 var catalog = new UsersCatalog();
You can solve this issue in either of two ways. You can take advantage of JavaScript support for closures, declare a self variable that “captures” the value of this when it refers to the UsersCatalog object and use it within the closure function as depicted below (a very common pattern in JavaScript):
1 function UsersCatalogWithClosure(){
2 "use strict";
3 var self = this;
4
5 self.users = [];
6 getUsers()
7
8 function getUsers(){
9 $.getJSON('https://api.github.com/users')
10 .success(function(users){
11 self.users.push(users);
12 console.log('success!');
13 });
14 }
15 }
16 var catalog = new UsersCatalogWithClosure();
Or you can take advantage of bind and ensure that the function that you use as callback is bound to the object that you want:
1 //#2. Using bind
2 function UsersCatalogWithBind(){
3 "use strict";
4
5 this.users = [];
6 getUsers.bind(this)();
7
8 function getUsers(){
9 $.getJSON('https://api.github.com/users')
10 .success(updateUsers.bind(this));
11 }
12
13 function updateUsers(users){
14 this.users.push(users);
15 console.log('success with bind!');
16 }
17 }
18 var catalog = new UsersCatalogWithBind();
Later within the book, you’ll see how ES6 arrow functions can also lend you a hand in this type of scenario.
Global Scope by Default and Namespacing in JavaScript
As you will come to appreciate by the end of the book, JavaScript has a minimalistic design. It has a limited number of primitive constructs that can be used and composed to achieve higher level abstractions and other constructs that are native to other languages. One of these constructs are namespaces.
Since we do not have the concept of namespaces, variables that are declared in a JavaScript file are part of the global scope where they are visible and accessible to every JavaScript file within your application. Yey! Party!
1 var dice = "d12";
2 dice;
3 // => d12
4 window.dice
5 // => d12
6 // ups... we are in the global scope/namespace
The problems with global variables are well known: they tightly couple different components of your application and they can cause name collisions. Imagine that you have several JavaScript files declaring variables with the same names but performing different tasks. Or imagine importing third party libraries that could overwrite your own variables. Chaos and destruction!! Because of these problems we want to completely avoid the use of global variables, yet we lack support for namespaces in JavaScript… What to do?
We can use objects to emulate the construct of namespaces. A commonly used pattern is depicted below where we use what we call an IIFE (immediately invoked function expression) to create/augment a namespace:
1 // IIFE - we invoke the function expression as soon as we declare it
2 (function(armory){
3 // the armory object acts as a namespace
4 // we can add properties to it
5 // these would constitute the API for
6 // the 'armory' module/namespace
7 armory.sword = {damage: 10, speed: 15};
8 armory.axe = {damage: 15, speed: 8};
9 armory.mace = {damage: 16, speed: 7};
10 armory.dagger = {damage: 5, speed: 20};
11
12 // additionally you could declare private variables and
13 // functions as well
14
15 // either augment or create the armory namespace
16 }(window.armory = window.armory || {} ));
17
18 console.log(armory.sword.damage);
19 // => 10
An immediately-invoked function expression is just that, a function expression that you invoke immediately. By virtue of being a function it creates a new scope where you can safely have your variables and avoid name collisions with the outside world. If you were to declare a variable with the same name of an existing variable in an outer scope, the new variable would just shadow the outer variable.
By immediately invoking the function you can extend the window.armory object with whichever properties you desire, creating a sort of public API for the armory object that becomes a namespace or module. A container where you can place properties and functions and expose them as services for the rest of your application.
We will come back to namespacing and higher level code organization in JavaScript within the tome on JavaScript modules.
Type Coercion Madness
In the basic ingredients of javascript-mancy you learned a little bit about type coercion in JavaScript. You learn how JavaScript provides the == and != abstract equality operators that let you perform loose equality between values and the === and !== operators that perform strict equality.
By using the first set of operators JavaScript will try to coerce the types being compared to a matching type before performing the comparison, whilst the second set of operators expect a matching type. You also learned how type coercion creates the concept of falsey and truthy by assigning true and false to different values and types when being converted to boolean.
I thought it would be interesting for you to learn a little bit more about this JavaScript feature and about its possible pitfalls.
JavaScript was designed to be an accessible language19, a language that even a layman, someone with no prior programming experience could use to create interactive websites. A welcoming language that would help anyone to write their own web applications and solve their own problems. You can see this vision clearly in many of the features of JavaScript, even in some of the most controversial ones. If you think about it from this perspective, it doesn’t feel so weird that the following statement evaluates to true:
1 > 42 == '42'
2 // => true
For is not 42 equal to '42'? Don’t both refer to the same number? Does it really matter that they have different types? And so we have implicit conversion of types.
In my experience, taking advantage of type coercion usually results in more terse code:
1 // as opposed to (troll !== null && troll !== undefined)
2 > if (troll) {
3 // do stuff
4 }
Taking advantage of the strict equality usually results in more correct, less bug-prone code:
1 > if (troll !== null && troll !== undefined){
2 // do stuff
3 }
In the first case the condition will be satisfied as long as troll has a truthy value: It could be an object, an array, a string, a number different than 0. In the second case, the condition will be satisfied whenever troll is not null nor undefined (so even it troll is equal to 0 as opposed to the previous example). Expressiveness or correctness, choose the one that you prefer.
The truthy and falsey values for the most common types are as follow (note how we use the !! to explicitly convert every value to booleans). Both arrays and objects are truthy, even when they are empty:
1 > !![1,2,3]
2 // => true
3 > !![]
4 // => true
5 > !!{message: 'hello world'}
6 // => true
7 > !!{}
8 // => true
A non-empty string is truthy while an empty string is falsey:
1 > !!"hellooooo"
2 // => true
3 > !!""
4 // => false
Numbers are truthy but for 0 that is falsey:
1 > !!42
2 // => true
3 > !!0
4 // => false
undefined and null are always falsey:
1 > !!undefined
2 // => false
3 > !!null
4 // => false
Using JavaScript in Strict Mode
From ES5 onwards you can use strict mode to get a better experience with JavaScript. One of the main goals of strict mode is to prevent you from falling into common JavaScript pitfalls by making the JavaScript runtime more proactive in throwing errors instead of causing silent ones or unwanted effects.
Take the example of the value of this in callbacks. Instead of setting the value of this to the Window object, when you use strict mode the value of this becomes undefined. This little improvement prevents you from accessing the Window object or extending it by mistake, and will alert you with an error as soon as you try to do it. Short feedback loops and failing fast are sure recipes for success.
Other improvements that come with strict mode are:
- trying to create a variable without declaring it (with
var,letorconst) will throw an error. Without strict mode it will add a property to theWindowobject. - trying to assign a variable to NaN, or to a read-only or non-writable property within an object throws an exception
- trying to delete non-deletable properties within an object throws an exception
- trying to have duplicated names as arguments throws a syntax error
- and more explicit errors that will help you spot bugs faster
Additionally with strict mode enabled the JavaScript runtime is free to make certain assumptions and perform optimizations that will make your code run faster. If you want to learn more about the nitty-gritty of strict mode I recommend that you take a look at the MDN (Mozilla Developer Network), the best JavaScript resource in the web.
Enabling Strict Mode
You can enable strict mode by writing 'strict mode'; at the top of a JavaScript file. This will enable strict mode for the whole file:
1 'strict mode';
2 // my code ...
3 var pouch = {};
Alternatively, you can use the strict mode declaration at the top of a function. This will result in the strict mode only being applied within that function:
1 (function(){
2 'strict mode';
3 // my code ...
4 var bag = {};
5
6 }());
Wrapping your strict mode declarations inside a function will prevent the strict mode from being applied to code that may not be prepared to handle strict mode. This can happen when concatenating strict mode scripts with non-strict mode scripts like external third party libraries outside of your control.
ES6 modules always use strict mode semantics.
Concluding
In this chapter you learned about the weirdest bits of JavaScript, the mysterious JavaScript Arcana. You started the chapter by reviewing parts of the JavaScript Arcana that you read about in previous chapters: function scope and variable hoisting, array-like objects and function overloading.
You continued taking a look at the sneaky this keyword, and understood how its value depends on the context in which a function is executed:
- If you invoke a function as a method using the dot notation, the
thisvalue will be the object that holds that method. - If you call a function directly the value of
thiswill be theWindowobject (orundefinedin strict mode). - If you call a function using either
call,applyorbind, the value ofthiswill be set to the object that you pass as argument to either of these functions. - You can use
bindto create a new version of a function that is bound to a specific object. That is, in that new funtionthisbecomes the object for all eternity.
You saw how JavaScript assumes global scope by default and how you can achieve a similar solution to namespaces by using objects to represent them and organize your code. You examined the concept of IIFE (Immediately Invoked Function Expression) and how you can use it to create an isolated scope to declare your variables and add them to a namespace object.
After that you reviewed type coercion in JavaScript to finally wrap the chapter examining strict mode, a more restricted version of JavaScript that attempts to help you find bugs faster by failing more loudly.