JavaScript is an essential part of the modern web. But like any technology this language has some awkward corners.
01-06-2021
JavaScript is an essential part of the modern web. But like any technology this language has some awkward corners.
Man of Constant Sorrow
Does a Constant do exactly what it says on the tin? These number and string constants do behave as expected. They cannot be reassigned. Attempting to change them throws a run-time error.
const ARTIST = "Bob Dylan";
const BORN = 1941;
ARTIST = "Dave Smith"; // Error
BORN = 1984; // Error
...but things become more nuanced when we work with objects and arrays:
const CAPITAL = { name:"Rome",temp:24 };
We cannot reassign this object constant.
CAPITAL = { name:"Paris",temp:17 }; // Error
But we can change its contents. This code does not throw an error.
CAPITAL.name = "Paris";
CAPITAL.temp = 28;
The same behaviour applies to constant Arrays.
const LOTTO = [4,5,6,7];
We cannot reassign the Constant but we can change its contents.
This Mozilla help page gives more detailed coverage on the logic of comparator functions and sort.
Copycat
Possibly the biggest source of bugs in JavaScript arises from not understanding the ideas of copy by reference and copy by value.
When you make a copy of a string or number variable this will create a separate independent variable known as copy-by-value.
let total = 10;
let result = total;
result++;
// total equals 10, result equals 11.
When making a copy of an array or object you need to be intentional in your code. This simple assignment will create two pointers to the same object known as copy by reference.
let movie = { name:"North By NorthWest" };
let film = movie;
film.name = "Jaws";
// Both film and movie contain { name:"Jaws" }
The JavaScript ES6 destructuring operator provides an elegant solution. This code breaks an object down into its component properties and then wraps them in a new object
{...movie}
We can refactor the previous code to create two separate objects.
let movie = { name:"North By NorthWest" };
let film = {...movie, name:"Jaws" ];
We can apply the same destructuring techniques to arrays
let lottery = [4,5,6,7,8];
let lotto = [...lottery];
The Power of Equality
In JavaScript every object is unique. One consequence is that we need to think carefully about object comparison. These two objects contain the same contents but can never be the same. Comparison needs to happen at the property level.
let italy = { capital:"Rome" };
let italia = { capital:"Rome" };
italy === italia; // false
italy.capital === italia.capital; // true
Object comparison will always fail even if the objects are empty.
{} === {}; // false
Once we understand that objects are unique, we also need to recognise the situation where object comparison returns a true value..
let italy = { capital:"Rome" };
let italia = italy;
This code is an example of copy by reference. Both variables (italy, italia) are pointers to the same object. In this case object comparison will return true. We are comparing the SAME object.
italia === italy; // true
Once Around the Block
Scope can be a big stumbling block in JavaScript. You might reasonably assume the scope of variable j to be defined by the for-loop that contains it.
for ( var j=0; j<10; j++ ) {
console.log(j);
}
// Variable j still exists here.
But variables defined with the var keyword do not have block scope. Variable j continues to exist after the for-loop. To confuse matters further, variables defined with var are hoisted and come into existence at the start of their containing scope. You can solve both the block scope and hoisting problems with one word. Use keyword LET instead of VAR to define variables. It has block-scope and does not hoist.
This Is Your Life
The scope rules surrounding the keyword this in JavaScript are very complex. If you encounter bugs, the presence of keyword this should raise a red flag. Gaining an understanding of how this behaves is a key step on your learning path as a JavaScript developer. In this example we define an ES6 class.
class Movie {
constructor(f){
this.film = f;
}
about()
{console.log(this.film);
}
}
We instantiate an object from the class and call the about method. Everything works. The name of the movie appears in the console.
const taxi = new Movie("Taxi Driver");
taxi.about();
If we modify the method call and invoke the method differently things go wrong. This code calls the about method after a one second delay.
But in the console undefined is displayed, not the name of the film.
The setTimeout function has changed the runtime value of this inside the about method so that it points to the global namespace (named Window in JavaScript.)
To solve this obscure problem, we can refactor the about method. JavaScript has been intentionally designed so that class methods which are written using arrow syntax will lexically bind the value of this correctly to the containing class.
about = () => console.log(this.film);
The about method will now work correctly in both these calls:
Of all the topics covered in this article working with keyword this is clearly the most challenging.
Editor's note - you may notice some of this code is screenshotted - it was breaking our CMS!
Sonic Reducer
Reduce is a functional programming method which applies a function to every element of an array and reduces that array to a single value.
The custom reducer function that you write works on pairs of values and takes two arguments. This example finds the larger of two numbers.
const larger = (a,b) => Math.max(a,b)
We apply this function to an array using reduce to find the largest item.
This code works but we need to handle the edge case where the array is empty. The code below will throw a runtime error.
sequence = [];
largest = sequence.reduce( larger );
This problem can occur when we define an empty array and then populate it with data returned from an asynchronous AJAX call. If reduce is invoked before the AJAX call completes this error will occur. The solution is to pass a second argument with an initial value.
largest = sequence.reduce( larger,0 );
The largest variable will contain zero if the array is empty. No error is thrown.
Function At The Junction
Here is a simple arrow function that calculates the area of a room.
This code works but contains a serious bug. We never declare variable area using either const or let. JavaScript will create an implied global. We have created a variable named area with global scope.
window.area; // contains 24
One obvious solution is to always declare variables with let or const. A second useful tool is to enable JavaScript strict mode. This will eliminate this silent error. A run-time error will be thrown when the function is called.
'use strict';
Fetch
Modern browsers include the Fetch API which enables you to make AJAX requests for JSON data from your JavaScript code.
We pass Fetch a path and it returns a Promise which we handle using a then method.
fetch("http://somePath/data.json")
.then( data => console.log(data));
However this code does not log the data. Instead it displays an HTTP response object describing the outcome of this call. We need to add a second step using a json() method to extract data from the HTTP response. This requires chaining a second then method to get at the actual data.
We maintain a robust range of hands-on training courses covering Coding, Data Science, DevOps, Security and more - available for on-site and online delivery as part of a full-stack training programme or as short standalone workshops. We would love to discuss your learning needs - get in touch for a no-obligation scoping chat.
We use cookies on our website to provide you with the best user experience. If you're happy with this please continue to use the site as normal. For more information please see our Privacy Policy.