Adding Tools To Your Technical Toolbox: JavaScript Edition II

Madeline Stalter
8 min readFeb 17, 2021

As previously stated, JavaScript is a powerful, object-oriented language that enables us to build dynamic, ultra responsive websites, handle API calls and more. Building upon the foundation laid in Edition I, I will continue to detail base concepts that every front end developer should know.

Source: https://www.incimages.com/uploaded_files/image/1920x1080/getty_494605768_2000133320009280151_316966.jpg

17. Objects are non-primitive data types (e.g., array, function). Objects are defined using:

// 1. Object Literal Syntaxconst dog = {
}
// 2. New Object Syntaxconst dog = new Object() // 3. Other Syntaxconst dog = Object.create()

You can also initialize an object using the new keyword before a function. The function is a constructor for that object where we can define parameters to setup the initial state of the object. For example:

function Dog(name, breed) {
this.name = name
this.breed = breed
}
const myDog = new Dog("Sidney", "German Shepherd")myDog.name // => "Sidney"
myDog.breed // => "German Shepherd"

Note: Objects are always passed by reference. If you assign a variable the same value as another, if it’s a primitive type (e.g., string, integer), they are passed by value. For example:

const cat = {
name: "Peyton"
}
const anotherCat = cat
anotherCat.name = "Iris"
cat.name
// => "Iris"

Objects have properties that are defined by a key-value pairs. The value of a property can be of any type (even other objects)! With the object literal syntax highlighted above, we can define a property in this way:

// Here we have an engineer object with a property named name and a value Madelineconst engineer = {
firstName: "Madeline",
lastName: "Stalter",
age: 25
}

Note: Keys should be camelCased and can be any string — but invalid characters include spaces, hyphens, and other special characters.

We can retrieve the value of a property using dot notation and square brackets.

// Dot Notationengineer.firstName // => "Madeline"// Square Brackets - this is useful for keys with "invalid names" for example if firstName was labeled "first name"engineer["first name"] // => "Madeline"// If you try to access a non-existent property, undefined will be returned. engineer.language // => undefined 

As I previously mentioned, objects can have nested objects as properties. Such as:

const bike = {
brand: {
name: "Cannondale",
series: "Quick Women's 4"
},
color: "Sage"
}

To access nested properties, we can use the following syntax:

// Dot Notationbike.brand.series// Square Brackets bike["brand"]["name"]

Note: You can update the value of a property after initializing the object; you can also add/remove properties after the fact:

let game = {
name: "Assassin's Creed"
}
game.name = "Modern Warfare"game.yearReleased = "2019"delete game.yearReleased

Objects have methods by assigning function to function properties. In the following example, the start property has a function assigned and we can invoke that function using dot notation with parentheses at the end:

const game = {
name: "Call of Duty",
console: "Xbox",
start: function() {
console.log("Game Has Begun - Watch Out For Campers!")
}
}
game.start()

Inside a method (defined using the function() {}) syntax we have access to the entire instance by using the this keyword. For example:

const game = {
name: "Call of Duty",
console: "Xbox",
start: function() {
console.log(`Started
${this.name} on ${this.console} - Watch Out For Campers!`)
}
}
game.start()

Note: We only have access to the this keyword if we use a plain function; this will not work with arrow functions as they’re not bound to the object.

Methods can also accept parameters, like plain functions:

const travel = {
date: "4/16",
passenger: "Madeline",
transportation: "Plane",
destination: function() {
console.log(`Exploring ${destination}!`)
}
}
travel.destination("Italy")

18. Classes are a way to define a common pattern for multiple objects. They function as a blue print for all instances.

Let’s take a person object:

// We can transform an object into a class so we can create many instances with ease.const city = {
name: "nyc",
state: "new york",
population: "8.419 million"
}
class City {
name
}
// We can now initialize an object (or instance of the City class) like: const savannah = new City()// We can set the value of a property like: savannah.population = "145.403 thousand"// We can access a property like: savannah.name = "savannah"

Classes can also hold methods. Methods are defined and invoked like:

class Student {
greeting() {
return "Good morning professor!"
}
}
// Creating an instance of the Student classlet madeline = new Student()// Invoking the methodmadeline.greeting()

Class properties can also be initialized when we create a new instance of an object using the constructor() method.

class Student {
constructor(name) {
this.name = name
}
hello() {
return "Hi, I'm " + this.name + "!"
}
}
// Now we can instantiate a new object from the Student class by passing in a string to create a personalized greetinglet nick = new Student("Nick")
nick.hello()
// => "Hi, I'm nick!"

Typically, methods are defined on the object instance instead of the class. However, you can define a method on the class — this is considered to be a static execution.

class Student {
static defaultWelcome() {
return "Hello"
}
}
Person.defaultWelcome() // => "Hello"

