Skip To Content
Javascript

Fundamentals

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:

Data Types #

All data in computers can be reduced to bits, positive/negative charges expressed as ones and zeros.

In JavaScript, groups of bits are represented as values. You create them by simply invoking their name. There's several types:

Type Coercion is when operators are applied to the wrong value types and quickly convert the value to what is needed, such as converting "5" from a string to 5 as an integer. To test for any unexpected type conversions or missing values, use the === and !== comparison operators.

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.

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".

Switch Statements #

Large amounts of conditionals can often be awkward for if statements. The switch statement will often work better

switch (variable_here) {
case (10):
console.log("It's ten!");
break;
case (5):
console.log("It's five!");
case (2):
console.log("It's two!");
default:
console.log("What is this number...?");
break;
}

Note that you don't need to include break in every case, which executes the code but then keeps going.

Value and Reference Types #

For data stored in different variables, JavaScript stores this data in two different ways: value and reference.

Data types with values are also called Primitive Data Types. Data types with references are technically all Objects, although they're still often referred by their specific names. They're basically the same thing and can be called either one, but Primitives and Objects are common and simpler ways to name them.

Changing Data #

A basic example of how Primitives work is below. When assigning variables with values to other variables, you can change each independently of each other.

let number = 42;
let anotherNumber = number;

number = 55;

console.log(number); // 55, has changed
console.log(anotherNumber); // 42, has not changed

This independence doesn't hold true for Objects. They both reference the same array, so changing one array winds up changing both of them.

let array = [1, 2, 3, 4];
let secondArray = array;

array.push(5);

console.log(array);
// [1, 2, 3, 4, 5]
// Changed, as expected

console.log(secondArray);
// [1, 2, 3, 4, 5]
// Also changed since it uses the same references

Note this also applies when passing Objects into functions. Using code directly on the variable passed in will affect the reference. A way around this is to assign values to a new Object, using the values from the passed object to calculate them when needed. This will create a new object with its own reference, which keeps the two objects from interfering with each other.

Hoisting #

Hoisting with ES5 #

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.

When you do a typical var declaration and assignment like this:

console.log('Foo bar!');
var y = 5;

The code will move the actual declaration to the top, which is the "hoist."

var y;
console.log('Foo bar!');
y = 5;

Because of this, you could technically call on the variable before it's declared when you wrote. So this:

console.log(y);
var y = 5;

Translates to this, which will put undefined in the console instead of a code error.

var y;
console.log(y);
y = 5;

Hoisting also means var doesn't necessarily need to be declared when assigning a value, 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.

This adds up to hoisting often causing bugs, or making bugs harder to find. Make it a habit of always declaring variables before use to avoid hoisting causing any unintended bugs.

Hoisting in ES6 #

Avoiding this is easier with ES6, since the new variable declarations let and const are hoisted differently. Technically hoisting still takes place, but instead of returning undefined they return a reference error. Therefore even though they support hoisting, using them before their declaration causes more errors and therefore avoiding this is better enforced in the code.

Hoisting Scope #

ES5 variables are only hoisted up based on, and limited to, global and function scope. ES6 variables also hoist within block scope, which is another good reason to use them.

IIFEs #

Immediately Invoked Function Expressions (IIFEs) are simply anonymous functions that are called right after they're defined. They're written the same as regular functions but

(function() {
// Function fun here
});

IIFEs let you invoke any needed logic or actions right away without calling a named function or variable. This is helpful since it:

Creating Variables #

IIFEs can be useful if complex logic must be saved to a variable, since all that logic can be written into the function and be scoped.

const birthdayStatement = (function() {
const name = "Maxwell",
birthYear = 2019 - 24;

return `${name} was born in ${birthYear}`;
})();

console.log(birthdayStatement);
// "Maxwell was born in 1995

You can also pass arguments to IIFEs if needed. The way they're passed in is tricky though, since:

const globalPerson = {
name: 'Maxwell',
birthYear: 1995
};

const ageStatement = (function (person) {
const name = person.name,
age = 2019 - person.birthYear;

return `${name} is ${age} years old`;
})(globalPerson);

