Introduction: JS Fundamentals

·

11 min read

JavaScript is a programming language mainly used to create websites that are dynamic and interactive. Almost all web applications use JavaScript somewhere in their functionality. In addition to that, JavaScript can also be used for mobile app development, game development, data analysis, and many other applications. Of course, other languages might be better in those areas, but when it comes to web development, JavaScript is the slam-dunk winner!

Since its creation, JavaScript has been a browser-based language. It is native to the browser, which means you can write JavaScript code directly in the browser’s console section. For example, you can try this: console.log("hello"). It is not compulsory to terminate statements with a semicolon, but it is advisable to be consistent and follow the same standard when working alongside others. The console.log method is used to print to the console and is a powerful debugging tool! Most bugs can be fixed by simple console.log commands.

After the introduction of Node.js a decade and a half ago, JavaScript was enabled to run outside of a browser environment like any other programming language, as long as you have Node.js installed on your system. To check whether you have it installed, type node in the terminal window. If you see something like “'node' is not recognized,” you don’t have it installed. You can head over to nodejs.org to download it. Once downloaded and installed, you can run JavaScript code in the terminal by typing node, which will launch a REPL (Read-Eval-Print Loop), or you can directly execute a JavaScript file by typing node example.js in the terminal.

Variable declarations

There are four different ways of declaring a variable in JavaScript: var, let, const, and implicit declaration. let is the most commonly used; it is block-scoped. const is also block-scoped but has a specific use case—when you want to define a value that does not change—and it must be assigned a value at the time of declaration. var is function-scoped, but its usage is often discouraged by most JavaScript developers due to its potential for creating unexpected bugs. Implicit declaration is a way of declaring a global variable, but it is generally regarded as bad practice!

let a = 10 // declare a variable this way 
let b
b = 10  // or this way 
const c = 10 // you must declare a constant with a value assigned to it!
var d = 10  // function-scoped variable
e = 10  // global-scoped variable

// Example to show the main difference between these declarations
function example() {
  if (true) {
    x = 20
    var y = 20
    let z = 20
  }
  console.log(x) // prints x without error because x is a global variable
  console.log(y) // prints y without error; y is defined with in this function
  console.log(z) // error! z is only known within the if block
}

example();
console.log(x)  // prints x without error because x is a global variable
console.log(y)  // error! y is only known within the function example
console.log(z)  // error! z is only known within the the function's if block

Primitive types vs Reference types

There are two data types in JavaScript: primitive types and reference types. The main difference between the two lies in how they relate to the values they are assigned. Primitives hold copies of the values assigned to them, while reference types store a reference to the memory address where the value is located.

We can use the typeof operator to determine the data type of any primitive variable. However, for most reference types, typeof returns "object" because, internally, every reference type is an object. Below is a list of some primitive and reference data types in JavaScript:

// Primitive types
let age = 23, name = 'Khalid', tired = true  
typeof age // returns number
typeof name // returns string
typeof tired // returns boolean

// other primitive types
null // represents abscence
undefined // a variable not declared 
typeof 123n // returns bigint. BigInts have n at the end!
Symbol // generates a unique and immutable value 

// Reference types
let arr = [1, 2, 3], obj = {}, date = new Date()
Array.isArray(arr) // returns true
typeof obj // returns object
typeof date // returns object

// Example to show the main difference between primitive and reference types
let a = { age: 10 } // an object literal— a reference data type
let b = 20 // a number— a primitive data type
let c = a, d = b
// a.age = 30
// b = 40
console.log(`a = ${a.age}  and `, `c = ${c.age}`)
console.log(`b = ${b}  and `, `d = ${d}`)

String coercion and some important string methods

There are different ways to convert both primitive and reference types to a string in JavaScript. For anything that is not null or undefined, we can use the toString method to convert it to a string provided it is a valid JavaScript object or primitive that supports the toString method. However, for any value that can be converted to a string including null and undefined, we can use the String() function, passing the variable as an argument, which returns its string form.

The same result could be obtained using concatenation "" + variable and template literal (`${variable}` , as shown above). Finally, another way is to use the String object constructor (as shown below). However, there is a small caveat with this option: the result achieved will not be a string literal but a String object. A string literal is a primitive type and behaves like one, whereas a String object is a reference type.

