Web Devolution

Essential JavaScript Concepts

December 07, 2018

JavaScript

In my own study of JavaScript, I thought I’d put together a list of core concepts in JavaScript every web developer should know. This list features theoretical aspects of JavaScript and no code snippets, the reason being so that the reader can focus more on understanding why the concepts exist as opposed to looking at extensive blocks of code which can confuse and obfuscate the meaning behind each idea.

Much of the list is comprised from these key sources:

What is JavaScript?

JavaScript is a high-level, JIT-compiled, dynamic, weakly-typed, multi-paradigm programming language. As a multi-paradigm language, JavaScript supports event-driven, declarative (functional), and imperative (object-oriented and prototype-based) programming styles. Alongside HTML and CSS, JavaScript is one of the three core technologies of the Internet.

Scope

Scope in JavaScript refers to the current context of code, which determines the accessibility of variables to JavaScript. The two types of scope are local and global:

  • Global variables are those declared outside of a block.
  • Local variables are those declared inside of a block.

Using local scope, we can actually create new variables with the same name as a variable in an outer scope without changing or reassigning the original value. Variables declared with the var keyword are always function-scoped, meaning they recognize functions as having a separate scope. This locally-scoped variable is therefore not accessible from the global scope.

The new keywords let and const, however, are block-scoped. This means that a new, local scope is created from any kind of block, including function blocks, if statements, and for and while loops.

It is generally recommended that you declare variables that are block-scoped, as they produce code that is less likely to unintentionally override variable values.

Hoisting

After using var to declare a variable, you initialize it with a value. After declaring and initializing, you can then access or reassign the variable. If you attempt to use a variable before it has been declared and initialized, it will return undefined. However, if you omit the var keyword, you are no longer declaring the variable, only initializing it. It will return a ReferenceError and halt the execution of the script.

The reason for this is due to hoisting, a behavior of JavaScript in which variable and function declarations are moved to the top of their scope. Since only the actual declaration is hoisted, not the initialization, the value in the first example returns undefined.

Closure

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.

Closures are useful because they let you associate some data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow us to associate some data (the object’s properties) with one or more methods.

Consequently, you can use a closure anywhere that you might normally use an object with only a single method. Situations where you might want to do this are particularly common on the web. Much of the code we write in front-end JavaScript is event-based — we define some behavior, then attach it to an event that is triggered by the user (such as a click or a keypress). Our code is generally attached as a callback: a single function which is executed in response to the event.

Languages such as Java provide the ability to declare methods private, meaning that they can only be called by other methods in the same class.

JavaScript does not provide a native way of doing this, but it is possible to emulate private methods using closures. Private methods aren’t just useful for restricting access to code: they also provide a powerful way of managing your global name-space, keeping non-essential methods from cluttering up the public interface to your code.

Closure seems to the unenlightened like a mystical world set apart inside of JavaScript which only the few bravest souls can reach. But it’s actually just a standard and almost obvious fact of how we write code in a lexically scoped environment, where functions are values and can be passed around at will.

Closure is when a function can remember and access its lexical scope even when it’s invoked outside its lexical scope.

Closures can trip us up, for instance with loops, if we’re not careful to recognize them and how they work. But they are also an immensely powerful tool, enabling patterns like modules in their various forms.

Modules require two key characteristics: 1) an outer wrapping function being invoked, to create the enclosing scope 2) the return value of the wrapping function must include reference to at least one inner function that then has closure over the private inner scope of the wrapper.

Now we can see closures all around our existing code, and we have the ability to recognize and leverage them to our own benefit!

The enlightenment moment should be: oh, closures are already occurring all over my code, I can finally see them now. Understanding closures is like when Neo sees the Matrix for the first time.

— Kyle Simpson

The Matrix

Asynchronous Programming

In a synchronous programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk).

Asynchronous programming makes it possible to express waiting for long-running actions without freezing the program during these actions. JavaScript environments typically implement this style of programming using callbacks, functions that are called when the actions complete. An event loop schedules such callbacks to be called when appropriate, one after the other, so that their execution does not overlap.

Programming asynchronously is made easier by promises, objects that represent actions that might complete in the future, and async functions, which allow you to write an asynchronous program as if it were synchronous.

Callbacks

One approach to asynchronous programming is to make functions that perform a slow action take an extra argument, a callback function. The action is started, and when it finishes, the callback function is called with the result.

Callbacks are the fundamental unit of asynchrony in JS. But they’re not enough for the evolving landscape of async programming as JS matures.

First, our brains plan things out in sequential, blocking, single-threaded semantic ways, but callbacks express asynchronous flow in a rather nonlinear, nonsequential way, which makes reasoning properly about such code much harder. Bad to reason about code is bad code that leads to bad bugs.

We need something better than callbacks. They’ve served us well to this point, but the future of JavaScript demands more sophisticated and capable async patterns.

Promises

Working with abstract concepts is often easier when those concepts can be represented by values. In the case of asynchronous actions, you could, instead of arranging for a function to be called at some point in the future, return an object that represents this future event.

This is what the standard class promise is for. A promise is an asynchronous action that may complete at some point and produce a value. It is able to notify anyone who is interested when its value is available.

Promises are awesome. Use them. They solve the inversion of control issues that plague us with callbacks-only code.

They don’t get rid of callbacks, they just redirect the orchestration of those callbacks to a trustable intermediary mechanism that sits between us and another utility.

Promise chains also begin to address (though certainly not perfectly) a better way of expressing async flow in sequential fashion, which helps our brains plan and maintain async JS code better. An even better solution to that problem is Async & Await.

Async & Await

The following is the evolution of asynchronous patterns in JavaScript:

  • ES5 = Callback Function
  • ES6 = Promise
  • ES7 = Async & Await