console.log(ageStatement);
// "Maxwell is 24 years old"

Functions #

Functions are the core of Javascript, allowing coders to wrap larger functionalities inside different values. This reduces repetition and makes it easier to manage complexity.

Functions begin with the keyword function, followed by any parameters (may have multiple or none) and then the body with the statements the function executes. The traditional syntax always requires curly braces around the body (ES6 rules have exceptions). Two examples of different functions the book gives are below, showing some variety in parameters and complexity. Note that the first doesn't return a value (would give undefined) while the second does (using return).

const makeNoise = function() {
console.log("Pling!");
};

makeNoise();
// → Pling!

const power = function(base, exponent) {
let result = 1;
for (let count = 0; count < exponent; count++) {
result *= base;
}
return result;
};

console.log(power(2, 10));
// → 1024

Arrow Functions #

Arrow functions are the new ES6 syntax for JavaScript functions. They're great for keeping your JS concise, especially for simpler functions. The big difference is they don't need the function keyword to set them up, instead using an => icon.

Arrow Function With Body #

These are more similar to ES5 functions, since it gives you multiple lines and must implicitly return a value.

const arrowFunction = () => {
return 'This is a string being returned!';
}

Arrow Function Without Body #

Without the body, arrow functions are limited to one line and implicitly return what's there. Great for simpler code so it's easier to read.

// Parenthesis are either blank or hold parameters
const arrowFunction = () => 'This is a string being returned!';
const doubleNumber = (n) => n * 2;

// Can break the line for neater syntax
const arrowFunction = () =>
'This is a string being returned!';

Return Functions in Functions #

Functions can work with more than just straight values, they can take and return other functions too! If you're writing code that's similar in many ways but will need to call different functions at times, you can pass in functions as arguments. This can exponentially increase a function's power and flexibility.

Below is a simple example:

function formatter(formatFn) {
return function inner(str){
return formatFn( str );
};
}

There's three functions to look at here:

It looks convoluted, but the basic use of this function is to take functions built on formatting text, save them, and use them in a specific way later on.

As an example, let's say we had a function that uppercases the first letter of a string.

const upperFirst = (string) => `${string[0].toUpperCase()}${string.substr( 1 ).toLowerCase()}`;

We could pass this function into formatter and then call it later. We can also adjust formatter to better show the control it gives us.

const formatter = (formatFn) => {
return function inner(str){
console.log(`Your returned string is "${formatFn( str )}."`);
};
}

const upperFirst = (string) => string[0].toUpperCase() + string.substr( 1 ).toLowerCase();

logUpperFirst = formatter(upperFirst);

logUpperFirst( "hello" );
// logs 'Your returned string is "Hello."' in the console

We can use formatter for any number of other ways to change strings - all lowercase, reversing them, working them into a paragraph, etc. We can write the function completely separate and pass it in, keeping the code decoupled and flexible.

Note that you don't need to define your functions in a variable before passing them them, you can define them in the argument itself. The end result is the same, so it's a matter of preference.

const logLower = formatter( function formatting(string){
return string.toLowerCase();
} );

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.

Async/Await #

Async/Await functions are simple, effective methods for asynchronous JavaScript. They're essentially functions that will do two additional things things:

An async/await function has a basic structure like this:

async function myAsyncFunction() {
let value = await promise;
return value;
}

const asyncValue = myAsyncFunction;

asyncValue.then( function(value) {
console.log(value)
})

This function will do the following:

  1. It will immediately run, but pause and wait at the await line while the promise resolves
  2. Once the promise resolves, it will continue until the function completes or there's another await.
  3. When the function is assigned to asyncValue, the variable's value will be an unresolved promise.
  4. When the variable's promise resolves, you can treat it like any other resolved promise. In this case, it uses .then to take the returned value when completed and log it.

Expressions and Statements #

Expressions and statements are used in virtually all parts of JavaScript code, so understanding the differences and what they do helps greatly in avoiding common mistakes. In simplest terms:

A JavaScript file could have dozens of expressions that create different values, but won't actually do anything without statements to use them in. Expressions will give you different numbers, statements will add them together and return the solution at the end.

Types of Expressions #

Arithmetic #

