@rkenmi - Javascript Essentials

Javascript Essentials


The intricacies of Javascript

Javascript Essentials


Back to Top

Updated on October 7, 2022

Hoisting

Hoisting is JavaScript's default behavior of moving declarations to the top.

Given the following Javascript code, what is the expected output, and why?

fcn2();  
fcn1();

function fcn2(){  
    alert("hi");
}
var fcn1 = function(){ alert("hey") }  

The expected output is a pop up alert that says "hi", followed by an error that fcn1 isn't defined. This occurs because fcn2 is a statement, and in Javascript, function statements are hoisted (or moved) to the top. This is why we can call fcn2() even though fcn2 isn't actually defined before the call.


Given the following Javascript code, what is the expected output, and why?

console.log(fruit);  
console.log(veggies);

var fruit = "bananas";  

The expected output is an undefined on the console log, followed by a Uncaught ReferenceError for veggies. Why doesn't fruit give an Uncaught ReferenceError?

This might seem confusing, but what is happening is that the fruit variable is once again, hoisted to the top and declared there, so when console.log(fruit) is ran, it acknowledges its existence. Although the variable is declared, it is undefined, because the actual assignment to "bananas" is done at line 4, and not before the console.log.


Given the following Javascript code, what is the expected output, and why?

console.log(fruit);  
console.log(veggies);

let fruit = "bananas";  

This time, there is an immediate Uncaught ReferenceError for fruits. This is because the new let keyword (introduced in ES6) is not hoisted. let is primarily used for block-level variable declaration, for performance and cleanliness. const is also similar, in that it is not hoisted like var, and is used as a read-only variable similar to const in other programming languages.


IIFE

function(){  
  alert("hi");
}();

This is a function statement. If this was an expression instead, where it resolves to some value, then we can expect it to work. In other words, var fcn = function(){ alert("hi") }(); will work just fine. These functions are called IIFE (Immediately Invokable Function Expressions).

This pattern is often used when trying to avoid polluting the global namespace, because all the variables used inside the IIFE (like in any other normal function) are not visible outside its scope.

function() {  
  var x = 1;
  (function() {
    var y = 2;
    console.log(x);
    console.log(y);
  })();

  // y is not accessible or visible from this scope
}

With ES6 however, we can use block level declarations with variables using let to do this job.


Classes

Creating a class depends on the Javascript standard. Vanilla Javascript will require you to create functions and use the new keyword to have class-like objects. Classes simply don't exist in Javascript. In ES6, there is a class keyword, and you can extend classes to do inheritance. They are basically syntactic sugar, and under-the-hood, they use functions and the new keyword like how it was normally done back in the old days.


Constructor

The constructor is just a function that is called with the new keyword.


Instantiating with Vanilla and ES6

class StarFox {  
  this.name = 'Fox';
}

function RedFox () {  
  this.name = 'Fox';
}

var s = new StarFox();  
var r = new RedFox();  

In the class declaration, properties cannot be set in the class scope in that way. The right way to set the properties inside a class is to set them via the constructor.

class StarFox {  
  constructor(){
    this.name = 'Fox';
  }
}

Hoisting (again)

var s = new StarFox();  
var r = new RedFox();

class StarFox {  
  this.name = 'Fox';
}

function RedFox () {  
  this.name = 'Fox';
}

The instantiation for StarFox will fail because class declarations (ES6 style) are not hoisted. RedFox on the other hand, is hoisted, so it will work fine.


Intrepreter

Browser engines, developed by Google, Mozilla, Microsoft, etc. They are built into the browsers we use today, such as Chrome, Firefox, and Internet Explorer.


Event Loop

The event loop is basically a loop that listens for an event and takes some action when an event is received.

while (queue.waitForMessage()) {  
  queue.processNextMessage();
}

Each message is processed sequentially. A message is added whenever an event occurs and a listener is attached to it. A JavaScript runtime uses a message queue, which is a list of messages to be processed. Each message has an associated function which gets called in order to handle the message.

One downfall of this model is that if a message takes a long time to complete, the application will be unable to process other events that are waiting in line, which may give your application a sluggish or non-responsive feeling.


Event Delegation

Event delegation is a way of setting a DOM element with an event listener, and implicitly passing down that event listener to all of its children. For example, if a Form element has an event listener, and it has Div and Anchor tags inside it, the Div and Anchor tags are also listening for the same event, and they will bubble the event back up when they catch an event.