let x_num = 10   // of type number
x_str = x_num.toString() // a string '10' is assigned to x_str 
x_str = x_bool.toString() // a string 'false' is assigned to x_str 
y_str = String(null)    // a string 'null' is assigned to y_str 
z_str = new String(20)  // instance of the String object is assigned to z_str
arr_to_str = String([1, 2, 3]) // a string '1,2,3' is assigned to arr_to_str

// Some important string methods  
"Hello".charAt(4);          // "o"
"Hello".concat(" world");   // "Helloworld"
"Hello".startsWith("H");    // true
"Hello".endsWith("o");      // true
"Hello".includes("x");      // false
"Hello".indexOf("l");       // 2
"Hello".lastIndexOf("l");   // 3
"Hello".match(/[A-Z]/g);    // ["H"]
"Hello".padStart(6, "?");   // "?Hello"
"Hello".padEnd(6, "?");     // "Hello?"
"Hello".repeat(3);          // "HelloHelloHello"
"Hello".replace("llo", "y"); // "Hey"
"Hello".search("l");        // 2
"Hello".slice(1, 3);        // "el"
"Hello".split("");          // ["H", "e", "l", "l", "o"]
"Hello".substring(2, 4);    // "ll"
"Hello".toLowerCase();      // "hello"
"Hello".toUpperCase();      // "HELLO"
"Hello".trim();             // "Hello"
"Hello ".trimStart();       // "Hello "
" Hello".trimEnd();         // " Hello"

Other conversions and Math methods

Likewise, we can convert strings to boolean values, boolean values to numbers, or even numbers to booleans. Passing a string representation of a number to parseInt() will return a number literal. If you are working with a floating-point number, you can use parseFloat(). Additionally, calling the global functions like Number() and Boolean() can also perform these conversions. For example, Boolean("false") returns true—anything that is not an empty string ("") or 0 is considered true. Similarly, Number(false) converts the boolean value false to a number, returning 0.

The Math object provides us with a variety of helpful methods for performing mathematical operations. An entire list of these methods can be found on the Mozilla Developer Network (MDN), one of the most reliable and up-to-date resources for developers in the web development community. Below are some of the most commonly used Math methods:

Math.ceil(4.1) // returns 5
Math.floor(4.1) // returns 4
Math.round(4.1) // returns 4
Math.random() // returns a random number between 0 and 1
Math.max(10, 5, 20) // returns 20
Math.min(10, 5, 20) // returns 5
Math.pow(2, 3) // returns 8
Math.sqrt(25) // returns 5

// Example: generate a random number between 1 and 10
let rand = Math.round(Math.random() * 10 + 1)
console.log(rand)

Dynamically-typed: Arrays

JavaScript is a dynamically typed language, unlike C languages or Java. This means, for example, that a variable holding a number can be re-assigned to hold a string. Additionally, arrays in JavaScript are dynamic: their sizes can grow and shrink as needed. Furthermore, arrays are not restricted to a single type. In a single array, we can have strings, numbers, objects, and other types together. The following visualization represents some of the most frequently used array methods:

There are multiple ways to loop through arrays in JavaScript. In addition to traditional methods like for, while, and do while, there are more modern iteration methods (examples shown below) that are unique to JavaScript and not as common in other programming languages. Methods like filter() and map() (shown above), as well as reduce() and others, provide ways to iterate through an array while performing specific operations.

let nums = [1, 2, 3]

// forEach method
nums.forEach((num) => console.log(num))   // prints 1 2 3  one by one

// for ... of 
for (let num of nums) {
  console.log(num) // prints 1 2 3  one by one
}

// reduce method example
let sum = nums.reduce((a, b) => a + b)
console.log(sum)   // prints 6 (the sum of the numbers in the array)

// sort method example
nums.sort((a, b) => a - b) // Ascending order; changes the original array.
nums.sort((a, b) => b - a) // Descending order; changes the original array.

Object Literals

An object literal is a structure of key-value pairs that allows us to store data in a way that is quick to retrieve. Here is an example of creating an object literal:

const person = {
  name: 'khalid',
  age: 23,
};

The object person has two properties: name and age. To access each property, you can use either dot notation or bracket notation (examples shown below). Bracket notation is especially useful when the property name is not known in advance and is generated dynamically. If we have multiple key-value pairs, we may need to iterate through them. While traditional loops like for or while can be used, it is generally more advisable to use loop structures designed specifically for objects.

// Dot notation example
console.log(person.name)  // outputs khalid