The one thing you need to know about async functions is that they always returns a promise.

The await keyword can only be used within an async block, otherwise it’ll throw a syntax error. This means you cannot use await by itself. When do we use it? If we have an asynchronous function inside of an async block. So let’s say we need to fetch some data from our server and then use that data within our async block. We will use await to pause the function execution and resume after the data comes in.

Await is simply a more elegant way to write a promise within an async function. It improves readability immensely and hence the reason we use it. Overall, async/await is a much cleaner syntax to write asynchronous JavaScript code. It enhances readability and flow of your code.

Class

Introduced in ES6, a JavaScript class is a type of function. Classes in JavaScript do not actually offer additional functionality, and are often described as providing “syntactical sugar” over prototypes and inheritance in that they offer a cleaner and more elegant syntax. Because other programming languages use classes, the class syntax in JavaScript makes it more straightforward for developers to move between languages.

Both classes and constructors imitate an object-oriented inheritance model to JavaScript, which is a prototype-based inheritance language. Understanding prototypical inheritance is paramount to being an effective JavaScript developer. Being familiar with classes is extremely helpful, as popular JavaScript libraries such as React make frequent use of the class syntax.

Prototype

Prototypes can be used to extend objects. Every object in JavaScript has an internal property called [[Prototype]]. The typical way you would create an object is with an object literal, but note that another way to accomplish this is with the object constructor.

It is important that every object in JavaScript has a [[Prototype]] as it creates a way for any two or more objects to be linked. Objects that you create have a [[Prototype]], as do built-in objects, such as Date and Array. A reference can be made to this internal property from one object to another via the prototype property.

All JavaScript objects have a hidden, internal [[Prototype]] property (which may be exposed through proto in some browsers). Objects can be extended and will inherit the properties and methods on [[Prototype]] of their constructor. These prototypes can be chained, and each additional object will inherit everything throughout the chain. The chain ends with the Object.prototype.

Immediately Invoked Function Expression (IIFE)

An IIFE is a JavaScript function that runs as soon as it is defined. It is a design pattern which is also known as a Self-Executing Anonymous Function and contains two major parts. The first is the anonymous function with lexical scope enclosed within the Grouping Operator (). This prevents accessing variables within the IIFE idiom as well as polluting the global scope.

The second part creates the immediately executing function expression () through which the JavaScript engine will directly interpret the function.

Module Pattern

In JavaScript, the module pattern is used to further emulate the concept of classes in such a way that we’re able to include both public/private methods and variables inside a single object, thus shielding particular parts from the global scope. What this results in is a reduction in the likelihood of our function names conflicting with other functions defined in additional scripts on the page.

The Module Pattern is one of the most common design patterns used in JavaScript and for good reason. The module pattern is easy to use and creates encapsulation of our code. Modules are commonly used as singleton style objects where only one instance exists. The Module Pattern is great for services and testing/TDD.

Other JavaScript design patterns to know include the Prototype, Observer, and Singleton patterns.

Currying

Named after Haskell Brooks Curry, currying is the process of breaking down a function into a series of functions that each take a single argument. It does so with the help of lambda calculus.

Briefly, currying is a way of constructing functions that allows partial application of a function’s arguments. What this means is that you can pass all of the arguments a function is expecting and get the result, or pass a subset of those arguments and get a function back that’s waiting for the rest of the arguments. It really is that simple.

Currying is elemental in languages such as Haskell and Scala, which are built around functional concepts. JavaScript has functional capabilities, but currying isn’t built in by default (at least not in current versions of the language). But we already know some functional tricks, and we can make currying work for us in JavaScript, too.

Currying is an incredibly useful technique from functional JavaScript. It allows you to generate a library of small, easily configured functions that behave consistently are quick to use, and that can be understood when reading your code. Adding currying to your coding practice will encourage the use of partially applied functions throughout your code, avoiding a lot of potential repetition, and may help get you into better habits about naming and dealing with function arguments.

Memoization

Memoization is a programming technique which attempts to increase a function’s performance by caching its previously computed results. Because JavaScript objects behave like associative arrays, they are ideal candidates to act as caches. Each time a memoized function is called, its parameters are used to index the cache. If the data is present, then it can be returned, without executing the entire function. However, if the data is not cached, then the function is executed, and the result is added to the cache.

Memoization becomes demystified when you boil it down to key-value pairs. All we’re doing is creating an object, checking for existing values that match the user input and storing new key-value pairs if they don’t exist in our object.

Of course, storing all this data means that we’re going to be using up memory. It’s best to implement memoization on functions that are pure and involve heavy, repetitive calculations.

Apply, call, and bind methods

Functions are objects in JavaScript, and as objects functions have methods including the powerful apply, call, and bind methods. On the one hand, apply and call are nearly identical and are frequently used in JavaScript for borrowing methods and for setting the this value explicitly. On the other hand, we use bind for setting the this value in methods and for currying functions.

Polymorphism

Polymorphism is the presentation of one interface for multiple data types. For example, integers, floats, and doubles are implicitly polymorphic: regardless of their different types, they can all be added, subtracted, multiplied, and so on. In the case of OOP, by making the class responsible for its code as well as its own data, polymorphism can be achieved in that each class has its own function that (once called) behaves properly for any object.

Just as all methods and properties are defined inside the prototype property, different classes can define methods with the same name; methods are scoped to the class in which they’re defined, unless the two classes hold a parent-child relation (i.e. one inherits from the other in a chain of inheritance).

Happy Coding!

Coding


Matthew Conrad

Written by Matthew Conrad, a web developer in Seattle. You can follow me on Twitter and GitHub.