< Back to JavaScript notes

Fundamentals

The vegetables of writing JavaScript - boring yet essential to doing it right

Resources:


Scopes

Scope refers to the specific area a code of JavaScript has “access” to. If two sections of JS code are in the same scope, then they can access each other - different scope means they can’t.

There’s several types of scopes to know:

This is generally easy to understand, but can get confusing when you use var, which isn’t block-scoped. Declaring a variable in the global scope, and declaring another with the same name in a block, will override the global variable since there’s no block scope.

var myName = "Earl";

if (2 === 2) {
  var myName = "Crystal Soul-Eater";
}

console.log(myName); // "Crystal Soul-Eater"

If we used let instead of var, the second declaration would be block-scoped and not overwrite the global one. The console.log would then return "Earl".

Call Stack

The call ctack is the basic data structure JavaScript uses to execute, or call, a code’s functions. Whenever JavaScript code is executed (or called), the call stack controls the method and order behind this execution. That’s why understanding the call stack is essential to see how JavaScript runs, since not understanding it can lead to unexpected errors or code called in the wrong order.

Some key traits of the call stack:

Closure

Closure relates to when variables are declared within certain scopes. When calling a function, any variables it has are created, do their business, and are destroyed afterwards. If you call the function again, it recreates the variables in its scope all over again - all changes and operations from before are forgotten, they’re redone, and then destroyed again.

Closure comes into play when calling a function being stored into a variable. If that function has any variables in its local scope, those values aren’t destroyed once the function is done - they’re saved and remembered for later. Calling this function instance that was saved executes the same operations, but it carries over the remembered values too.

function iWillSaveValues() {
  let number = 0;
  let increaseNumber = () => n++;
  return increaseNumber;
}

let closureExample = iWillSaveValues();

closureExample has created a closure of this function. The number variable is set at zero, but changes to this variable will be remembered. You can see this if you call this function a few times:

closureExample(); // 1
closureExample(); // 2
closureExample(); // 3

Every time it’s called, it remembers the increased value of number from before and increases it. A non-closure example of this function would start over at 0 each time and always return 1. A way to remember this is if a bubble closed around these variables and protected them from destruction in this variable so they can keep changing as you keep using it.

Understanding closure, like many things on this page, is important for avoiding accidental bugs and taking advantage of what JavaScript can do. Closure lets you keep track of if your functions can be used for longer, more complex operations without losing track of what they’ll actually do.

Recursion

Recursions are functions that call themselves. They can be helpful since they can keep code dry, since you only write code once but can execute it as many times as needed. However recursive functions that have no end will cause a stack overflow (see Call Stack), so be sure they’re used properly.

Take this example function that finds a number’s factorial. This multiplies a number by every number lower than it until it returns the total product. So the factorial of 5 would be 5 * 4 * 3 * 2 * 1.

const factorial = (n) => (n < 2) ? 1 : n * factorial(n - 1);

Running factorial(5) leads to the following recursive function calls:

factorial(5) = 5 * factorial(4)
factorial(5) = 5 * 4 * factorial(3)
factorial(5) = 5 * 4 * 3 * factorial(2)
factorial(5) = 5 * 4 * 3 * 2 * factorial(1)
factorial(5) = 5 * 4 * 3 * 2 * 1
factorial(5) = 120

The part of the function with (n < 2) ? 1 is crucial, since it’s what stops the function from returning itself. Once the number gets down to 1, it simply returns that without calling itself, stopping the loop. Without it, the function would simply look like this:

const factorial = (n) => n * factorial(n - 1);

And would be played out this way instead:

factorial(5) = 5 * factorial(4)
factorial(5) = 5 * 4 * factorial(3)
factorial(5) = 5 * 4 * 3 * factorial(2)
factorial(5) = 5 * 4 * 3 * 2 * factorial(1)
factorial(5) = 5 * 4 * 3 * 2 * 1 * factorial(0)
factorial(5) = 5 * 4 * 3 * 2 * 1 * 0 * factorial(-1)
factorial(5) = 5 * 4 * 3 * 2 * 1 * 0 * -1 * factorial(-2)
factorial(5) = 5 * 4 * 3 * 2 * 1 * 0 * -1 * -2 * factorial(-3)
// Adding more negative numbers into infinity

Even though the result would have to be 0 since the result is being multipled by zero, the important thing is nothing’s telling this function to stop calling itself. It will keep doing so until something in the code tells it through, and since nothing will, it simply goes into it creates a stack overflow.

Variable Hoisting

var declarations and value assignments can be placed anywhere on a page. However, the variable declarations are “hoisted” automatically to the top of the page. This means that any var doesn’t necessarily need to be declared, since it will be declared when it’s hoisted. A computer reading this:

y = 5;

This will change to this once it gets hoisted:

var y;
y = 5;

This is why the first code sample, although it looks invalid, actually becomes valid when it becomes the second version. This isn’t necessarily a good thing, since it can create unexpected complexity or bugs in the code.

Avoiding this is easier with ES6, since the new variable declarations let and const are not hoisted.

Double vs Triple Equal Signs

=== comparisons looks for strict equality, meaning it looks for matching “type” and “value”.

== comparisons look for loose equality, meaning it only looks for matching value. It can perform “type coercion” if needed, which converts the both values to the same type before comparing values.

This distinction is important since loose equality can lead to unexpected bugs related to conditions returning true when they may need to be false. It’s better practice to always use === for comparisons.

Primitive Types

Expression VS Statement

Immediately-Invoked Function Expressions

Value and Reference Types

this