19. A class can extend another class. As a result, all objects initialized using that class inherit all the methods of both classes. For example:

class Person {
hello() {
return "Hello"
}
}
// We can define a new class that extends Person. class Engineer extends Person {}// Now if we instantiate a new object with the class Programmer, it has access to the hello() method in the Person class. let sophie = new Engineer()
sophie.hello()
// => "Hello"
// Inside a child class, you can reference the parent class by calling super().class Engineer extends Person {
hello() {
return super.hello() +
", I'm a software engineer!"
}
}
const matthew = new Programmer()
matthew.hello()
// => "Hello, I'm a software engineer!"

19. Without intervention, JavaScript runs synchronously — meaning that it executes lines of code one at a time; left to right, top to bottom. However, there are times when we cannot wait for a line of code to execute. It would seriously impede user experience if a program halted in order to load a large file. According to Kissmetrics, “47% of users expect a website to load in less than 2 seconds, and 40% of users will leave the website if the loading process takes more than 3 seconds.” This dramatic decrease in traffic due to slow loading speeds would be the death of a website/company! Instead, we can load that large file in the background while doing/displaying other things. JavaScript implements this problem solving approach using callbacks.

A simple example of callbacks leverages timers (made available in a JavaScript program from the browser and Node.js). One timer is setTimeout(); this timer accepts two arguments — a function and integer. The integer is the milliseconds that must pass before the function is run. For example:

// By putting console.logs before and after the function that must wait 2 seconds before running, we can see the magic of the callback / we can see how the program is functioning asynchronously.console.log("Before")setTimeout(() => {
console.log("Inside the Function")
}, 2000)
console.log("After")// => "Before"
// => "After"
// => "Inside the Function"

This is a very common pattern and especially useful for fetching data from the server, responding to events, etc.. Callbacks are implemented in code by passing them into functions and defining them as parameters.

When the code is ready to invoke the callback, we invoke it by passing the result:

doSomething = callback => {
// some code
const result = "Result"
callback(result)
}
doSomething(result => {
console.log(result)
})

20. Promises are an alternative (and arguably better) way to deal with asynchronous code. The main issue with callbacks is that if we need to use the result of the function in another part of our code, all code must be nested inside the callback. If we have to do 2–3 callbacks, there are many levels of functions indented into other functions:

doSomething(result => {
doSomethingElse(anotherResult => {
doSomethingElseAgain(yetAnotherResult => {
console.log(result)
})
})
})

With promises, instead of doing this:

doSomething(result => {
console.log(result)
})

We can do this:

doSomething()
.then(result => {
console.log(result)
})

We first call the function, then we have a then() method that is called when the function ends. You can detect errors using the catch() method:

doSomething()
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})

To be able to use this syntax, the doSomething() function must use the Promise API/declare it as a promise object and pass a function in the promise constructor. The function receives two parameters — the first is a function we call to resolve the promise, the second a function we call to reject the promise.

const doSomething = new Promise(
(resolve, reject) => {

})

Resolving a promise means to complete it successfully (which results in calling the then() method). Rejecting a promise means ending it with an error (which results in calling the catch() method). For example:

const doSomething = new Promise(
(resolve, reject) => {
//some code
const success = /* ... */
if (success) {
resolve("Completed successfully")
} else {
reject("Rejected")
}
}
)

21. Async functions return a promise. For example:

const getData = () => {
return new Promise((resolve, reject) => {
setTimeout(() =>
resolve("Some Data"), 2000)
})
}

Any code that wants to use this function will use the await keyword before the function:

const data = await getData()

By doing so, the data returned by the promise is going to be assigned to the data variable. Note: whenever we use the await keyword, we must do so inside a function defined as async:

const doSomething = async () => {
const data = await getData()
console.log(data)
}

Async functions allow us to simplify our code opposed to using promises or callback functions listed above. The efficacy of this simplification is seen when our code becomes more complex — for example, if we were fetching and parsing a JSON resource using promises:

// This will: 1. get a list of all users from our server, 2. parse the JSON object, 3. select the first user, 4. obtain the first user's data, and 4. parse that JSON object.const getFirstUserData = () => {
return fetch("/users.json")
.then(response => response.json())
.then(users => users[0])
.then(user =>
fetch(`/users/${user.name}`))

.then(userResponse => response.json())
}
getFirstUserData()

Below is the same functionally using using await/async:

// The async/await tells our program to wait until we have all the users data / json objects to proceed more specifically to the first user.const getFirstUserData = async () => {
const response = await fetch('/users.json')
const users = await response.json()
const user = users[0]
const userResponse =
await fetch(`/users/${user.name}`)
const userData = await user.json()
return userData
}
getFirstUserData()

--

--