Single threaded or Multi threaded?

Javascript is single-threaded in nature due to the event loop and the event queue.

A single thread handles the event loop, and the event loop is a way for Javascript to handle concurrent actions. This is why calling setTimeout with a delay of 10000 ms does not immediately block the execution of code after it.

With that being said, multiple threads can push an event into the event loop concurrently, which is typically what happens inside modern web browsers such as Chrome and Firefox.

Node.js vs. Javascript

Javascript is a language. The web browsers we use today have a Javascript engine to run the Javascript pages. Google Chrome in particular uses the V8 engine.

Node.js is not a language, nor a server. Instead, Node.js is a Javascript engine based on the V8, bundled with I/O and Networking libraries. Node.js can be used to create a server, (i.e. Express) but it can also be used as a run-time environment. For example, you can use Node for a project's front-end by making use of it's package manager npm to install front-end frameworks like React and transpilers like Babel.

CommonJS vs. ES6

This is a good one to talk about. CommonJS is a module formatting system which comes packaged with NodeJS. The keywords require('foo') should be very familiar as a means to import modules, and exporting modules can be done with module.exports = ....

The ES6 standard is similar yet very different. CommonJS and ES6 will often butt heads with each other, since the way these two systems export and import are fundamentally different. In practice, you will not want to mix up these two, say by mixing import 'foo' with require('bar') in the same codebase. What also tends to happen is that if you use a transpiler such as Babel, you could often get away with some benefits such as writing NodeJS code with Typescript (Typescript is a superset of the ES6 standard). If you mix up the standards before this transpiling step, you are bound to run into headaches, as the transpiler will get confused on whether you are writing CommonJS code or ES6 code.


Inheritance

Classical vs. Prototypical

The concept of classical inheritance mainly derives from languages like Java where there are true classes. In Javascript, there are no true classes, as they are mainly just functions.

When a new object is created via new Object(), this object inherits from Object.prototype. This is an example of prototypical inheritance. ES6 language constructs for defining a class and extending a class may look similar to language constructs from true OOP languages (like Java), but under-the-hood, it uses prototypical inheritance. The underlying principle of prototypical inheritance is object composition.

Classical inheritance is the general idea of creating a class that extends a subclass and inheriting all of its properties in the process. It is also known as class inheritance. Some of these properties may not be needed by this new class, but it has them anyway. Class inheritance introduces the tightest coupling and is frowned upon due to many issues, such as inflexible hierarchies and unnecessary overhead. Object composition is preferred over class inheritance because properties to inherit can be specifically chosen, instead of inheriting everything.


Inheriting a class (Vanilla)

Here is an example of a class Human inheriting from Ape, i.e. Human extending the Ape class.

var Ape = function() {};  
Ape.prototype.warcry = function () {console.log("roar!")};

var Human = function() {};  
var person = new Human();  
person.warcry();  // function does not exist

Human.prototype = Object.create(Ape.prototype);  
person.warcry();  // prints "roar!"  


Promises

The concept of a Promise can be tricky to understand due to how messy it looks in practice, often blurring the lines between sequential execution and asynchronous execution in the world of single-threaded Javascript.

The best way to think of a Promise is that a Promise is a function that will not immediately finish. setTimeout(fn, timeout) is a great example of a Promise - the fn is the function that will execute after timeout milliseconds have elapsed. This means that when Javascript executes code line-by-line and sees setTimeout(fn, timeout), it will process it by adding the fn to the call queue, but it won't actually execute the code in fn until the desired timeout. This is one of the most confusing things to encounter as a beginner, since you would expect Javascript's execution to block until that fn is done executing all of its code.

The next most confusing thing is, given that we know about the fact that Javascript will execute fn sometime later, how do we actually access results from it? This is where the Javascript Promise API comes into play. In a nutshell, using the Promise API allows you to interact with results or errors from a function that executes later. Javascript will not block automatically on asynchronous function calls (such as the setTimeout), but using the Promise API, you can intentionally block the line-by-line execution of Javascript until the asynchronous function (i.e. fn) is done executing.

async/await

async/await are new keywords introduced in ES6 that are built on top of Promises, but with more simplicity and readability. It allows you to resolve Promises and store the result or error into a variable in just one line. The downside of async/await is that it doesn't offer much benefit vs. Promises for handling errors, and you will also need to transpile the async/await keywords down to vanilla Javascript using something like Babel.

Callback Hell

