What I learned in Boating School: Deep Dive into Javascript
Where I started
In July I started working as a developer for a SaaS company. I was tasked with updating and maintaining a Node.js Android/iOS application. This is written using a UI toolkit and Cordova plugins that compile one Javascript codebase into Android/iOS native code.
Most people interested in Javascript have probably heard of Node.js. For those who haven't essentially it is Google Chromes v8 Javascript engine spruced up with some libraries to make it conducive for server-side programming.
What is CommonJS?
Node.js has also extended traditional Javascript a module specification specific to the Node platform. This ‘dialect’ of Javascript is referred to as CommonJS or cjs.
A node module looks a little something like this:
Issues with CommonJS
As Javascript has increasingly been used to write large scale applications the short comings around the languages design becomes more apparent.
The usage of var
Some common issues are var
and how is not ‘block-scoped’. A block refers to the code inside {} when in an if
, for
, or function
. When a variable is declared with var
the declaration gets hoisted
as if it was declared at the beginning of a file but is labeled as undefined
until it is actually given a value at the point of execution you actually declared it. While this intended to make development easier, it can grow complicated if you wish to reuse the same variable name multiple times in a function, such as if you had a series of for loops that use i
as the iterator:
for(var i=0; 0 < list.length; i++) { ... }
for(var i=0; 0 < anotherlist.length; i++) { ... }
In some instances, the 2nd for loop would only iterate once because the value of i
will have been preserved from the previous for loop because it is not ‘block-scoped’ and holds value even after the first for loop terminates.
Messy Class Syntax
Objects in Javascript are not the same as they are in traditional C derived Object-Oriented programming languages. Essentially, objects in Javascript are just functions, these functions can store properties as member objects and can have functionality added through prototypes
. For example, here is a class for a Person
:
function Person(name) {
this.name = name; //declares a Person class that stores a name
}
If you wanted to add a method, such as to say ‘hello’ you would prototype this function:
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
return "Hello! " + "I am " + this.name;
}
While functionally OOP features such as static variables and inheritance can be achieved the way it is achieved is very different than other languages and can look messy and convoluted.
Where is ‘this’ pointing?
Another issue with building classes in cjs is the true meaning of this
. When you use this
traditionally you are referring to the parent class you are writing code under. This means you can store a class-wide variable and reuse it in other member functions. In the above example, this.name
was used to store a Person
's name for use in other methods.
But what if a method calls another function from another module?
function Person(name) {
this.name = name;
this.photo = null;
}
Person.prototype {
sayHi: function() {
return "Hello! " + "I am " + this.name;
}
getPhoto: function(name) {
util.downloadPhoto(url, function(data) {
this.photo = data; // problem lies here
});
}
}
util.getPhoto
accepts a callback as its 2nd argument. This means, as the function executes it will pass data back up to the calling function and pass it a variable we called data
. In side the callback, this
changes meaning because it is scoped to the function, we cannot modify the photo in the Person
class!
One solution for this is to store the meaning of this
into a variable at the beginning of the callback function, this eliminates the ambiguity. For more information on this you can read about lexical closures in Javascript.
What is ECMAScript 6?
Web browser manufacturers have to agree on how they implement their Javascript interpreters, they have to support the same language features otherwise web applications wouldn't work on all different browsers.
Stakeholders in Web Technologies worked to update Javascript syntax by adding modern language features to Javascript. These new features are a part of a new Javascript ‘dialect’ called ES6. ES6 written in 2015 is almost universally supported at this point.
Useful features of ES6
Let declarations
CommonJS uses var
and const
as variable declarations. As I mentioned earlier variables declared using var
get ‘hoisted’ at runtime which can cause some hard to see errors. ES6 introduces let
which behaves just like var however the variable is ‘block-scoped’. This means that if you declare x
with let x = 0;
inside of a pair of { } brackets, whenever execution leaves those brackets x is destroyed.
Generally, from this point on developers should be using let
unless they explicitly want to exploit one of the properties of using var
. This is especially true when using for
loops.
Import Syntax
ES6 actually defines it's own module system separate than what Node.js uses. This is referred to as import
syntax versus node's require
syntax. In practice, they both work very similarly, however es6 goes beyond this and supports partial imports and exports to help simplify using modules’ code.
Arrow Syntax
Arrow Syntax is shorthand for declaring ‘anonymous functions’, these are commonly used when using function callbacks. However arrow functions go above and beyond and handle ‘lexical closures’ for you. This means inside of a callback, when using arrow functions, the meaning of this
is preserved for you and behaves as one would expect. Lets look at a common pattern when using fetch
with no special options
var self = this; // lexical closure to handle the inner scope
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
self.photo = data;
});
And with arrow functions:
fetch(url).then((response) => {
return response.json();
}).then((data) => {
this.photo = data.photoURL;
});
The arrow function handles the scoping of this
for us, as well as providing users with shorter, cleaner syntax.
You can simplify this further, however I recommend not using the shorthand until you've played with the full syntax:
fetch(url).then(response => response.json())
.then(data => {
this.photo = data.photoURL;
this.name = data.name;
});
Note: fetch
uses a 2nd parameter to override the default HTML headers and body. To read up more on fetch I prefer Google's tutorial as it uses .then()
instead of async/await
.
I would also recommend knowing a little bit of HTTP programming if you need to http PUT
, or pass in data to the body of the request.
String Templates
If you have a background with Python or C's printf()
you're probably familiar with string templating. This syntax sugar makes programmatically building strings easier to read. Lets look back at the Person class and it's sayHello()
function.
sayHi: function() {
return "Hello! " + "I am " + this.name;
}
can become
sayHi: function() {
return `Hello! I am ${this.name}`;
}
While the benefits are not as clear on such a simple statement, you are working with several api's and have to build complex URL's with querystring parameters template syntax becomes very useful as they are more representative of the strings we are actually building.
Cleaner Class Syntax
ES6 also introduces classes
. In reality ES6 classes, in the background, are still traditional JS prototype
based functions. However, they are abstracted away into a form that is more recognizable to programmers coming from different languages. Lets apply this to the Person
class:
function Person(name) {
this.name = name;
this.photo = null;
}
Person.prototype {
sayHi: function() {
return "Hello! " + "I am " + this.name;
}
getPhoto: function(name) {
util.downloadPhoto(url, function(data) {
var self = this;
self.photo = data;
});
}
}
Can become:
class Person {
constructor(name) {
this.name = name
}
sayHi() {
return `Hello I am ${this.name}`;
}
getPhoto(name) {
util.downloadPhoto(url, (data) => {
this.photo = data;
});
}
}
Even in such a small example you can see how combining es6 features can make code much more readable.