// Bracket notation example
console.log(person["age"])  // outputs khalid 

// Mutating a value
let key = "name" // key can be used to dynamically render of the name property
person[name] = "Khalid Mohammed" // kalid is changed to Khalid Mohammed

// Adding a property
person["tired"] = true  // the new property tired holds a boolean value

// Object literal specific condtional and loop statements
if ('name' in person) 
    console.log(person.name) // prints first name then age i.e. the properties

for (let key in person) 
    console.log(key)     // prints Khalid Mohammed

Rest and Spread Operators

The rest and spread operators are two of the most important operators that are useful when working with strings, arrays, objects, and functions. Both operators are represented by three dots (...) but they serve different or maybe even opposite purposes. Rest collects arguments or properties in an array or object and Spread, as its name suggests, spreads them. Let’s consider the following:

// Rest (array destructure example) 
const arr = ["apple", "banana", "orange"]
const [first, ...firuts] = arr
console.log(first) // outputs "apple" 
console.log(...firuts) // outputs ["banana", "orange"]

// Rest (object destructure example)
const { name, ...rest } = person;
console.log(name); // prints "Khalid Mohammed"
console.log(rest); // prints { age: 23, tired: true }

// Rest (function arguments in to array)
function sum(...args) {
  console.log(String(args))  // prints 1,2,3 — string representation of args
}
sum(1, 2, 3) 

// Spread (string to array example)
const word = 'hello'
const letters = [...word]
console.log(letters) // prints ['h', 'e', 'l', 'l', 'o']

// Spread (merging arrays example) 
const nums = [1, 2, 3]
const merged = ['apple', 'banana', ...nums]
console.log(merged) // prints ['apple', 'banana', 1, 2, 3]

// Spread (merging objects example)
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // prints { a: 1, b: 2, c: 3, d: 4 }

// Spread (array in to function arguments)
const arr = [1, 2, 3];
function args(...args) {
  console.log(...args); // prints 1 2 3 as arguments passed to the function
}
args(...arr);

Functions

The dynamic nature of JavaScript is also evident when it comes to functions. In JavaScript, functions are not defined with a specified return type or argument type. This means you can pass different types of variables, or even arrays, to a single function (as long as you match the number of arguments specified in the function declaration) and you can expect a return of various types as well.

There are several ways to declare functions in JavaScript. Here are three of them: function declarations, function expressions, and arrow functions. The major differences between them include hoisting and how they handle the this keyword. Hoisting is the process of moving function declarations to the top during compilation, making them accessible before their actual definition in the code.

// Function declaration
function echo(word) {
  return word
}
console.log(echo('hi'))  // prints hi

// Anonymous function expression 
const echo = function (word) {
  return word;
};
console.log(echo('hi'))  // prints hi

// Named function expression 
const echo = function repeat(word) {
  return word;
};
console.log(echo('hi'))  // prints hi

// Arrow function
const echo = (word) => {
  return word
}
console.log(echo('hi'))  // prints hi


//  hoisting example
console.log(echo('hi'))  // prints hi
function echo(word) {
  return word
}
console.log(echo('hi'))  // Error! Cannot access 'echo' before initialization
const echo = function (word) {
  return word;
}

Date and Time

For web-related applications, date and time play a crucial role. The Date object constructor in JavaScript accepts various formats as arguments to create a date object. Some of these formats are listed below. However, as with many other topics discussed in this article, you can find more comprehensive documentation on Date objects on MDN.

// passing no argument returns today's date
let today = new Date()  // returns Mon Nov 25 2024 15:08:41 ...

// passing arugement with different format examples 
let bday = new Date('09-11-17')  // returns Mon Sep 11 2017 00:00:00 ...
bday = new Date('September-11-17')  // returns same result
bday = new Date('09/11/17 06:30:00') // returns Mon Sep 11 2017 06:30:00..

// get methods 
bday.getMonth()  // returns 8. It's 0 indexed January is 0, December is 11  
bday.getDate()   // returns 11 
bday.getDay()    // returns 1 (Monday)

// set methods
bday.setHours(3) // sets Hour to 3
bday.setMonth(2) // sets Month to 2
bday.setDate(12) // sets Date to 12

Credits and helpful resources:

Documentation: MDN Web Docs

Video resources: Traversy Media Code With Mosh

LinkedIn posts: Ram Maheshwari David Mráz