WHAT’S IN THIS CHAPTER?
There are enough syntactic similarities between JavaScript and C# or Java that many developers don’t think twice about jumping in and writing JavaScript using the same concepts and constructs that they’ve used to build C# or Java programs.
Taking such an approach, however, invariably leads to unwieldy JavaScript code that has neither the type safety of C# and Java nor the elegance of good JavaScript. In order to create truly reliable JavaScript applications, you must understand the quirks that make JavaScript what it is: a flexible, powerful, and elegant programming language.
While this book explicitly is not a JavaScript primer, there are some features of the JavaScript language that are worthy of explicit review. This chapter highlights some of the unique, idiomatic aspects of JavaScript that were used in the preceding chapters.
JavaScript has only five primitive types: String
, Number
, Boolean
, Undefined
, and Null
. Everything else, including functions, is an object
. This section revisits some of the idiomatic aspects of JavaScript objects that we’ve used in the book.
A JavaScript object’s dynamic nature means it may be manipulated in interesting ways. Generally speaking, once an object has been created in C# or Java, that object’s properties are set in stone; it isn’t possible to add or remove properties at will. JavaScript objects usually aren’t bound by the same restriction. In fact, it’s common to add properties to and remove properties from an object after it has been created.
For example, Listing 11-5 in Chapter 11 leveraged the dynamic nature of JavaScript objects to add and remove tools for use within a Conference.WidgetSandbox
. The following example highlights the relevant portions of Listing 11-5.
describe("Conference.WidgetSandbox", function(){
describe("Constructor function", function(){
var widgetFcnSpy;
beforeEach(function(){
// Add test tools so the tests aren't dependent upon
// the existence of actual tools
Conference.WidgetTools.tool1 = function(sandbox){
return {};
};
Conference.WidgetTools.tool2 = function(sandbox){
return {};
};
// create a spy that may be used as the widget function
widgetFcnSpy = jasmine.createSpy();
});
afterEach(function(){
// remove the test tools
delete Conference.WidgetTools.tool1;
delete Conference.WidgetTools.tool2;
});
// *** Code omitted for brevity ***
});
The properties of a JavaScript object may be accessed using dictionary syntax. For example, the following assignment statements are functionally equivalent:
var obj = { };
obj.myProperty = 'Property value';
obj['myProperty'] = 'Property value';
The capability to access their properties in dictionary fashion make JavaScript objects natural candidates for use as caches, as the following excerpt from Listing 14-6 in Chapter 14 illustrates:
Conference.attendeeProfileProxy = function(
attendees, profileService, prefetchLimit) {
var ix,
prefetched = {};
function prefetch(attendeeId) {
prefetched[attendeeId] = profileService.getProfile(attendeeId);
}
// *** Code omitted for brevity ***
};
There are some key idiomatic aspects of variables in JavaScript that must be understood in order to create reliable JavaScript code. This section reviews two of those aspects: hoisting and scoping.
What will the output of the following C# code sample be?
static void WriteValue()
{
value = "this is my value";
string value;
System.Diagnostics.Debug.WriteLine(value);
}
WriteValue();
If you said: “Nothing, it won’t even compile,” you’re correct. In C#, a variable can’t be used before it has been declared. In fact, the C# compiler refuses to create an executable for you because attempting to assign a value to a variable before the variable has been declared generates an error.
Suppose, however, that the C# compiler didn’t protect you from referencing a variable before it’s declared. When the previous sample executed, you’d expect it to generate some sort of error when it attempted to execute the first line, which references the as yet undeclared variable value
.
Now consider the following analogous JavaScript example. What do you expect its output to be?
function writeValue(){
value = "This is my value";
var value;
console.log(value);
}
writeValue();
Though it may be counterintuitive, “This is my value” is written to the console. What type of magic is this?
The preceding sample successfully executes because the JavaScript interpreter hoists all variable declarations to the top of the function that they’re contained in. Because of hoisting, that sample is functionally equivalent to the following one:
function writeValue(){
var value;
value = "This is my value";
console.log(value);
}
writeValue();
It’s important to note that only the declaration of a variable is hoisted. If a variable is declared and initialized in the same step, as in this example:
function writeValue(){
console.log(value);
var value = "This is my value";
}
writeValue();
application of hoisting yields the following:
function writeValue(){
var value;
console.log(value);
value = "This is my value";
}
writeValue();
The declaration of value
is hoisted to the top of the function, but initialization of value
is left in its original location. As such, this example will output “undefined,” the value of a declared but uninitialized variable.
Many of the examples in the book gather all variable declarations at the top of their containing function, such as this example from Listing 20-15 in Chapter 20:
var Conference = Conference || {};
Conference.attendee = function(firstName, lastName){
var checkedIn = false,
first = firstName || 'None',
last = lastName || 'None',
checkInNumber;
//*** Code omitted for brevity ***
}
Adopting the practice of declaring variables at the top of functions will eliminate hoisting-related surprises.
A discussion of JavaScript variables wouldn’t be complete without a discussion of scoping. Unlike C# and Java, in which variables are scoped to the block they’re declared in, JavaScript variables are scoped to the function they’re declared in.
The following example illustrates block scoping in C#:
static void BlockScope()
{
string variable1 = "Outer scope";
// Only variable1 is in-scope
if (!string.IsNullOrEmpty(variable1))
{
string variable2 = "Inner scope";
// both variable1 and variable 2 are in-scope
System.Diagnostics.Debug.WriteLine(variable1 + " " + variable2);
}
// Only variable1 is in-scope
System.Diagnostics.Debug.WriteLine(variable1);
}
In the preceding example, variable2
is only accessible within the if
block in which it is declared and variable1
is available within the entire function block.
The following example illustrates a similar function written in JavaScript:
function functionScope(){
var variable1 = "Outer scope";
if(variable1){
var variable2 = "Inner scope (not really)";
// variable2 is scoped to the function, not the if block
console.log(variable1 + " " + variable2);
}
// both variable1 and variable2 are in-scope
console.log(variable1+ " " + variable2);
}
Although the preceding example declares variable2
within an if block, variable2
is scoped to the entire function, not just the containing if block. In fact, the JavaScript interpreter hoists the declaration of variable2
out of the if block to the top of the function.
Another place C# and Java developers may be surprised by function versus block scoping is for
loops. It’s common in C#, for example, to write for
loops with the indexing variable declared within the for
statement:
for(int index = 0; index < 100; index++){
// index is scoped to the for block
}
Declaring the indexing variable within the for
statement in JavaScript—as in for(var index = 0; index < 100; index++)
—doesn’t scope the index
variable to the for
block. Instead, index
is scoped to the function, and its declaration is hoisted to the top of the function. As such, it’s common to see the indexing variable declared (and often initialized) outside of the for
statement.
Separate declaration of a for
loop’s indexing variable is illustrated in Listing 18-13 from Chapter 18:
var Conference = Conference || {};
Conference.polyfills = Conference.polyfills || {};
Conference.polyfills.arrayForEach = function(callbackFcn, thisObj){
var i;
if (typeof callbackFcn !== "function") {
throw new Error(callbackFcn + ' is not a function');
}
for(i = 0; i < this.length; i++){
callbackFcn();
}
};
Linting tools such as ESLint and JSHint include rules that alert you when they detect JavaScript code that looks like it’s attempting to use block scoping.
JavaScript’s function scoping is also useful because it allows data to be protected from access, providing functionality similar to private and protected class variables in C#.
The Conference.simpleCache
module from Listing 9-4 in Chapter 9 illustrates the data-hiding capabilities provided by function scoping:
var Conference = Conference || {};
Conference.simpleCache = function(){
var privateCache = {};
function getCacheKey(key){
return JSON.stringify(key);
}
return {
// Returns true if key has an entry in the cache, false if
// it does not.
hasKey: function(key){
return privateCache.hasOwnProperty(getCacheKey(key));
},
// Stores value in the cache associated with key
setValue: function(key, value){
privateCache[getCacheKey(key)] = value;
},
// Returns the cached value for key, or undefined
// if a value for key has not been cached
getValue: function(key){
return privateCache[getCacheKey(key)];
}
};
};
The variable privateCache
is declared within the Conference.simpleCache
function and thus is only in scope within that function. By returning an object that defines functions that access privateCache
, the Conference.simpleCache
has provided an interface to interact with privateCache
while disallowing direct manipulation of privateCache
.
Functions in JavaScript are different beasts than functions in languages such as C# and Java. This section summarizes some of the key features and capabilities of JavaScript functions.
Functions in JavaScript are objects, meaning they have all the capabilities of “regular” objects, and they can be executed. Functions may be assigned to variables and passed as arguments to other functions.
Use of functions as arguments was demonstrated in numerous examples in this book, including many in Chapter 5.
As objects, functions may also have properties. Many of the module functions in this book used this capability to expose a messages
property. An example, taken from Listing 10-7 in Chapter 10, is shown here:
var Conference = Conference || {};
Conference.presentationFactory = function presentationFactory() {
'use strict';
return {
//*** Code omitted for brevity ***
};
};
Conference.presentationFactory.messages = {
unexpectedProperty: 'The creation parameter had an unexpected property '
};
As it does with variables, the JavaScript interpreter hoists function definitions. There’s a bit of additional detail to be aware of, however.
When a function is defined via a function declaration, the function’s definition is hoisted along with the declaration. This hoisting behavior means the following code will output “myFunction body”:
myFunction();
// function declaration and definition is hoisted
function myFunction(){
console.log("myFunction body");
}
When a function is defined via a function expression, however, only the declaration of the function variable is hoisted. The following will result in an error:
myFunction();
var myFunction = function(){
console.log("myFunction body");
}
That's because hoisting makes the previous statement equivalent to this
var myFunction;
myFunction();
myFunction = function(){
console.log("myFunction body");
}
which attempts to execute myFunction
when its value is undefined
.
You must specify a return type when defining a function in C# or Java, or you need to indicate that the function doesn’t return a value. In C#, a function that doesn’t return a value to its caller may be defined like so:
void NoReturnValue()
{
// this function doesn't return anything
}
JavaScript, on the other hand, doesn’t provide a mechanism by which the return type of a function may be specified. The definition of a function in JavaScript doesn’t even indicate whether a value will be returned from the function.
A JavaScript function without a return
statement, or a return
statement without value, will always return a value of undefined
:
function noReturn(){
};
var fromNoReturn = noReturn();
console.log(fromNoReturn === undefined); // true
function bareReturn(){
return;
}
var fromBareReturn = bareReturn();
console.log(fromBareReturn === undefined); // true
Anonymous functions, especially those without a reference by which they may be invoked, are a staple of idiomatic JavaScript. One of the most common uses of anonymous functions is as callbacks participating in the Callback Pattern.
Listing 5-6 in Chapter 5, repeated in the following sample, shows an extreme case of anonymous functions being passed as callback functions.
CallbackArrow = CallbackArrow || {};
CallbackArrow.rootFunction = function(){
CallbackArrow.firstFunction(function(arg){
// logic in the first callback
CallbackArrow.secondFunction(function(arg){
// logic in the second callback
CallbackArrow.thirdFunction(function(arg){
// logic in the third callback
CallbackArrow.fourthFunction(function(arg){
// Logic in the fourth callback
});
});
});
});
};
CallbackArrow.firstFunction = function(callback1){
callback1(arg);
};
CallbackArrow.secondFunction = function(callback2){
callback2(arg);
};
CallbackArrow.thirdFunction = function(callback3){
callback3(arg);
}
CallbackArrow.fourthFunction = function(callback4){
callback4(arg);
};
If you’re a C# programmer, you may see that anonymous functions in JavaScript are similar to lambda expressions in C#.
Defining one function inside of another is a common and useful practice in JavaScript. Consider this excerpt from Listing 21-1 in Chapter 21:
var Conference = Conference || {};
Conference.recentRegistrationsService = function(registrationsService){
var service = {
//*** Code omitted for brevity ***
},
getNewAttendees = function getNewAttendees(){
// calls the server and retrieves and returns a promise of an
// array of the attendees that registered since the last time it
// polled.
return new Promise(function(reject, resolve){
resolve([]);
});
},
pollingProcess = setInterval(function pollForNewAttendees(){
getNewAttendees().then(function processNewAttendees(newAttendees){
newAttendees.forEach(function updateWithNewAttendee(newAttendee){
service.updateObservers(newAttendee);
});
});
}, 15000);
return service;
};
In the example, the function getNewAttendees
is defined within the function Conference .recentRegistrationsService
. Given JavaScript’s scoping rules, getNewAttendees
is scoped to Conference.recentRegistrationsService
. As such, getNewAttendees
may only be invoked from within Conference.recentRegistrationsService
. Leveraging JavaScript’s nested functions and scoping rules allow the creation of functions (and variables) that are private to the function in which they’re defined.
When interviewing developers for our team, one of the questions we regularly ask is: “What happens if you execute a JavaScript function with more arguments than the function expects?” We also ask the inverse: “What happens if you execute a JavaScript function with fewer arguments than the function expects?”
Interviewees answer with: “errors are generated” more often than either of us expected. Had we been asking about C#, the answer wouldn’t be far from the mark; code that calls a C# function with too many or too few parameters won’t even compile.
JavaScript, however, happily executes a function with whatever arguments you give it. Within the body of the function, any extra arguments are ignored, and omitted arguments have a value of undefined.
This flexible argument handling allows for functionality similar to function overloading in C#. In C#, multiple functions specifying different numbers of parameters are defined:
void OverloadedFunction(int argOne)
{
// function body
}
void OverloadedFunction(int argOne, int argTwo)
{
// function body
}
void OverloadedFunction(int argOne, int argTwo, string argThree)
{
// function body
}
In JavaScript, overloading may be simulated by defining a function that defines the maximum number of expected parameters, and then checking to see if the arguments provided have a value:
function simulatedOverloading(var argOne, var argTwo, var argThree){
if(argOne !== undefined){
// argOne provided
}
if(argTwo !== undefined){
// argTwo provided
}
if(argThree !== undefined){
// argThree provided
}
}
JavaScript also provides the special array-like variable arguments
, which contains all of the arguments provided to the function by the caller. The arguments
special variable is what enabled the WidgetSandbox
constructor function developed in Chapter 11 to accept any number of tool name arguments.
Immediately invoked function expressions (IIFEs) are, as their name implies, JavaScript functions that are defined and immediately executed. Use of an IIFE ensures that a function is executed only once, and also has the benefit of establishing function-local variables.
Listing 14-8 in Chapter 14 uses an IIFE to both ensure the function named prefetchAll
is executed only once and the variable sortedAttendees
is isolated from the outer scope. An excerpt from Listing 14-8 follows:
var Conference = Conference || {};
Conference.attendeeProfileProxy = function(
attendees, profileService, prefetchLimit) {
//*** Code omitted for brevity ***
(function prefetchAll() {
var sortedAttendees = //*** omitted ***
})();
};
IIFEs are also used to avoid variable naming collisions. For example, IIFEs are commonly used to ensure that $
is bound to the jQuery
global variable in this manner:
(function($){
// Function is immediately invoked, providing jQuery as the argument
// for the $ parameter.
// Guarantees that $ bound to the jQuery global variable within this
// function, even if $ is bound to another value elsewhere.
})(jQuery);
Also, IIFEs may be used to create static modules, as the following code from Listing 3-2 in Chapter 3 illustrates:
var MyApp = MyApp || {};
MyApp.WildlifePreserveSimulator = (function() {
var animals = [];
return {
addAnimal: function(animalMaker,species, sex) {
animals.push(animalMaker.make(species,sex));
},
getAnimalCount: function() {
return animals.length;
}
};
}()); // <--Immediate execution!
Because MyApp.WildlifePreserveSimulator
’s module function is immediately executed, MyApp .WildlifePreserveSimulator
becomes a singleton object. The object exposes two methods, addAnimal
and getAnimalCount
, which manipulate the animals
variable. Because the animals
variable is defined within the scope of the module function, it’s protected from direct access.
This section reviews two important aspects, type coercion and truthy and falsy values.
What is the output of the following JavaScript sample?
var v1 = 1;
var v2 = "1";
console.log(v1 == v2);
If you have a background in C# or Java, you might have said “false.” That answer, while logical, is incorrect.
Unlike C# and Java, JavaScript applies automatic type coercion to the operands of the ==
and !=
operators. It “helpfully” converts one of the operands to be the same type as the other before comparing the values of the operands. In the case of the preceding sample, the output is, perhaps unexpectedly, true
.
In JavaScript: The Good Parts (O’Reilly Media, 2008), Douglas Crockford calls the ==
and !=
operators the evil twins of their non-coercing siblings: ===
and !==
. The non-coercing operators don’t perform any automatic casting, so in order for two values to be equal, the values must have the same type. Altering the example to use ===
rather than ==
yields the answer you likely anticipated: false
.
From the standpoint of reliability, we favor explicit over automatic. We used ===
and !==
equality and inequality comparisons throughout the samples in this book to avoid automatic type coercion, and we recommend that you adopt the same practice. As Chapter 23 illustrated, linting tools such as ESLint can help you adhere to the practice.
Many of the examples we presented contained conditional statements such as the following:
if (!registry[contractName]) {
throw new Error(this.getMessageForNameNotRegistered(contractName));
}
Is the preceding example, extracted from Listing 16-8 in Chapter 16, checking to see if registry[contractName]
is false
? No, it isn’t.
The extracted conditional’s purpose is to determine if the registry[ContractName]
is undefined
, which JavaScript considers to be falsy. Falsy values evaluate as false
when used within a conditional expression, and truthy values evaluate to true
when used within a conditional expression.
JavaScript considers these values falsy:
false
0
''
(empty string)null
undefined
NaN
and all other values as truthy. An understanding of truthy and falsy values is important when writing—and reading—idiomatic, reliable JavaScript.
While JavaScript may have been derided as a toy language in the past, it is now considered to be just as suitable for large-scale projects as its strongly-typed compiled cousins.
In order to harness the capabilities of JavaScript, the language’s quirks and idioms must be understood and appreciated. Behaviors such as hoisting and type coercion, and capabilities such as dictionary property access and immediate function execution make JavaScript different from C# and Java, but no less powerful.