These return integer values. These can be integers on their own, or calculations that return the result of them in the end.

20       // 20
20 * 5 // 100

String #

These return strings. They can be strings on their own or concatenated strings.

Logical #

Logical expressions compare two values in some way, and the result of this comparison returns a boolean.

100 === 100             // true
100 < 100 // false
true && false // false
true || false // true
(10 === 10) && (9 > 8) // true

Primary #

These are simple standalone values, also known as literal values. Just using 15 or 'I am a string' are both primary expressions.

Assignment #

These use the = operator to assign values to variables. This doesn't include var, let, or const if they're being used, and would only be somelike like total = 123. Even with const total = 123, only total = 123 would be the assignment expression.

Types of Statements #

Declaration #

These create variables and functions, respectively.

const number;

function logSeven() {
console.log(7);
}

Expression #

These are statements that have expressions substituted inside them. Expression statements will resolve their expressions, and then carry out the statement accordingly.

const number = (5 + 5);
// The resolved expression in this expression statement translates to the below statement
const number = 10;

These are useful for statements that rely on changing or more volative values, by writing the expression in a flexible way.

Conditional #

These run statements based on different expressions. If the expression resolves to a truthy value, the statement is executed. For an if..else statement, another statement will be run even if the expression is falsey.

if (true && true) {
// Statement here is run since above expression is true
}

if (true && false) {
// Statement here isn't run since above expression is false
} else {
// Statement here runs instead
}

Loops and Jumps #

Loop statements are statements run in different for or while javascript loops, and are therefore run multiple times which may or may not change with each iteration (based on index or a common variable).

Jump statements, meanwhile, are statements used in syntaxes like switch statements which jump to a specific statement(s) on a certain condition. This separates them from conditional statements, which instead go down a list of conditions and stop at the first matching one.

this #

The this keyword is basically a shortcut reference to the object where it is being called. This is determined entirely on the call-site, which is simply the object this is being used in.

There's three basic bindings that will determine exactly what object this gets bound to.

Global Binding #

By default, this is bound to the program's global object. For browsers and Node environments, this will basically be the window object. This should usually be avoided.

Implicit Binding #

Calling this in any sort of object means it will refer to that instead, since that's the call site.

const hulk = {
name: 'Hulk',
attack: 'Smash',

scream: function() {
console.log(`${this.name} ${this.attack}!!!`)
}
}

hulk.scream(); // Hulk Smash!!!

Calling this.name in the call-site is basically the same as calling hulk.name outside of it. This lets you reference code inside the call-site, letting this easily adjust to any data dependent on context or closure data. Just know that this can only be used in functions or other code definined within the call site.

Note that this won't be bound the same way with arrow functions, since they can never be bound to this. They'll default to the lexical scope. This variable is at the root of the file, so using an arrow function like the one below would be undefined.

const hulk = {
name: 'Hulk',
attack: 'Smash',

scream: () => console.log(`${this.name} ${this.attack}!!!`)
}

Explicit Binding #

If there's no implicit object for this to reference, one can be assigned with a few potential function calls.

.call() and .apply() #

You can use either of these two on a function that uses this while passing in the object it'll use as the call-site. You can also pass in other arguments the function may need afterwards as a variable spread (.call) or in an array (.apply).

function saySomething(message) {
console.log( `${this.name} says "${message}"` );
}

const person = {
name: 'Maxwell'
};

const announcement = "Watch Big Hero 6!";

saySomething.call(person, announcement);
saySomething.apply(person, [announcement]);
// Both return the following:
// Maxwell says "Watch Big Hero 6!"

.bind() #

Bind operates similarly to the previous two methods, but differs in how it is called. This creates a new function with the passed object saved for this, and you then call that function afterwards.

function say(message) {
console.log( `${this.name} says "${message}"` );
}

const person = {
name: 'Maxwell'
};

const MaxwellSays = say.bind(person);
// MaxwellSays is the 'say' function with
// 'person' bound to 'this'

const announcement = "Watch Big Hero 6!"; // Pass in needed args
MaxwellSays(announcement);
// Returns 'Maxwell says "Watch Big Hero 6!"'

Resources