Basically, this term is a reference for a bunch of nested callbacks that become hard to maintain and hard to read.

Nested callbacks are something you want to avoid if possible. Instead of writing nested callbacks, you can try to declare callback functions into a variable beforehand, to make code readable. The Promise API works fine, but async/await may be cleaner to read and easier to reason with. If the result of each call doesn't depend on the other, you can use jQuery's when or the Promise.all API to do some logic when all callbacks are complete.

// callback hell - messy, hard to read

$.ajax("url1").done(function(res) {
    if (res && res.data) {
        $.ajax("url2").done(function(res2) {
             if (res2 && res2.data) {
                 $.ajax("url3").done(function(res3) {
                     console.log(res.data);
                     console.log(res2.data);
                     console.log(res3.data);
                     //...
                 }
             }
        }
    } 
});
// declare callback functions into variables - a bit cleaner

var d1 = $.ajax("url1");  
var d2 = $.ajax("url2");  
var d3 = $.ajax("url3");

var lastStep = function() {  
    $.when(d3).done(function(v3) {
         console.log( v1.data );
         console.log( v2.data );
         console.log( v3.data );
    }
}

var secondStep = function() {  
    $.when(d2).done(function(v2) {
         lastStep();
    }
}

var firstStep = function() {  
    $.when(d1).done(function (v1) {
         secondStep();
    });
}

firstStep();  
// when results of each ajax call doesn't depend on each other, use the All APIs. clean, easy to read

var d1 = $.ajax("url1");  
var d2 = $.ajax("url2");  
var d3 = $.ajax("url3");

// when all ajax calls are successful
$.when( d1, d2, d3 ).done(function ( v1, v2, v3 ) {
    console.log( v1.data );
    console.log( v2.data );
    console.log( v3.data );
});

API

Object.assign()

Object.assign(target, source) takes a source object, copies all of its properties, and inserts them into the target object.


setTimeout()

Calling setTimeout with a delay of 0 (zero) milliseconds doesn't execute the callback function after the given interval.


bind vs. call vs. apply

bind allows you to set the this scope of a function to another this reference. It returns a function with the new this scope.

call on the other hand allows you to make the function call immediately with another this reference.

apply is the same as call, except that it takes an array of arguments as a parameter.


let vs var

  • let is a block-level declaration, meaning that it is not accessible by anything outside the block scope. This includes if, while, for blocks, as well as function blocks.
  • var is a function-level declaration, meaning that it is defined throughout the entire scope of the wrapping function. In the case that it is declared outside of functions, it is defined in the global scope (i.e. the browser)

For...in vs. for...of vs. forEach()

For...in loops will iterate over all non-Synbol, enumerable properties of an object. This can have unforeseen consequences if say an array is iterated and a new property is added to Array.prototype. For...in will also skip undefined properties.

For...of loops will iterate over all values of iterable objects (i.e. an array, a map), however this is introduced only in ES6. In general, For...of is preferred. For...of will not skip undefined properties.

forEach() is a method (unlike the constructs above) of any iterable object. The use of forEach() is preferred in synchronous functions only.


Lexical Scoping

Lexical scoping refers to the authored scope at compile time (Yes, Javascript does compile! It just does so much more swiftly than traditional compiled languages.) Lexing is the parsing of strings, splitting them into tokens with semantic meaning (to the compiler/interpreter).

Lexical scoping is important for many reasons. Lines of code are positioned by lexical scoping, and the execution of code depends on the lexical scoping in order to look for references (i.e. variables) with respect to where the line of code was executed. For example, code in nested scopes may look at its own scope for a reference of a variable, and if it does not exist, it will check for the reference in the parent scope, and so on.

eval()

The main reason why the use of eval() is shunned by the Javascript community is due to the fact that eval() will modify the lexical scope at runtime, nullifying performance optimizations by the Javascript interpreter that rely on a non-changing lexical scope. Lexical scope is determined at compile time, and if the lexical scope gets changed at runtime, then the interpreter can no longer rely on the lexical scope for consistency; code references may be re-arranged, added, or removed.


Object.create()

Similar to new, it takes a prototype as an argument and creates a new object with a reference to that prototype and returns it. This is the preferred way to create objects, since it is much faster than manually setting prototypes and using new. Note that, Object.create() doesn't exist in ancient versions of Javascript.


Object.freeze() vs. Object.seal()

If we want to make an object read-only, we can use Object.freeze(obj)
If we want to allow only the defined properties in an object to be updated, we can use Object.seal(obj).


Object.defineProperty()

We can immediately define a new property and make it read-only by using the Object.defineProperty(obj, key, property) function and setting the writable flag to false.

Object.defineProperty(object1, 'property1', {  
  value: 42,
  writable: false
});

The 'new' keyword

The new keyword takes a function, creates a new object internally, passes in the arguments into the object, then sets the prototype on that function with that new object and returns it.


The 'in' keyword

The in keyword allows you to check if a property exists in an object.

"a" in {"a": "b"} => true // "a" is a property (or key) of the object
"c" in {"a": "b"} => false // "c" is not a property (or key) of the object

WARNING: Unlike Python's in keyword, in Javascript this can work against you. For example, if you try to use in like you do in Python with arrays, you will be doing a property lookup, rather than a value lookup.

0 in ["make"] => true // index 0 is a symbol inside the Array object  
"make" in ["make"] => false // "make" is not a symbol (or property) inside the Array object

In some sense, Python's in keyword has more magic built-in, where as with Javascript, you'll want to consider the property you're looking for from the object.


= = vs. = = =

The idea that == checks the values and === checks both the values and types is one big misconception.

Instead,

  • == checks for the equality of values after type coercion.
  • === checks for the equality of values without type coercion.

This means that when we compare values with ==, both values are converted to the same type before evaluating the values. There are no conversions involved with === on the other hand.


The 'this' keyword

The this keyword is the context of a function and it always refers to the object invoking the function that is called.

  • In function invocations, this points to the global object, which depends on the execution environment. For example, in a browser, the global object is window.
    • In nested function invocations, this also points to the global object. This could be confusing, and the use of use strict is recommended to set this to be undefined in function invocations. This is done to avoid bugs, and is introduced in ECMAScript 2015.
  • In method invocations, this points to the object instance of that method.
  • In constructor invocations, i.e. via new, this points to the newly created object.
  • In indirect invocations, i.e. call(this, fcn), this points to the custom context being passed into the argument.

Also of great importance: arrow functions use the this reference from the outer scope, where as normal functions have its own this reference in the new execution context.


Closures

A closure is the combination of a function and the scope object in which it was created. It is useful for creating private methods in a function, or to encapsulate data.

A closure is also used when we want to have functions remember data, by making use of variables defined in the enclosed scope.

Module Pattern

Module patterns are one of the most common examples of using closures.

The module pattern allows for private implementation details (functions, variables) to be inaccessible by the outside world, but still leave the ability to have public features available to the outside world, such as a public API.

Example:

function User() {  
  var username;
  var password;

  function doLogin(user, pw) {
    username = user;
    password = pw;
  }

  var publicAPI = {
    login: doLogin
  }

  return publicAPI;
}

A User function that returns a public API, but leaves details such as username and password unexposed to the public.


jQuery

In a nutshell, jQuery is a Javascript library that simplifies HTML DOM manipulation. It is the perfect example of the facade pattern, which wraps a set of complex APIs into a more simpler, developer-friendly and digestable API.

For example, there are many ways within Javascript to retrieve all the HTML elements with a
specific class name. For modern browsers, it is preferred to use getElementsByClassName(), but for older browsers, we'll have to use more primitive methods, such as selecting all of the elements in a page and filtering the elements by class, or using a browser specific feature such as querySelectorAll(). The facade pattern can abstract this subtlety and have one API that determines the best course of action for us.


Data Structures

(Note: Requires use of ECMAScript2015 and/or Google Closures Library)


Empty array

let list = [];  

Array with values

let list = [1, 2, 3];  

Empty hash map

let map = new Map();  
// or alternatively
let object = {};  

Hash map with values

let map = new Map(['a', 'b']);  
let object = {a : 'b'};  

Empty set

let s = new Set();  

Set with values

let s = new Set([1, 2, 3]);  

Min heap

goog.require('goog.structs.Heap');  
let h = new goog.structs.Heap();

h.insert(1, 'a');  
h.insert(2, 'b');  
h.insert(3, 'c');  
h.peek(); // 'a'

Max heap

Use goog.structs.PriorityQueue  

Pair/tuple

// a simple object can be used
let some_pair = {a: 1, b: 2}  

Queue

Use goog.structs.Queue  

Stack

Use goog.structs.Stack, or a normal Javascript array  

Article Tags:
Javascriptunlistedwiki