0% found this document useful (0 votes)
7 views

JS Advanced Notes

The document is an accompanying ebook for the 'JavaScript Pro' video course on StevenCodeCraft.com, designed to enhance learning through reading and exercises. It covers advanced JavaScript topics including Object-Oriented Programming (OOP), prototypes, ES6 classes, and asynchronous JavaScript, emphasizing the importance of understanding these concepts for effective software development. The ebook serves as a reference tool for users to solidify their knowledge and prepare for job interviews in the field of programming.

Uploaded by

aliesiere42
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

JS Advanced Notes

The document is an accompanying ebook for the 'JavaScript Pro' video course on StevenCodeCraft.com, designed to enhance learning through reading and exercises. It covers advanced JavaScript topics including Object-Oriented Programming (OOP), prototypes, ES6 classes, and asynchronous JavaScript, emphasizing the importance of understanding these concepts for effective software development. The ebook serves as a reference tool for users to solidify their knowledge and prepare for job interviews in the field of programming.

Uploaded by

aliesiere42
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 557

JavaScript Pro

StevenCodeCraft.com
Table Of
Contents
StevenCodeCraft.com

Table Of
Contents
Intro to Advanced Topics 3
Objects 5
Prototypes 37
Prototypical Inheritance 73
ES6 Classes 113
ES6 Tooling 154
Node Module System 189
Node Package Manager 264
Asynchronous JavaScript 336
JavaScript Essentials 338
01

Intro to
Advanced
Topics

StevenCodeCraft.com
Page 3
StevenCodeCraft.com

JavaScript Pro
This PDF is meant to accompany the video course, "JavaScript Pro" on
StevenCodeCraft.com. To get the most out of the course, read a section of
this ebook and then watch the associated video course lesson. By reading,
watching, and going through the exercises and active recall questions, you
can truly solidify your learning and master JavaScript.

The main benefit of this ebook is that whether you cancel your subscription
to StevenCodeCraft.com or continue, you will be able to reference this ebook
to refresh your knowledge and it can serve as a helpful study tool for your
future job interviews and mastering your craft.

Thank you for giving StevenCodeCraft.com a chance, and I hope that you get
great value from my courses and that you achieve massive success in your
career and future entrepreneurial ambitions.

StevenCodeCraft.com
Page 4
02

Objects

StevenCodeCraft.com
Page 5
StevenCodeCraft.com

What is OOP?
Object-Oriented Programming (OOP) is a programming paradigm that
focuses on using objects to design and build applications.

This approach organizes software design around data, or objects, rather


than functions and logic. In OOP, objects are instances of classes, which can
encapsulate data and functions together. This methodology promotes
greater modularity and code reusability. Languages that support OOP include
C#, Java, Python, JavaScript, and many others. These languages provide the
tools needed to create and manipulate objects, making it easier to manage
complex software projects.

Introduction to Object-Oriented
Programming (OOP)
Object-Oriented Programming (OOP) is a programming paradigm centered
around objects rather than functions.

This approach is designed to improve software design by organizing data and


behavior into units known as objects.

StevenCodeCraft.com
Page 6
StevenCodeCraft.com

The four main principles of OOP are:

Abstraction
Hiding the complex implementation details of objects and exposing only the
necessary parts. This makes it easier to work with objects without needing to
understand all their complexities.

Polymorphism
Polymorphism means "many forms". Enabling objects to be treated as
instances of their parent class rather than their actual class. This allows for
methods to behave differently based on the object that is calling them.

Inheritance
Allowing new objects to take on properties and behaviors of existing
objects. This helps to reduce redundancy and improve code reusability.

Encapsulation
Combining data and functions that manipulate the data into a single unit
called an object. This helps to keep the data safe from outside interference
and misuse.

You can remember these four pillars by the acronym, A.P.I.E. where A is for
abstraction, P is for polymorphism, I is for inheritance, and E for
encapsulation.

StevenCodeCraft.com
Page 7
StevenCodeCraft.com

Transition from Procedural Programming


Previously, programs were written using procedural programming, which
involves dividing a program into a set of functions. These functions operate
on data stored in variables. While this approach is simple, it can lead to
issues such as:

- Code Duplication
Frequently copying and pasting code, leading to redundancy.

- Interdependencies
Changes in one function can inadvertently break other functions, creating
spaghetti code.

Moving to Object-Oriented Programming


OOP addresses these issues by combining related variables and functions
into a single unit called an object. Variables within an object are referred to as
properties, and functions are referred to as methods. For example, consider
the localStorage object in your browser, which encapsulates data storage
functionality.

StevenCodeCraft.com
Page 8
StevenCodeCraft.com

Abstraction
Hides the complex logic within objects. Users interact with a simplified
interface without needing to understand the underlying complexity.

Polymorphism
Allows for the implementation of methods that can operate on objects of
different types, eliminating the need for switch and case statements.

Inheritance
Allows for the creation of new classes based on existing ones, reducing code
duplication and enhancing reusability.

Encapsulation
Groups related data and methods within an object, making it easier to
manage and reducing the number of parameters needed in methods.

By adopting OOP principles, you can write more modular, maintainable, and
scalable code. This approach simplifies interactions between different parts
of your application and makes it easier to handle changes and extensions in
the future.

StevenCodeCraft.com
Page 9
StevenCodeCraft.com

Object Literals
In JavaScript, an object can be defined using curly braces, which signify an
object literal. An object literal is a way to organize data using key-value pairs.
Here’s how you can declare an empty object:

let programmer = {};

The curly braces {} indicate that programmer is an object. We can populate


this object with properties and methods.

StevenCodeCraft.com
Page 10
StevenCodeCraft.com

In this instance, the programmer object has four members:

Two properties: 'name' and 'preferredLanguage' which store data


Two methods: writeCode and drinkCoffee, which are functions that
perform actions.

This structure allows us to encapsulate related properties and functions


within a single, organized entity, making our code cleaner and more intuitive
to work with. By leveraging object literals, you can create versatile and
reusable code structures that represent real-world entities or complex data
models effectively.

Exercise: Creating a Grocery List Item


To help reinforce the concept, your exercise is to create a simple JavaScript
object literal representing a grocery list item. This object should have the
properties quantity and name, along with a method display that logs the
quantity and name to the console in the format quantity x name.

Refer to the video lesson: "Object Literals" for the solution


and explanation of the exercise on StevenCodeCraft.com

StevenCodeCraft.com
Page 11
StevenCodeCraft.com

Factories
Suppose you need to add another programmer to your team. Using the
object literal syntax repeatedly can lead to duplicated code, which makes
your application harder to maintain. This is especially true for objects that
include methods, indicating that the object has behavior which may be
repeated across multiple instances. Consider the following object
representing a programmer:

If we need to create multiple programmer objects, it would be inefficient and


error-prone to duplicate this code each time. A more scalable solution is to
use a factory function. A factory function is a function that returns a new
object each time it is called, ensuring that each object has its unique
properties but shares the same methods.

StevenCodeCraft.com
Page 12
StevenCodeCraft.com

Here’s how you can implement a factory function to create programmer


objects:

You can now create a new programmer simply by calling this function:

Constructors
In traditional JavaScript, there are no native classes as found in languages like
Java or C#. Instead, JavaScript uses functions and the new keyword to mimic
class-like behavior, a technique known as constructor functions. This
approach was the standard before ES6 introduced class syntax as syntactic
sugar to simplify object creation and inheritance.

StevenCodeCraft.com
Page 13
StevenCodeCraft.com

Here’s an example of a constructor function for creating Programmer


objects:

Constructor Property
Every object in JavaScript includes a special property known as
constructor. This property references the function that was used to create
the object via the new keyword. Understanding this property is key to
grasping how JavaScript manages object creation and inheritance.

For instance, consider the following example where we create an object from
a constructor function:

StevenCodeCraft.com
Page 14
StevenCodeCraft.com

To find out which constructor function created newProgrammer, you can


access its constructor property. This will output the Programmer function,
confirming that newProgrammer is indeed an instance of Programmer. This
property is particularly useful for confirming the type of object you're
working with, especially when dealing with complex codebases that involve
multiple constructors and prototypes.

Functions are Objects


In JavaScript, functions are treated as objects. This means that like any object,
a function can have properties and methods, and it can be assigned to
variables or passed as arguments to other functions. Consider the following
simple function:

You can assign this function to another variable, effectively creating a


reference to the original function. As an object, the function add has
properties and methods. For example, the length property of a function
returns the number of its expected arguments.

StevenCodeCraft.com
Page 15
StevenCodeCraft.com

Functions also have a constructor property, which points to the function that
created them. This is demonstrated with the following constructor function:

To further illustrate how functions are objects, consider creating a function


using the Function constructor:

So this lesson shows the unique nature of functions in JavaScript. By


understanding that functions are indeed objects, you gain a deeper insight
into the flexibility and capabilities of JavaScript as a programming language.

StevenCodeCraft.com
Page 16
StevenCodeCraft.com

Value vs Reference Types


JavaScript supports eight different data types, which include seven primitive
types and one complex type. The primitive types are number, string, boolean,
BigInt, undefined, null, and Symbol. The eighth data type is object, which also
encompasses arrays and functions. Understanding the distinction between
these types is crucial due to how they are allocated and managed in memory.

Primitive Types: Passed by Value


When you work with primitive values, these are passed by copy. This means if
you assign a variable to another containing a primitive value, the new variable
gets a copy of that value. Altering one will not affect the other because each
variable holds its own unique value in a separate memory location:

StevenCodeCraft.com
Page 17
StevenCodeCraft.com

Reference Types: Passed by Reference


On the other hand, reference types like objects are handled differently. They
are passed by reference, meaning both variables point to the same object in
memory. Thus, changes made through one variable are reflected in the
other.

Summary
In JavaScript, primitive values are copied by value, meaning they do not affect
one another when changed. Objects, including arrays and functions, are
copied by reference, meaning changes to one affect any other variable
referencing the same object. This fundamental understanding helps in
managing how data is passed around in your programs, ensuring fewer
surprises and more predictable code behavior.

StevenCodeCraft.com
Page 18
StevenCodeCraft.com

Adding or Removing Properties


Objects in JavaScript are inherently dynamic, which means that you can add
or modify their properties and methods at any time after their
creation. Using the const keyword with an object declaration ensures that the
variable cannot be reassigned to a different value. However, the contents of
the object it points to, such as its properties and methods, can still be altered
or extended. Consider the following example:

StevenCodeCraft.com
Page 19
StevenCodeCraft.com

Enumerating Properties
In JavaScript, different loops offer various ways to iterate over collections like
arrays and objects.

For Arrays: Using the for-of Loop

The for-of loop is ideal for iterating over array elements. Here's how you can
use it with an array of numbers:

For Objects: Using the for-in Loop

The for-in loop allows you to iterate over the keys of an object. This is useful
for accessing values when you know the structure of the object:

StevenCodeCraft.com
Page 20
StevenCodeCraft.com

Enumerating Keys, Values, and Entries

Besides traditional loops, JavaScript provides methods to retrieve keys,


values, and key-value pairs directly from objects, which can then be iterated
over:

Keys

Values

StevenCodeCraft.com
Page 21
StevenCodeCraft.com

Entries

These methods provide a powerful and flexible way to handle objects in


JavaScript. They simplify the process of working with object properties and
make your code cleaner and more efficient.

Abstraction
Abstraction is a core concept in object-oriented programming that involves
hiding complex details while exposing only the necessary parts of a class or
object to the user. This makes the objects in our applications easier to
interact with and reduces the impact of changes.

StevenCodeCraft.com
Page 22
StevenCodeCraft.com

Consider this Programmer constructor function as an example:

In this example, writeCode is a public method that is accessible to any


instances of Programmer. On the other hand, drinkCoffee is defined as a
private method inside the constructor using the const keyword. This method
is not accessible from outside the Programmer function. Instead, it is meant
to be used internally by other methods within the Programmer object, like
startDay, which exposes a controlled interaction with drinkCoffee.

StevenCodeCraft.com
Page 23
StevenCodeCraft.com

Key Points on Abstraction


Show What's Necessary

Like in our example, the startDay method abstracts away the details of what
starting the day entails for a programmer (in this case, drinking coffee). The
user of the object does not need to know about these details; they just need
to know that they can start the day.

Hide the Complexities

Methods like drinkCoffee do not need to be exposed outside of the object, as


they handle specific functionalities that are irrelevant to the user.

This approach helps in maintaining a cleaner interface for the objects, making
them easier to use and reducing dependencies on the internal
implementation details. This leads to better modularity and easier
maintenance of our code.

StevenCodeCraft.com
Page 24
StevenCodeCraft.com

Private Properties and Methods


Closures in JavaScript provide a powerful way to achieve encapsulation, one
of the core principles of object-oriented programming.

So a closure means that an inner function has access to variables declared in


its outer function. This means you can hide the internal state and
functionality of an object, exposing only what is necessary to the outside
world. Let’s take a look at how closures can be used to create private
properties and methods within a constructor function:

The reason why we do this is because we want our JavaScript objects to be


easy to work with which is achieved through a minimal public interface. So
rather than have an overwhelming amount of public properties and public
methods, we only want to expose what is absolutely necessary for the object
to carry out the desired functionality.

StevenCodeCraft.com
Page 25
StevenCodeCraft.com

Let's go over an example for this to make more sense:

so even though the example function has returned and finished executing, its
inner function still have access to its parents scope. this is done because at
the time of function declaration, JavaScript creates a lexical environment. This
is an internal data structure that JavaScript uses to keep track of identifiers
(variables and function names) and their values. A lexical environment stores
all of the locally available identifiers as well as a reference to the parent
environment

Lexical scoping is the scoping system in JavaScript that ensures all code
blocks have access to all identifiers in their parent environment when an
identifier is not defined locally, JavaScript will look to the parent environment
for it.

StevenCodeCraft.com
Page 26
StevenCodeCraft.com

Note that private variables in JavaScript can be made with # prefix so like
#privateNum, but this is not yet supported in most browsers.

StevenCodeCraft.com
Page 27
StevenCodeCraft.com

In this example:

Private Properties and Methods


The privateName variable and the drinkCoffee function are defined with let,
respectively. These are private because they are not exposed via this. They
are only accessible within the Programmer function itself.

StevenCodeCraft.com
Page 28
StevenCodeCraft.com

Public Properties and Methods


preferredLanguage and writeCode are accessible outside the
function because they are defined with the this keyword. Any instance of
Programmer can call writeCode.

Using Closures
The startDay method demonstrates a closure. It is a public method that can
access the private drinkCoffee method. This is possible because inner
functions in JavaScript have access to the variables of their outer functions
even after those outer functions have returned.

Closure vs. Scope


Scope
Is generally the context in which variables and expressions are visible or can
be referenced. If a variable or other expression is not "in the current scope,"
then it is unavailable for use.

Closures
Functions that capture and reference external variables. So when I say
external variables, this refers to variables which are not defined locally in the
function, but these variables are accessible because they exist in the outer
environment.

StevenCodeCraft.com
Page 29
StevenCodeCraft.com

The function defined in the closure 'remembers' the environment in which it was created at
the time of function declaration. This is why drinkCoffee remains accessible only within
functions defined in the same scope. Closures are a fundamental and powerful aspect of
JavaScript, allowing for more secure and modular code by protecting and encapsulating the
behavior within objects.

Getters and Setters


Getters and setters are special methods that provide you with a way to get and set the
properties of an object. This encapsulation technique allows you to control how important
values are accessed and modified in your objects, it's often used to ensure that data
encapsulation and validation rules are followed. Here’s how you can incorporate getters and
setters into the Programmer function using Object.defineProperties (view on next page):

StevenCodeCraft.com
Page 30
StevenCodeCraft.com

StevenCodeCraft.com
Page 31
StevenCodeCraft.com

Explanation

Getters and Setters


The name property has both a getter and a setter. The getter simply returns
the current value of privateName, while the setter allows you to validate the
new name before assigning it to privateName.

Heading
This structure ensures that the internal state of the object can only be
changed in controlled ways, increasing data integrity and interaction safety.

Using getters and setters not only encapsulates the internal states but also
allows for additional logic to be implemented when getting or setting a
property, such as validation or logging, which enhances the functionality and
robustness of your code.

StevenCodeCraft.com
Page 32
StevenCodeCraft.com

Summary
In this section, we covered the fundamentals of Object-Oriented Programming (OOP),
beginning with a definition of OOP and an exploration of its four core pillars. We
covered essential concepts and techniques including object literals, factory functions,
and constructor functions, which are foundational in understanding how objects are
created and utilized in JavaScript. We also discussed the constructor property, which
links an object back to its constructor function, and examined how functions
themselves can be treated as objects in JavaScript. This leads to a deeper
understanding of JavaScript's flexible approach to OOP. Important distinctions
between value and reference types were highlighted, clarifying how JavaScript handles
data storage and manipulation, which is crucial for effective memory management and
performance. Further, we explored dynamic aspects of objects such as adding,
removing, and enumerating properties, providing practical skills for manipulating
object properties in real-time. The concept of abstraction was introduced along with
practical implementations of private properties and methods, which enhance
encapsulation and data hiding in JavaScript. Finally, the use of getters and setters was
discussed, illustrating how these can be employed to control access to an object’s
properties, ensuring data integrity and encapsulation.

StevenCodeCraft.com
Page 33
StevenCodeCraft.com

Active Recall Study Questions


7) What is an object literal and how would you create one?
8) What are factory functions and why would you use them?
9) What is a constructor function?
10) What are primitive values and what are they passed by?
11) What are object values and what are they passed by?
12) Explain in your own words, how are objects dynamic?
13) How can you enumerate over the properties of an object?
14) What are closures?

StevenCodeCraft.com
Page 34
StevenCodeCraft.com

Active Recall Answers


7) What is an object literal and how would you create one?
An object literal is a data structure in JavaScript that allows you to define key-value
pairs. It essentially enables you to group together variables and functions in the
context of an object. The variables and functions would be referred to as properties
and methods of the object. You create an object literal by using curly braces.

8) What are factory functions and why would you use them?
Factory functions provide an efficient way for us to create a new object. So the name of
the function would be in camelCase naming convention and it can accept parameter
variables. These parameter variables can help customize the values of the object being
returned. The benefit is that it reduces code duplication and the need to copy and
paste code.

9) What is a constructor function?


A constructor function is used to instantiate a new object in JavaScript. So for the
naming convention, you would name the function using PascalCase and for assigning
the properties and for the methods, you would use the ‘this’ keyword which references
this object or the current object.

StevenCodeCraft.com
Page 35
StevenCodeCraft.com

10) What are primitive values and what are they passed by?
Primitive values refer to simple values, such as string, number, boolean, and they are
passed by copy.

11) What are object values and what are they passed by?
Object values refer to key-value pairs and include arrays, which are passed by
reference.

12) Explain in your own words, how are objects dynamic?


Objects in JavaScript are dynamic because you can change and mutate the properties
and methods of the object after it has been initialized.

13) How can you enumerate over the properties of an object?


There are different ways to enumerate over the properties of an object. One way is to
use the for-in loop to iterate over the keys of an object. We can also use the built-in
object class. For example, enumerating over the keys with Object.keys(obj),
enumerating over the keys with Object.values(obj), and enumerating over the
properties with Object.entries(obj).

14) What are closures?


Closures are inner functions that are able to access variables defined in its outer
function. So in other words, closure functions can access variables from their outer
scope even after the outer function has finished executing. You would utilize closures
in order to achieve encapsulation and to hide private variables or private properties.

StevenCodeCraft.com
Page 36
03

Prototypes

StevenCodeCraft.com
Page 37
StevenCodeCraft.com

Overview of Inheritance in JavaScript


This section focuses on inheritance, a fundamental concept in object-oriented
programming that allows one object to inherit the properties and methods of
another. This mechanism is key for reusing code efficiently and effectively. I'll
introduce some terms that are critical to understanding different aspects of
inheritance:

Base/Super/Parent Class
These three terms are all synonymous and all mean the same thing
(Base/Super/Parent class) These terms refer to the class whose features are inherited
by other classes.

Derived/Sub/Child Class
These are classes that inherit properties and methods from the base class. This
relationship is often referred to as an "is-a" relationship, indicating that the derived
class is a specialized form of the base class.

We also differentiate between two types of inheritance:

Classical Inheritance
Typically found in class-based languages, where inheritance is defined through classes.

Prototypical Inheritance
Specific to JavaScript, this type of inheritance does not involve classes. Instead,
JavaScript uses prototypes—objects that other objects can inherit properties and
methods from.
StevenCodeCraft.com
Page 38
StevenCodeCraft.com

It's important to note that in JavaScript, the traditional concept of classes is


implemented differently. While ES6 (which stands for modern JavaScript)
introduced class syntax, it is syntactical sugar over JavaScript's existing
prototypical inheritance model. Understanding this helps clarify how
JavaScript handles inheritance and sets the stage for using these concepts to
structure and reuse code effectively.

Prototypes and Prototypical


Inheritance
In programming, enhancing an object's functionality is a common task. For
instance, if we have a user object with defined properties and methods, and
we need to create admin and guest objects, it is beneficial to reuse what we
have in the user object. (view code on the next page) The admin and guest
objects are more specific versions of the user object. Both an admin and a
guest are users of the website. We can utilize Prototypal Inheritance to
accomplish this. In JavaScript, objects possess a special hidden property
[[Prototype]] that is either null or references another object. This referenced
object is called a "prototype." When you see the term prototype, think of it as
interchangeable with 'parent,' similar to a parent class from which properties
and methods are inherited.

StevenCodeCraft.com
Page 39
StevenCodeCraft.com

StevenCodeCraft.com
Page 40
StevenCodeCraft.com

Notice how we are not duplicating properties and methods in our objects. If
you attempt to use a property not defined in an object, JavaScript
automatically retrieves it from the prototype. This concept is known as
prototypal inheritance. The [[Prototype]] property is internal and hidden, but
it can be set in various ways, one of which is using the special __proto__
property. If the user object has many useful properties and methods, they
become automatically available in admin. These properties are called
inherited properties. The prototype chain can extend further:

Limitations
1. Prototype references cannot form a circular chain; otherwise, JavaScript
throws an error. For example, setting the user object's __proto__
property to superAdmin would cause a circular reference.
2. The __proto__ value can only be an object or null; other types are
ignored. An object may not inherit from two other objects
simultaneously.

StevenCodeCraft.com
Page 41
StevenCodeCraft.com

The value of the this keyword in methods refers to the current object, even if
the method is inherited. Setting a property in an inherited object modifies
only that object's state and does not affect the base object's state.

The for...in Loop


The for...in loop iterates over both own and inherited properties of an object:

Other methods like Object.keys(), Object.values(), and Object.entries() only


consider properties directly on the object and ignore inherited properties.

Modern Approach
The __proto__ property is a getter and setter for the internal [[Prototype]]
property. However, in modern JavaScript, we prefer using
Object.getPrototypeOf() and Object.setPrototypeOf().

StevenCodeCraft.com
Page 42
StevenCodeCraft.com

Summary of what we've covered so far


In JavaScript, all objects have a hidden [[Prototype]] property that is either
another object or null. The object referenced by [[Prototype]] is called a
"prototype." If a property or method is not found in an object, JavaScript
looks for it in the prototype. Write and delete operations directly affect the
object itself, not the prototype. The for...in loop iterates over both explictly
defined and inherited properties, while other key/value-getting methods only
operate on the object itself.

Another example of Prototypical Inheritance


In JavaScript, we know that inheritance is achieved using prototypes, which
are essentially objects that other objects can use as a template. We know that
this process is called prototytpal inheritance.

How Prototypical Inheritance Works:


Base Object with Common Behavior
Create an object that encapsulates common behaviors and properties that
are shared among all programmers. (view on next page)

StevenCodeCraft.com
Page 43
StevenCodeCraft.com

Creating a Derived Object


Use the base object as a prototype to create specialized programmer
objects.

StevenCodeCraft.com
Page 44
StevenCodeCraft.com

In this example, we define a Programmer constructor function that sets up private


properties and public properties using Object.defineProperties. We then set the
prototype of each instance to programmerPrototype using Object.setPrototypeOf,
allowing instances like jsProgrammer to inherit methods like writeCode and
drinkCoffee.

Testing the Inheritance


Verify that the inheritance works correctly by calling the methods on the
jsProgrammer object.

This example showcases how to effectively use prototypes to implement inheritance in


JavaScript, allowing for shared behaviors and properties while maintaining
encapsulation of private data. By leveraging prototypes, the Programmer object can
be extended to create various types of programmers with minimal code duplication.

StevenCodeCraft.com
Page 45
StevenCodeCraft.com

Understanding Multilevel Inheritance in


JavaScript
In JavaScript, multilevel inheritance allows objects to inherit properties and
methods from multiple levels of prototypes. Consider this simple example

let myArray = [];

When myArray is created, it is not just an instance of an array but also part of
a larger inheritance hierarchy:

1. myArray is an instance of Array.


2. Array.prototype (often referred to as arrayBase) provides methods and
properties common to all arrays.
3. Object.prototype (often referred to as objectBase) is the prototype for
arrayBase. It provides methods and properties that are inherited by all
objects in JavaScript, including arrays.

This setup forms a multilevel inheritance chain:

myArray inherits from Array.prototype


Array.prototype inherits from Object.prototype.

StevenCodeCraft.com
Page 46
StevenCodeCraft.com

This is how the inheritance looks in a structured form:

In JavaScript, myArray inherits from Array.prototype (arrayBase), which in turn


inherits from Object.prototype (objectBase), forming a prototype chain where
Object.prototype is at the top.

Key Points:

Objects created by a specific constructor, like Array, will share the same
prototype (Array.prototype).
This multilevel inheritance allows objects like myArray to access properties and
methods defined in Object.prototype, giving JavaScript its dynamic and flexible
nature.

Understanding this inheritance structure helps in recognizing how methods like


toString() or hasOwnProperty() are available to your array, even though they are not
directly defined within the Array constructor or on the array instance itself. This
concept is fundamental to mastering JavaScript’s prototype-based inheritance and
helps in designing more efficient and robust applications.

StevenCodeCraft.com
Page 47
StevenCodeCraft.com

Property Descriptors
In JavaScript, when you define an object like this:

And iterate over its properties:

You'll notice that only the properties directly defined on the person object
are displayed. Similarly, when using Object.keys(person), you get the same
result, showing only the properties defined at the object's own level.

StevenCodeCraft.com
Page 48
StevenCodeCraft.com

Why Properties from objectBase Aren't Displayed


The properties inherited from Object.prototype (referred to as objectBase
when accessed via Object.getPrototypeOf(person)) aren't displayed because
of specific attributes attached to them. These attributes can control whether
properties are enumerable, writable, and configurable. To see this in action,
let’s examine the property descriptor for the toString method inherited from
objectBase:

The enumerable attribute set to false prevents the toString method from
appearing in the output of for-in loops and methods like Object.keys()

Modifying Property Attributes


You can also define or modify these attributes for properties in your own
objects. For instance, if you want to make the name property non-
enumerable, you could do this:

StevenCodeCraft.com
Page 49
StevenCodeCraft.com

After this change, name will no longer appear in for-in loops or when calling
Object.keys(person).

Default Property Attributes


By default, when properties are created directly on an object, their writable,
enumerable, and configurable attributes are set to true. This allows
properties to be modified, enumerated, and reconfigured unless explicitly
changed. Understanding these property attributes and their implications
helps in managing how data in objects is accessed and manipulated,
providing greater control over object behavior in JavaScript applications.

StevenCodeCraft.com
Page 50
StevenCodeCraft.com

Constructor Prototypes
In JavaScript, every object has a prototype, except for the root object, which is
at the top of the prototype chain. The prototype acts as a template or parent
from which the object inherits methods and properties.

Accessing an Object's Prototype


The recommended method to get the prototype of an object is by using:

Object.getPrototypeOf(myObj);

This function returns the prototype (or the parent) of the specified object.

Constructors and Their Prototypes


In JavaScript, constructors are special functions used to create instances of
objects. Since functions in JavaScript are objects themselves, they too have
properties, one of which is the prototype property. This property is not the
prototype of the constructor itself, but rather the object that will be used as
the prototype for all instances created with that constructor.

StevenCodeCraft.com
Page 51
StevenCodeCraft.com

Example

Creating Objects and Their Prototype Links


When you create a new object, its prototype is set to the prototype of its
constructor function. This is illustrated with:

The prototype of obj can be referred to using __proto__, which is an accessor


property that exposes the internal prototype linkage:

StevenCodeCraft.com
Page 52
StevenCodeCraft.com

Understanding how prototypes and constructors work in JavaScript is crucial for


effectively leveraging the language's object-oriented capabilities. By grasping these
concepts, developers can better manage and utilize object inheritance in their
programs.

Prototype vs Instance Members


Optimizing Memory Usage with Prototypes in JavaScript

In practical applications, especially those involving numerous object instances, it's


important to manage memory efficiently. If each object instance were to maintain its
own copies of methods, this would lead to significant memory overhead when
dealing with thousands of objects.

StevenCodeCraft.com
Page 53
StevenCodeCraft.com

How JavaScript Handles Method Access


To address this, JavaScript utilizes prototype-based inheritance:

When a method is called on an object, the JavaScript engine first checks


if the method exists on the object itself.
If the method is not found, the engine then looks up the prototype chain
of the object.

Utilizing the Prototype Property


Each constructor function in JavaScript has a prototype property, which is
shared by all instances of the constructor. This shared prototype is crucial for
memory efficiency.

StevenCodeCraft.com
Page 54
StevenCodeCraft.com

Advantages of Using Prototypes


Memory Efficiency
Since methods defined on the prototype are shared, there is only one copy of
each method in memory, regardless of the number of instances. This is
especially beneficial in applications where many instances of a class are
created, as it saves a significant amount of memory.

Flexibility
JavaScript's dynamic nature allows you to add or modify methods on the
prototype at any time. Modifications to the prototype are reflected across all
instances immediately, which can be particularly useful for updating
functionality at runtime.

Overriding Methods
You can easily override inherited methods by defining new implementations
on the prototype. For example, the toString method for Programmer has
been overridden to provide more specific information about each
programmer instance.

StevenCodeCraft.com
Page 55
StevenCodeCraft.com

Managing Instance and Prototype Members


Instance Members
Defined within the constructor function, these are unique to each instance, such as
name and preferredLanguage in the Programmer constructor. These properties are
set per individual object and manage data that varies from one instance to another.

Prototype Members
Defined on the constructor’s prototype, these members are shared across all
instances, like the writeCode and toString methods. This setup is optimal for
methods that perform generic actions, applicable to all instances.

By strategically using prototypes to share methods among instances, you can


enhance your JavaScript applications both in terms of performance and
maintainability. This approach ensures efficient memory usage while maintaining
the flexibility to adapt the shared behavior of objects.

Understanding Prototype Dynamics in


JavaScript
In JavaScript, changes to a constructor's prototype affect all instances, regardless of
whether they were created before or after the changes. This behavior highlights
JavaScript's use of object references for prototypes.

StevenCodeCraft.com
Page 56
StevenCodeCraft.com

Consider the Programmer constructor function:

Now, let’s add a new method to the Programmer prototype:

Enumerating Object Properties


Using a for..in loop, you can enumerate all properties of an object, including those on
the prototype:

StevenCodeCraft.com
Page 57
StevenCodeCraft.com

In JavaScript documentation, properties defined directly on the object are


often referred to as "own" properties, while those inherited from the
prototype are called "prototype properties".

Checking for Own Properties


You can use the hasOwnProperty method to determine if a property is an
own property of the object:

This method helps distinguish between properties that are part of the object
itself and those inherited from the prototype. Understanding the distinction
between own and prototype properties is crucial for working effectively with
object-oriented features in JavaScript. This lesson demonstrates the dynamic
nature of prototypes and their practical implications, providing a deeper
understanding of how JavaScript handles object properties and inheritance.

StevenCodeCraft.com
Page 58
StevenCodeCraft.com

Avoid Extending the Built-in Objects


While JavaScript allows you to extend the functionality of built-in prototypes, such as
adding a shuffle method to Array.prototype, it is generally advisable to avoid doing so.
Here's how such an addition might look:

Though this approach may seem convenient, it carries significant risks:

StevenCodeCraft.com
Page 59
StevenCodeCraft.com

Potential for Conflicts


Library Conflicts
If you modify a built-in prototype, you risk conflicts with third-party libraries that
might rely on the default behaviors of these objects. Different implementations of
the same method can lead to inconsistent behavior across parts of your
application.

Best Practice: Do Not Modify Objects You Do Not Own

Maintainability and Compatibility


Modifying objects that are globally accessible (like built-in prototypes) can make
your code harder to manage and predict. It may also break compatibility with
future versions of JavaScript or interfere with enhancements in third-party
libraries.

Alternative Approach
Instead of modifying built-in prototypes, consider safer alternatives:

StevenCodeCraft.com
Page 60
StevenCodeCraft.com

Use a Utility Function


Implement your functionality as a standalone function that does not affect the
prototype. For example:

Extend the Prototype Safely


If absolutely necessary, ensure that your modifications do not overwrite existing
methods and check if the method isn't already defined:

By adhering to these guidelines, you ensure that your JavaScript code remains robust,
maintainable, and compatible with other libraries and future updates.

StevenCodeCraft.com
Page 61
StevenCodeCraft.com

Summary
This section has provided a thorough exploration of several key concepts in
JavaScript related to object-oriented programming. We began by
understanding inheritance, including prototypes and prototypical
inheritance, which are fundamental to how JavaScript handles objects and
classes. We discussed multilevel inheritance, where objects inherit properties
and methods across multiple levels, allowing for a more structured and
hierarchical object model. The use of property descriptors was covered to
control and manage how properties behave, including their enumerability,
configurability, and writability. Further, we examined the distinctions between
constructor prototypes and instance members, emphasizing the differences
and roles of each in JavaScript programming. The process of iterating over
both instance and prototype members was explored, giving practical insight
into how to access and manipulate object properties effectively. Lastly, we
addressed best practices regarding prototype manipulation, specifically
advising against extending built-in objects. This practice, while possible, can
lead to conflicts and compatibility issues, especially when dealing with third-
party libraries. Overall, this section aimed to equip you with a solid
understanding of how inheritance and prototypes contribute to efficient and
effective JavaScript programming, promoting better coding practices and a
deeper understanding of the language's capabilities.

StevenCodeCraft.com
Page 62
StevenCodeCraft.com

Active Recall Study Questionsa


15) What is inheritance and why is it important?
16) What type of inheritance model does JavaScript use? Prototypical
inheritance or classical inheritance?
17) What do these three terms refer to? (Base class, super class, and parent
class)
18) What do these three terms refer to? (Derived class, subclass, and child
class)
19) What is the __proto__ property?
20) What are the property descriptors or attributes in JavaScript that
determine whether a property can be accessed, modified, or iterated over?\
21) Does every object have a prototype? In other words, does every object
have a parent object?
22) Do constructor functions have their own prototype?
23) How does using prototype-based inheritance in JavaScript help in
managing memory efficiently when dealing with numerous object instances?]
24) What are the advantages of defining methods on a constructor’s
prototype as opposed to defining them within the constructor function itself?
25) How does adding a new method to a constructor’s prototype affect
existing instances of that constructor in JavaScript?
26) What is the difference between “own” properties and “prototype”
properties in JavaScript and how can you check if a property is an “own”
property of an object?

StevenCodeCraft.com
Page 63
StevenCodeCraft.com

27) Why is it generally advisable to avoid modifying built-in prototypes in


JavaScript?
28) What are some safer alternatives to modifying built-in prototypes in
JavaScript, and how can they help maintain code compatibility and
predictability?
29) What is the significance of prototypes and prototypical inheritance in
JavaScript’s handling of objects and classes?
30) How do property descriptors help manage the behavior of object
properties in JavaScript and what attributes can they control?
31) Why is it generally advised against extending built-in objects in JavaScript
and what are the potential risks of doing so?

StevenCodeCraft.com
Page 64
StevenCodeCraft.com

Active Recall Answers


15) What is inheritance and why is it important?
Inheritance is a concept in programming where one object can inherit
properties and methods from another object. In JavaScript, this is done
through prototypical inheritance (also called prototypal inheritance). It’s
important because it allows for code reuse, making your code more efficient
and easier to manager. Inheritance is one of the four pillars of object-
oriented programming along with encapsulation, abstraction, and
polymorphism.

16) What type of inheritance model does JavaScript use? Prototypical


inheritance or classical inheritance?
JavaScript uses the prototypical inheritance model. This means that objects
can inherit properties and methods directly from other objects rather than
from classes as in classical inheritance.

StevenCodeCraft.com
Page 65
StevenCodeCraft.com

17) What do these three terms refer to? (Base class, super class, and parent
class)
In JavaScript, the terms of base class, super class, and parent class all refer to
the same concept, the class in which another class inherits properties and
methods. These terms are used interchangeably to describe the original class
that provides functionality to a derived or child class. JavaScript introduced
class syntax in ES6 as a new way to define classes. However, under the hood,
JavaScript uses objects and prototypes to implement inheritance. These
features help reduce code duplication and make code easier to maintain.

18) What do these three terms refer to? (Derived class, subclass, and child
class)
In JavaScript, the terms derived class, subclass, and child class all refer to the
same concept. The refer to a class that inherits properties and methods from
another class, which acts as the base/parent class. These three terms
(derived class, subclass, and child class) are used interchangeably to describe
the new class that extends the functionality of the original class.

19) What is the __proto__ property?


The __proto__ property is a reference to the prototype of an object. It allows
objects to inherit properties and methods from another object. This property
is used to set or access the prototype directly, but it is considered
outdated. Instead modern JavaScript uses Object.getPrototypeOf() and
Object.setPrototypeOf() for these tasks.

StevenCodeCraft.com
Page 66
StevenCodeCraft.com

20) What are the property descriptors or attributes in JavaScript that


determine whether a property can be accessed, modified, or iterated over?
In JavaScript, property descriptors or property attributes, determine whether
a property can be accessed, modified, or iterated over. These are the
attributes of enumerable, writable, and configurable which all accept a
boolean value. So for enumerable, if set to true, the property will appear
during for-in loops, Object.keys(obj), Object.values(obj), and
Object.entries(obj). For writable, if set to true, then the properties value can
be changed. For configurable, if set to true, the property descriptor can be
changed and the property can be deleted.

21) Does every object have a prototype? In other words, does every object
have a parent object?
Every object has a prototype or a parent object except for the root object,
which is at the top of the prototype chain. So an object will inherit the
properties and methods from its prototype or parent object.

22) Do constructor functions have their own prototype?


Constructor functions have their own prototypes. In JavaScript, functions are
objects so they have their own prototype property which is used to assign
properties and methods to instances created by the constructor function.

StevenCodeCraft.com
Page 67
StevenCodeCraft.com

23) How does using prototype-based inheritance in JavaScript help in


managing memory efficiency when dealing with numerous object instances?
Using prototype based inheritance in JavaScript helps manage memory
efficiently because methods defined on the prototype are shared among all
instances of an object. instead of each instance having its own copy of the
method, there’s only one copy stored in memory. This significantly reduces
memory usage, especially when creating thousands of instances as all
instances refer to the same method on the prototype.

24) What are the advantages of defining methods on a constructor’s


prototype as opposed to defining them within the constructor function itself?
Defining a method on a constructor’s prototype in JavaScript has several
advantages. One being memory efficiency as methods on the prototype are
shared by all instances. So only one copy of each method exists in memory
which helps save space. A second advantage is consistency. Changes to a
prototype method are immediately reflected in all instances, making it easy
to update functionality across multiple objects. A third advantage is
performance. Shared methods reduce the memory overhead and can lead to
better performance as there is no need to duplicate methods for each
instance.

StevenCodeCraft.com
Page 68
StevenCodeCraft.com

25) How does adding a new method to a constructor’s prototype affect


existing instances of that constructor in JavaScript?
Adding a new method to a constructor’s prototype in JavaScript makes that
method available to all existing instances of that constructor as well as any
new instances created afterward. This is because instances reference the
prototype for methods. So they automatically have access to any new
methods added to the prototype, even if they were created before the
method was added.

26) What is the difference between “own” properties and “prototype”


properties in JavaScript and how can you check if a property is an “own”
property of an object?
In JavaScript, “own” properties are those defined directly on an object while
prototype properties are inherited from the object’s prototype. To check if a
property is an owned property of an object, you can use the
.hasOwnProperty() method. If it returns true, then the property is an “own”
property. If it returns false, the property is inherited from the prototype
(parent object).

StevenCodeCraft.com
Page 69
StevenCodeCraft.com

27) Why is it generally advisable to avoid modifying built-in prototypes in


JavaScript?
It is generally advisable to avoid modifying built-in prototypes in JavaScript
because doing so can cause conflicts with third party libraries that rely on the
default behaviors of these objects. Modifying properties can also make code
harder to maintain and predict. It might break compatibility with future
versions of JavaScript or interfere with enhancements in other libraries.

28) What are some safer alternatives to modifying built-in prototypes in


JavaScript, and how can they help maintain code compatibility and
predictability?
Some safer alternatives to modifying built-in prototypes in JavaScript include
using utility functions where you create standalone functions to add new
functionality without affecting built-in prototypes. This avoids potential
conflicts and keeps your code isolated and maintainable. You can also extend
prototypes safely. So if you must extend a prototype, first check if the method
already exists to avoid overriding existing functionality. These approaches
help maintain code compatibility and predictability by preventing conflicts
with existing or future code, and ensuring your additions do not interfere
with standard JavaScript behavior or third party libraries.

StevenCodeCraft.com
Page 70
StevenCodeCraft.com

29) What is the significance of prototypes and prototypical inheritance in


JavaScript’s handling of objects and classes?
Prototypes and prototypical inheritance are crucial in JavaScript because they
allow objects to inherit properties and methods from other objects. This
enables code reuse and efficient memory usage since methods can be
shared across instances rather than duplicated. Prototypical inheritance also
supports a flexible and dynamic object model, allowing developers to extend
and modify objects at runtime, which is fundamental to JavaScript’s object
oriented programming capabilities.

30) How do property descriptors help manage the behavior of object


properties in JavaScript and what attributes can they control?
Property descriptors help manage the behavior of object properties by
allowing you to control specific attributes of those properties. The attributes
they can control are enumerable, writable, and configurable. The enumerable
attribute determines if the property shows up in the for-in loop,
Object.keys(obj), Object.values(obj), and Object.entries(obj). The writable
attribute determines if the property’s value can be changed. The configurable
attribute determines if the property descriptor can be configured and if the
property can be deleted. Property descriptors enable you to precisely define
how properties behave, making your objects more secure and predictable.

StevenCodeCraft.com
Page 71
StevenCodeCraft.com

31) Why is it generally advised against extending built-in objects in JavaScript


and what are the potential risks of doing so?
It is generally advised against extending built-in objects in JavaScript because
it can cause conflicts with third party libraries that rely on the default
behavior of these objects. This can lead to inconsistent behavior and bugs.
Additionally modifying built in objects can make your code harder to maintain
and predict, and it may break compatibility with future versions of JavaScript
or interfere with updates and other libraries. A better practice is to utilize
utility functions or safely extending prototypes by checking for existing
methods.

StevenCodeCraft.com
Page 72
04

Prototypical
Inheritance

StevenCodeCraft.com
Page 73
StevenCodeCraft.com

Creating your own prototypical


inheritance
When building software, duplicating methods across different objects not
only consumes more memory but also goes against the DRY (Don't Repeat
Yourself) principle. Let’s consider how we can use inheritance to share
common functionality in a way that aligns with best practices. Imagine we
have multiple types of programmer objects, such as FrontEndProgrammer
and BackEndProgrammer, and they share some common behaviors. Instead
of duplicating methods across both constructors, we can define a generic
Programmer object that contains shared methods.

Base Shape Object for Shared Methods


First, define a Programmer function that will act as the base for other specific
programmer roles. So here we're going to add three methods for our
Programmer object and define it within the constructor function. (view on
next page)

StevenCodeCraft.com
Page 74
StevenCodeCraft.com

When you define methods directly within the Programmer constructor


function, each instance of Programmer will have its own copy of these
methods. This approach is less efficient in terms of memory usage because
every instance has duplicate methods.

In this case, Alice and Steven each have their own copies of the code, debug,
and meetings methods, which is redundant and can consume more memory,
especially if you create many instances. So instead we can define Methods on
the Prototype.

StevenCodeCraft.com
Page 75
StevenCodeCraft.com

When you define methods on Programmer.prototype, all instances of Programmer


share the same method implementations. This is more memory-efficient because the
methods are not duplicated for each instance. Instead, they are shared among all
instances, which is the essence of prototypical inheritance in JavaScript.

In this case, Alice and Bob share the same code, debug, and meetings methods, which
are defined on the prototype. This reduces memory usage and ensures that all
instances benefit from any updates to these methods without needing to update each
instance individually.

StevenCodeCraft.com
Page 76
StevenCodeCraft.com

So lets summarize this so far:

Memory Efficiency
Defining methods on the prototype ensures that all instances share the same
method implementations, reducing memory usage.

Method Sharing
Prototype methods are shared among all instances, making it easier to maintain
and update the methods.

Inheritance
Prototypical inheritance leverages this shared behavior, allowing derived objects
like FrontEndProgrammer and BackEndProgrammer to inherit and use these
methods without duplication. By defining methods on the prototype, you adhere
to the principles of efficient memory usage and code reuse, making your
JavaScript code more maintainable and performant.

StevenCodeCraft.com
Page 77
StevenCodeCraft.com

So lets define Specific Programmer Roles. Now, let’s create specific programmer
roles that inherit from Programmer:

To ensure that the Programmer constructor function is called for


FrontEndProgrammer and BackEndProgrammer, we use the .call() method. This
allows us to inherit properties defined in Programmer.

StevenCodeCraft.com
Page 78
StevenCodeCraft.com

Setting Up Inheritance
To enable FrontEndProgrammer and BackEndProgrammer to inherit the methods
from Programmer, we set their prototypes to be instances of Programmer:

Now, FrontEndProgrammer and BackEndProgrammer instances can use the methods


defined in Programmer:

StevenCodeCraft.com
Page 79
StevenCodeCraft.com

This setup uses prototypical inheritance to share common methods among different
types of programmers, minimizing redundancy and enhancing maintainability. By
structuring objects in this way, you effectively utilize JavaScript's dynamic nature and
prototypical inheritance to create more efficient and organized code. This approach
ensures that all specific programmer types benefit from updates to the Programmer
prototype without needing to modify each constructor separately, adhering to
efficient coding practices and the principles of object-oriented design.

Resetting the constructor


The term "prototype" in JavaScript essentially means the parent object. In our
constructor functions, we define properties and methods. Often, we define a general
constructor function, like Programmer, which contains common properties and
methods. However, we may need to create more specialized objects, such as
FrontEndProgrammer, which have specific capabilities. Instead of duplicating the
properties and methods defined in Programmer, we use prototypical inheritance to
reduce code duplication. By doing this, FrontEndProgrammer inherits the properties
and methods from Programmer, its parent object. When we call a method, such as
code(), JavaScript checks the FrontEndProgrammer object. If it doesn't find the
method, it looks up the inheritance chain to the Programmer prototype and uses the
method defined there. This setup means we write less code, which is easier to
maintain and modify because the code is defined in one place and reused. As
applications grow larger, reducing code duplication through inheritance makes logical
sense.

StevenCodeCraft.com
Page 80
StevenCodeCraft.com

Addressing Constructor Property Issues in


Prototypical Inheritance
When implementing inheritance, a common issue is the constructor property. This
property is important for creating instances dynamically based on the constructor
function associated with a prototype. Let's illustrate this with our Programmer
example, adding specific types like FrontEndProgrammer and
BackEndProgrammer:

Every object has a constructor property, which refers to the function called to
initialize the object. When using new FrontEndProgrammer(), we want it to call the
FrontEndProgrammer constructor. However, after setting the prototype to
Object.create(Programmer.prototype), the constructor property of
FrontEndProgrammer.prototype points to Programmer instead of
FrontEndProgrammer, which can cause confusion.

StevenCodeCraft.com
Page 81
StevenCodeCraft.com

Best Practice Recap


Whenever you change the prototype of a function in JavaScript, reset the
constructor property. This ensures the constructor property accurately
reflects the actual constructor function for instances, preventing potential
issues in object creation and inheritance. This practice maintains consistency
and predictability, especially in complex inheritance hierarchies. Now that
we've covered the basics and best practices, let's move on to the next part of
our lesson.

Calling the Super Constructor


In object-oriented programming in JavaScript, constructors play a crucial role
in setting up new objects. Let's refine your example using a modified
Programmer constructor that includes additional attributes.

StevenCodeCraft.com
Page 82
StevenCodeCraft.com

Modifying the Programmer Constructor


We'll start by modifying the Programmer constructor to include not just the
programmer's name but also their specialization.

Modifying the Programmer Constructor


Now, let's create a more specific type of programmer, such as a FrontEndProgrammer,
who will also have a preferred framework along with the name and specialization.

How the 'new' Operator Works


When you use the new operator, several things happen:

StevenCodeCraft.com
Page 83
StevenCodeCraft.com

1) New Object Creation


JavaScript creates a new object.

2) Setting the Prototype


It sets the prototype of this new object to the prototype of the constructor
function from which it was called (Programmer.prototype for Programmer,
FrontEndProgrammer.prototype for FrontEndProgrammer).

3) Executing the Constructor


The constructor function is called with the 'this' keyword bound to the newly
created object, allowing properties and methods to be assigned to this

4) Returning the Object


Unless the constructor explicitly returns a different object, the new object is
returned by default.

The 'this' keyword


In the context of a constructor function called with the new operator:

By default, the this keyword refers to the new object being created.
If the constructor function is called without the new operator (not
recommended), this would refer to the global object (window in browsers,
global in Node.js), which can lead to unexpected behaviors and errors.

StevenCodeCraft.com
Page 84
StevenCodeCraft.com

Example Execution

Key Takeaway
Using constructor functions properly with the new operator ensures that the
new objects are set up correctly with their intended prototypes and properties. It
is essential to use the new operator, to avoid unintended side effects and ensure
that this behaves as expected within the constructor function. This mechanism is
fundamental for implementing inheritance and creating a hierarchy of objects in
JavaScript.

StevenCodeCraft.com
Page 85
StevenCodeCraft.com

Intermediate Function Inheritance


Suppose we have a base Programmer class and specific subclasses like
FrontEndProgrammer and BackEndProgrammer. Here's how you could set
up inheritance using the generalized extend function.

Base Programmer Constructor

Specific Programmer Types

StevenCodeCraft.com
Page 86
StevenCodeCraft.com

Generalized Extend Function


Let's define an extend function that automates the process of setting up
prototypical inheritance between a parent and a child constructor function.

Using the Extend Function


With the extend function in place, you can easily set up inheritance for any
number of specific programmer types by simply calling extend.

Testing the Implementation


Create instances of FrontEndProgrammer and BackEndProgrammer to verify
that the inheritance has been set up correctly.

StevenCodeCraft.com
Page 87
StevenCodeCraft.com

Benefits of Using the Extend Function


Encapsulation
By encapsulating the inheritance logic in the extend function, we avoid
redundancy and make the code more maintainable.

Reusability
The extend function can be reused across the project wherever inheritance is
needed, promoting consistency and reducing the chance of errors.

This approach not only streamlines the inheritance process but also makes
the codebase more organized and easier to manage. By using a generalized
method for extending prototypes, you ensure that all derived classes
correctly inherit from their parent class without manually setting the
prototype and constructor each time.

Method Overriding
To demonstrate overriding, we'll redefine the code method in
FrontEndProgrammer to include additional behavior specific to front-end
programming.

StevenCodeCraft.com
Page 88
StevenCodeCraft.com

Explanation of Method Overriding


This setup showcases how prototypical inheritance works in JavaScript:

When you call the code method on an instance of FrontEndProgrammer,


JavaScript first checks the FrontEndProgrammer prototype for this method.
Since we've overridden code in FrontEndProgrammer, this version is executed,
which also calls the base class method to maintain the general programming
behavior, then adds specific behaviors.

Testing the Implementation

StevenCodeCraft.com
Page 89
StevenCodeCraft.com

Key Points
Method Lookup
JavaScript's prototype chain means that method lookup starts from the
object itself and moves up the chain until it finds the method or reaches the
top of the chain.

Using 'call' for Base Methods


To include the base class method's functionality when overriding methods,
use call to specify the context (this) and ensure the base method executes
correctly for the derived class instance.

By following these principles, you can effectively manage and extend


behaviors in JavaScript, allowing for more flexible and reusable code
structures.

Polymorphism
Polymorphism, a key concept in object-oriented programming, so
polymorphism stands for "many forms" and it is what allows objects of
different classes to be treated as objects of a common super class through
inheritance. It's essentially the ability for different objects to respond to the
same method call in different ways.

StevenCodeCraft.com
Page 90
StevenCodeCraft.com

Implementing Polymorphism with the


Programmer Function
First, let's define the base Programmer function and a couple of specialized
programmer types.

Base Programmer Constructor

Specialized Programmer Types


Create two specialized types of programmers, FrontEndProgrammer and
BackEndProgrammer, each with a unique implementation of the work method.
(refer to the next page)

StevenCodeCraft.com
Page 91
StevenCodeCraft.com

StevenCodeCraft.com
Page 92
StevenCodeCraft.com

Using Polymorphism
Create an array of Programmer objects, which can be either
FrontEndProgrammer or BackEndProgrammer, and demonstrate how each
object can use the work method differently, in accordance to their specialized
class definitions.

Key Points
No Type Checking Needed
With polymorphism, you don’t need to check the type of each object before
calling the work method. Each object knows its own implementation of work,
allowing them to behave correctly according to their specific type.

StevenCodeCraft.com
Page 93
StevenCodeCraft.com

Code Flexibility and Reusability


Polymorphism simplifies code management and enhances its reusability by
allowing you to use a general interface for a range of different objects.

This example demonstrates how polymorphism can be leveraged in


JavaScript to interact with objects from different classes through a common
interface, enabling flexible and easily manageable code. This approach is
particularly powerful in systems where new types might be added, but
existing code shouldn’t need extensive changes to accommodate them.

When to use inheritance


While inheritance is a powerful feature in object-oriented programming, it's
important to use it carefully to avoid creating overly complex and fragile
systems.

Potential Issues with Inheritance


Inheritance should always satisfy the 'is-a' relationship, meaning that a
subclass should represent a more specific version of the superclass. For
instance, a FrontEndProgrammer is a specific type of Programmer. However,
problems can arise if the subclass inherits methods that do not logically
apply to it, leading to an inappropriate relationship.

StevenCodeCraft.com
Page 94
StevenCodeCraft.com

Example of Inheritance Complexity


Consider a scenario where you are expanding the Programmer function to
include different types of technical staff in a company:

If Manager inherits from Employee, and Employee has programming


methods, those methods would not logically apply to Manager. This example
shows that the inheritance hierarchy can become inappropriate and
bothersome as you don't want to inherit methods that don't make logical
sense.

StevenCodeCraft.com
Page 95
StevenCodeCraft.com

Best Practice: Limiting Inheritance Depth


It's generally best to keep inheritance hierarchies shallow – ideally, not going
beyond one level. Deep inheritance trees can become difficult to manage and
understand.

Favoring Composition Over Inheritance


An alternative to inheritance is composition, where you build classes out of
components rather than using a strict parent-child relationship. This
approach often provides greater flexibility and reduces dependencies
between classes. When you think of the term composition, imagine it as a
"has-a" relationship, whereas inheritance represents an "is-a"
relationship. With composition, an object includes instances of other objects
and their functionality, allowing it to have the behavior it needs without
relying on a hierarchical structure.

Implementing Composition with Mixins


In JavaScript, composition can be achieved using mixins, where functionality
can be mixed into a class. For example, instead of having a Programmer
inherit all behaviors, specific functionalities like canCode, canReview,
canDesign might be mixed in as needed:

StevenCodeCraft.com
Page 96
StevenCodeCraft.com

StevenCodeCraft.com
Page 97
StevenCodeCraft.com

Summary
In software design, while inheritance might seem like a straightforward way to
reuse code, it's essential to evaluate whether it introduces unnecessary
complexity. Often, composition provides a more flexible and robust
alternative, allowing objects to be constructed from discrete, reusable
behaviors. This approach aligns with the principle "favor composition over
inheritance," helping to maintain clean and manageable codebases.

Mixins
In JavaScript, Object.assign is a powerful method used to copy properties
from source objects to a target object. This capability is particularly useful for
implementing mixins, where common functionalities can be shared across
different objects without forming a rigid inheritance structure. Here’s how
you can apply this to a Programmer function, allowing programmers to have
modular capabilities like eating, walking, and coding.

Defining Mixin Objects


First, define simple objects that contain methods which can be mixed into any
target object:

StevenCodeCraft.com
Page 98
StevenCodeCraft.com

Using Object.assign to Mix Capabilities


You can use Object.assign to add these capabilities to a Programmer:

StevenCodeCraft.com
Page 99
StevenCodeCraft.com

Testing the Combined Features


Create a Programmer instance and use the mixed-in capabilities:

Reusable Mixin Function


To streamline the process of mixing capabilities into different objects, you
can create a reusable mixin function:

StevenCodeCraft.com
Page 100
StevenCodeCraft.com

Discussion
This approach demonstrates the flexibility of composition over inheritance.
By composing objects from smaller, function-focused objects, you can easily
extend functionality without the constraints of a strict class hierarchy. This
method not only makes your code more reusable but also keeps it modular
and easier to understand. Using mixins with Object.assign allows for dynamic
capabilities to be added to objects, promoting code reuse and reducing the
complexity typically associated with deep inheritance hierarchies. This
method aligns well with the modern JavaScript best practice of favoring
composition over inheritance, providing greater flexibility and easier
maintenance.

Summary
Course Section Summary: Advanced Object-Oriented Programming

This section delved into several advanced topics essential for mastering
object-oriented programming in JavaScript:

Creating Your Own Prototypical Inheritance


We explored how to establish custom inheritance chains using JavaScript’s
prototype-based inheritance system.

StevenCodeCraft.com
Page 101
StevenCodeCraft.com

Resetting the Constructor


The importance of properly resetting the constructor property when
modifying an object’s prototype was emphasized to maintain proper linkages.

Calling the Super Constructor


Techniques for invoking a superclass constructor within a subclass were
discussed to ensure that inherited properties are correctly initialized.

Intermediate Function Inheritance


We examined how to implement inheritance through intermediary functions,
enhancing flexibility in how inheritance is applied

Method Overriding
The course covered strategies for overriding methods in subclasses to tailor
or enhance functionality derived from a superclass.

Polymorphism
We discussed how polymorphism allows objects of different classes to be
treated as objects of a common superclass, facilitating flexible and dynamic
code.

StevenCodeCraft.com
Page 102
StevenCodeCraft.com

When to Use Inheritance


Guidelines were provided on appropriate scenarios for using inheritance,
focusing on maintaining clear 'is-a' relationships.

Mixins
The use of mixins was introduced as a flexible alternative to inheritance for
composing objects with multiple behaviors or capabilities without forming rigid
hierarchical structures.

By understanding and applying these advanced concepts, you can write more
robust, maintainable, and scalable JavaScript applications. This knowledge will
enable you to leverage the full power of object-oriented programming in
JavaScript, adapting to various programming challenges with effective
solutions.

StevenCodeCraft.com
Page 103
StevenCodeCraft.com

Active Recall Study Questions


32) What are the benefits of defining methods on the prototype of a
constructor function in JavaScript and how does this approach align with the
DRY principle? (DRY stands for don’t repeat yourself)
33) How does using Programmer.call(this, name) within the constructors of
FrontEndProgrammer and BackEndProgrammer ensure proper initialization
of the name property for these specific programmer types?
34) Why do we use Object.create(Programmer.prototype) to set the
prototypes of FrontEndProgrammer and BackEndProgrammer and what is
the significance of resetting their constructor properties?
35) What is the primary benefit of using prototypical inheritance in JavaScript
when creating specialized objects like FrontEndProgrammer from a general
constructor function like Programmer?
36) Why is it necessary to reset the constructor property when setting up
inheritance using Object.create() in JavaScript, and what potential issues can
arise if this step is omitted?
37) How does JavaScript handle method lookups in the context of
prototypical inheritance and what is the sequence of steps when a method
like code() is called on an instance of FrontEndProgrammer?
38) Explain the steps involved when the new operator is used with a
constructor function in JavaScript. Why is it important to use the new
operator when calling a constructor function?

StevenCodeCraft.com
Page 104
StevenCodeCraft.com

39) How do you override the code() method in FrontEndProgrammer to


include additional behavior while still calling the base class method?
40) What is the significance of using call() when invoking the base class
method in an overridden method in JavaScript?
41) How does polymorphism allow different objects to respond to the same
method call in different ways?
42) What are the benefits of using polymorphism in terms of code flexibility
and reusability as demonstrated by the Programmer example?
43) What is composition?
44) What are potential issues with inheritance and why is it important to
ensure that a subclass satisfies the “is-a” relationship with its superclass?
45) How does composition offer a more flexible alternative to inheritance in
software design, and how can it be implemented in JavaScript using mixins?
46) What is a mixin?
47) How does using Object.assign() facilitate the implementation of mixins in
JavaScript, and what are the benefits of this approach compared to
traditional inheritance?

StevenCodeCraft.com
Page 105
StevenCodeCraft.com

Active Recall Answers


32) What are the benefits of defining methods on the prototype of a
constructor function in JavaScript and how does this approach align with the
DRY principle? (DRY stands for don’t repeat yourself)
Defining methods on the prototype of a constructor function in JavaScript
provides two main benefits. The first being memory efficiency as methods
defined on the prototype are shared among all instances of the constructor.
This reduces memory usage since each instance does not create its own copy
of the method. The second benefit is code reuse. By using the prototype, you
avoid duplicating code across multiple instances. This aligns with the DRY
principle and makes code easier to maintain and update. In summary,
prototypical inheritance allows for shared behavior and memory efficiency,
making your code more maintainable and performant.

33) How does using Programmer.call(this, name) within the constructors of


FrontEndProgrammer and BackEndProgrammer ensure proper initialization
of the name property for these specific programmer types?
Using Programmer.call() within the constructors of FrontEndProgrammer
and BackEndProgrammer ensures proper initialization of the name property
by calling the Programmer constructor with the current instance. So by
passing the ‘this’ keyword as the first argument to Programmer.call(this,
name) will correctly set the name property for the current object.

StevenCodeCraft.com
Page 106
StevenCodeCraft.com

34) Why do we use Object.create(Programmer.prototype) to set the


prototypes of FrontEndProgrammer and BackEndProgrammer and what is
the significance of resetting their constructor properties?
We use Object.create() to set the prototypes of FrontEndProgrammer and
BackEndProgrammer so they can inherit methods from the base
Programmer object. This allows instances of these objects to share the same
methods, making the code more efficient, resetting their constructor
properties, and ensures the instances are correctly identified as
FrontEndProgrammer or BackEndProgrammer. This helps maintain proper
type initialization.

35) What is the primary benefit of using prototypical inheritance in JavaScript


when creating specialized objects like FrontEndProgrammer from a general
constructor function like Programmer?
The primary benefit of using prototypical inheritance in JavaScript is that it
reduces code duplication by inheriting properties and methods from a
general constructor function, like Programmer, specialized objects like
FrontEndProgrammer can use those shared methods without having to
rewrite them. This makes the code easier to maintain and update since the
common code is defined in one place and reuse across different objects

StevenCodeCraft.com
Page 107
StevenCodeCraft.com

36) Why is it necessary to reset the constructor property when setting up


inheritance using Object.create() in JavaScript, and what potential issues can
arise if this step is omitted?
It is necessary to reset the constructor property when setting up inheritance
using Object.create() because Object.create() changes the prototype which
can mistakenly point to the wrong constructor. If you don’t reset it, the
constructor property might refer to the parent object like Programmer
instead of the child object, like FrontEndProgrammer. This can cause
confusion and bugs when creating new instances as JavaScript won’t know
the correct function to use for initializing the objects.

37) How does JavaScript handle method lookups in the context of


prototypical inheritance and what is the sequence of steps when a method
like code() is called on an instance of FrontEndProgrammer?
When a method like code() is called on an instance of FrontEndProgrammer,
JavaScript follows these steps: The first is checking the instance, JavaScript
first looks for the code() method directly on the FrontEndProgrammer
instance. Second, JavaScript checks the prototype. If JavaScript doesn’t find
the code() method, then it will look up the prototype chain. The process
ensure that the instance can use methods defined in its own class or any
parent class following the inheritance chain.

StevenCodeCraft.com
Page 108
StevenCodeCraft.com

38) Explain the steps involved when the new operator is used with a
constructor function in JavaScript. Why is it important to use the new
operator when calling a constructor function?
When the new operator is used with a constructor function in JavaScript the
following steps occur: First is the new object creation. So a new JavaScript
object is created in memory. Second is setting the prototype. The new
object’s prototype is set to the constructor function’s prototype. Third is
executing the constructor.The constructor function is called with the ‘this’
keyword bound to the new object, allowing the properties and methods to be
assigned to it. The fourth step is returning the object. The new object is
returned automatically unless the constructor explicitly returns a different
object. It’s important to use the new operator to ensure that the ‘this’
keyword refers to the new object being created. Without the new operator,
then the ‘this’ keyword would refer to the global object or be undefined.

39) How do you override the code() method in FrontEndProgrammer to


include additional behavior while still calling the base class method?
You could do the following: First you would define the new code() method on
FrontEndProgrammer.prototype. Inside this method you call the base code()
method using Programmer.prototype.code.call(this) and passing in the ‘this’
keyword. This will keep the original behavior and then you would add the new
behavior specific to FrontEndProgrammer. This way when code() is called on
the FrontEndProgrammer instance it will first run the base code() method
and then add the new behavior.

StevenCodeCraft.com
Page 109
StevenCodeCraft.com

40) What is the significance of using call() when invoking the base class
method in an overridden method in JavaScript?
Using call() when invoking the base class method in an overridden method is
important because it ensures the base method runs in the correct context.
By using call(), you pass the current object, which would be the ‘this’ keyword
to the base method so it behaves as if it was called directly on that object.
This allows the base method to properly access and modify the object’s
properties.

41) How does polymorphism allow different objects to respond to the same
method call in different ways?
Polymorphism allows different objects to respond to the same method call in
different ways by using method overloading. Each object, even if they are of
different classes, can have its own version of a method. So when a method is
called the correct version for that specific object is executed.

42) What are the benefits of using polymorphism in terms of code flexibility
and reusability as demonstrated by the Programmer example?
Polymorphism increases code flexibility and reusability by allowing objects of
different types to be treated through a common interface. In the
Programmer example, it means that you can call the same method, in this
case the work() method, on any Programmer object instance without
worrying about its specific type. Making the code simpler and easier to
extend with new types of Programmers. The FrontEndProgrammer and
BackEndProgrammer inherit from the base Programmer and they have their
own unique implementation of the work() method.

StevenCodeCraft.com
Page 110
StevenCodeCraft.com

43) What is composition?


Composition is a design principle where an object is made up of one or more
other objects allowing it to use the functionality it represents. It represents a
“has-a” relationship, meaning that an object contains and uses other objects
rather than inheriting from a parent class.

44) What are potential issues with inheritance and why is it important to
ensure that a subclass satisfies the “is-a” relationship with its superclass?
Potential issues with inheritance include creating complex and fragile
systems if subclasses inherit methods that don’t logically apply to them. It’s
important to ensure that a subclass satisfies the “is-a” relationship with its
superclass to maintain logical consistency. For example, a
FrontEndProgrammer should be a specific type of Programmer.

45) How does composition offer a more flexible alternative to inheritance in


software design, and how can it be implemented in JavaScript using mixins?
Composition offers more flexibility than inheritance by allowing objects to
include and use functionality from other objects, avoiding complex
hierarchies. In JavaScript, composition can be implemented using mixins
where specific functionalities are added to an object as needed. This way you
can add functionality without relying on inheritance.

StevenCodeCraft.com
Page 111
StevenCodeCraft.com

46) What is a mixin?


A mixin is a reusable piece of code that adds specific functionality to objects
or classes. In JavaScript, mixins are used to share behavior across multiple
objects by copying properties and methods into them.

47) How does using Object.assign() facilitate the implementation of mixins in


JavaScript, and what are the benefits of this approach compared to
traditional inheritance?
Using Object.assign() in JavaScript makes it easy to implement mixins by
complying properties from multiple source objects to a target object, adding
new capabilities without using inheritance. This approach offers greater
flexibility, avoids complex inheritance hierarchies, and promotes code reuse
by allowing you to combine only the needed functionality.

StevenCodeCraft.com
Page 112
05

ES6 Classes

StevenCodeCraft.com
Page 113
StevenCodeCraft.com

ES6 Classes
In modern JavaScript, the class syntax provides a cleaner and more intuitive
way to define constructor functions and manage prototypical inheritance.
This new syntax simplifies the creation of objects and their prototypes, even
though it is essentially syntactic sugar over the existing prototypical
inheritance model. This syntax, introduced in ES6 (ECMAScript 2015), is part
of a specification that enhances JavaScript by adding new language features
to modernize it.

Rewriting with Class Syntax


Consider the traditional constructor function for a Programmer:

StevenCodeCraft.com
Page 114
StevenCodeCraft.com

Let's rewrite this using modern class syntax to make it clearer and more
structured This is the syntax that you would use in modern JavaScript
applications.

Understanding the Underlying Functionality


Despite the use of class, the underlying mechanism remains the same:

typeof Programmer
Even with the class syntax, Programmer is still a function in terms of
JavaScript's type system. It's specifically a constructor function.

StevenCodeCraft.com
Page 115
StevenCodeCraft.com

ES5 Compatibility
For environments that do not support ES6 classes, tools like Babel (available
at babeljs.io) can transpile this modern JavaScript back to ES5-compatible
code, ensuring that the classes work across all browsers.

The Benefits of Using Class Syntax


Clarity and Simplicity
The class syntax is easier to read and understand, especially for developers
coming from other object-oriented languages.

Encapsulation
It encourages better encapsulation and a more declarative structure for
defining object constructors and methods.

Standardization
Offers a standardized way of object creation and inheritance that aligns with
other programming languages, easing the learning curve.

StevenCodeCraft.com
Page 116
StevenCodeCraft.com

Summary
By utilizing modern JavaScript's class syntax, you can write cleaner, more
maintainable code while still leveraging the powerful prototypical inheritance
model that JavaScript offers. This approach not only enhances readability but
also simplifies complex coding structures, making it an essential skill for
developers working in modern JavaScript environments.

Hoisting
Function Declaration vs. Function Expression in
JavaScript
In JavaScript, functions can be defined using either function declarations or
function expressions. Both methods serve similar purposes but have key
differences in behavior, particularly regarding how they are hoisted.

Function Declarations
Function declarations are hoisted to the top of their containing scope. This
means that they are moved to the top during the compile phase, allowing you
to call them before they are physically defined in the code:

StevenCodeCraft.com
Page 117
StevenCodeCraft.com

Function Expressions
In contrast, function expressions are not hoisted, which means you cannot
call them before they are defined in the code. Trying to do so will result in a
ReferenceError:

StevenCodeCraft.com
Page 118
StevenCodeCraft.com

Applying This to the Programmer Class Context


When working with classes, it's crucial to understand that like function
expressions, class declarations are not hoisted. This means you must declare
a class before you can instantiate it:

Using Class Expressions


Just as with function expressions, classes can also be defined using a class
expression syntax, which is not hoisted and allows for more dynamic
declarations:

StevenCodeCraft.com
Page 119
StevenCodeCraft.com

Conclusion
Choosing between function declarations and expressions (or class
declarations and expressions) can impact how you structure your JavaScript
code. For simplicity and clarity, especially when defining constructors and
methods within a class, using class declaration syntax is often preferred.
However, being aware of hoisting rules is crucial for avoiding runtime errors
and ensuring that your JavaScript code executes as intended.

StevenCodeCraft.com
Page 120
StevenCodeCraft.com

Static Methods
In object-oriented programming, especially within JavaScript classes, we
distinguish between instance methods, which act on instances of the class,
and static methods, which are called on the class itself and are often used as
utility functions.

Defining the Programmer Class


Here's how you can define the Programmer class with both instance and
static methods:

StevenCodeCraft.com
Page 121
StevenCodeCraft.com

Using Instance and Static Methods


Instance methods like code are used to perform actions that are relevant to
the individual objects created from the class. Static methods like
compareSkill, on the other hand, are used for operations that are relevant to
the class as a whole or involve multiple instances of the class but don't
depend on any single object's state. Here's how you can use these methods:

Utility of Static Methods


Static methods are particularly useful for utility functions where the
operation does not depend on the state of a particular instance but rather
on the parameters provided to the function. This makes them similar to
functions provided by the Math object in JavaScript, such as Math.round() or
Math.max(), which perform general utility operations and are not tied to a
specific object.

StevenCodeCraft.com
Page 122
StevenCodeCraft.com

Conclusion
Understanding when to use instance methods versus static methods in your
JavaScript classes can significantly impact the design and functionality of your
applications. Instance methods are best for manipulating or utilizing the
state of individual objects, while static methods are ideal for tasks that
require a broader scope that involves the class itself or interactions between
multiple instances. This distinction helps in organizing code logically and
maximizing the efficiency and readability of your JavaScript applications.

The "this" keyword


Let's go over Understanding the this Keyword in Different Contexts with the
Programmer Class. The this keyword in JavaScript behaves differently based
on how a function is called. This behavior can lead to unexpected results,
especially when functions are detached from their object context. Let's
illustrate this with a Programmer class where we have a method that logs the
this context:

StevenCodeCraft.com
Page 123
StevenCodeCraft.com

Detaching a Method
When you detach a method from its object and call it independently, this
loses its original context:

StevenCodeCraft.com
Page 124
StevenCodeCraft.com

Strict Mode and this Behavior


In JavaScript, strict mode changes the behavior of this in function calls not
associated with an object. In strict mode, this becomes undefined instead of
defaulting to the global object (Window in browsers, global in Node.js), which
is a safer default that prevents accidental modifications to the global object.

Add the following line of code as the first line of your JavaScript file to make it
use strict mode.

'use strict';

Benefits of Strict Mode in Class Bodies


Classes in ES6 are implicitly in strict mode to avoid these issues:

Safety
It prevents functions from unintentionally modifying global variables, which
can lead to difficult-to-track bugs.

Consistency
Ensures that this behaves consistently, making the code more predictable
and less prone to errors.

StevenCodeCraft.com
Page 125
StevenCodeCraft.com

Conclusion
Understanding how this behaves in different contexts is crucial for JavaScript
developers. By using strict mode and being cautious of how methods are
called, developers can write more robust and secure code. The implicit use of
strict mode within class bodies in modern JavaScript helps enforce these best
practices, preventing common mistakes associated with this in global
contexts.

Private Members using Symbols


Abstraction in programming involves hiding complex implementation details
and exposing only the necessary parts of an object. This is commonly
achieved through private properties and methods. Let's apply the concept of
abstraction to a Programmer class, where certain details, like the
programming language proficiency level, might be internal to the
programmer's operations.

Approach 1: Naming Convention


(Not Recommended)
One approach is to use an underscore prefix to indicate a private property:

StevenCodeCraft.com
Page 126
StevenCodeCraft.com

Issues: This method relies on a naming convention and does not provide true
privacy. Properties are still accessible from outside the class.

Approach 2: ES6 Symbols


A more secure way to implement private properties is using ES6 Symbols,
which provides a way to create unique identifiers:

Observations
Symbols provide a pseudo-private mechanism as they are not
accessible through normal property access methods.
However, Symbols can still be accessed through
Object.getOwnPropertySymbols, so it’s not completely private.

StevenCodeCraft.com
Page 127
StevenCodeCraft.com

Private Methods Using Symbols


Similarly, you can use Symbols to create methods that are not accessible
directly via the class instance:

Conclusion
While Symbols enhance privacy, they do not provide a foolproof solution for
private properties or methods. For truly private class fields, ES2022
introduces private class fields and methods using the # prefix, which might
be the best approach going forward for ensuring data encapsulation and
adhering to the principles of abstraction in object-oriented programming.

StevenCodeCraft.com
Page 128
StevenCodeCraft.com

ES2022 private class field syntax

This method ensures that the language property and code method are
completely private, encapsulated within the Programmer class, and not
accessible from outside. This approach aligns closely with the principle of
abstraction, keeping implementation details hidden and interfaces clean and
straightforward.

Using WeakMap for Private Properties and


Methods in the Programmer Class
WeakMap provides a way to securely store private data for an object without
preventing garbage collection when the object itself is no longer in use.
Here’s how you can use WeakMap to implement private properties and
methods within the Programmer class.

StevenCodeCraft.com
Page 129
StevenCodeCraft.com

Define the Programmer Class Using WeakMap

Create an Instance and Use Methods

StevenCodeCraft.com
Page 130
StevenCodeCraft.com

Discussion of WeakMap Benefits and Usage


Garbage Collection
A major benefit of using WeakMap is its handling of keys. If there are no other
references to a key object (in this case, an instance of Programmer), it can be
garbage collected. This helps prevent memory leaks in applications where
objects are dynamically created and destroyed.

Encapsulation
Using WeakMap for storing private data ensures that these details are not
accessible from outside the class. This is much more secure than using
properties prefixed with an underscore, as these are only conventionally
private.

Arrow Functions and this


Arrow functions do not have their own this context; instead, they inherit this
from the enclosing execution context. Within _work, this refers to the
Programmer instance, allowing access to the instance's name and other
properties securely stored in the WeakMap.

StevenCodeCraft.com
Page 131
StevenCodeCraft.com

Alternative with a Single WeakMap


You can also group all private properties into a single WeakMap entry per
object, though this might make the code slightly more verbose:

Conclusion
Using WeakMap for managing private data in JavaScript classes provides a
robust way to ensure encapsulation and data security. This approach is
particularly useful in scenarios where data privacy is crucial and helps
maintain clean API boundaries within your classes.

StevenCodeCraft.com
Page 132
StevenCodeCraft.com

Getters and Setters


Using WeakMap for Private Properties in the
Programmer Class
We'll start by demonstrating how to store a private property in a
Programmer class using a WeakMap and then show how to define property
accessors.

Define the Programmer Class Using WeakMap

StevenCodeCraft.com
Page 133
StevenCodeCraft.com

Using Object.defineProperty to Define a Getter


For cases where you want the property to be accessible more like a typical
property rather than a method, you can use Object.defineProperty:

Cleaner ES6 Syntax for Getters and Setters


In ES6, getters and setters can be defined within the class body, providing a
more concise and readable way to access properties:

StevenCodeCraft.com
Page 134
StevenCodeCraft.com

Explanation of Code Snippets


WeakMap
Provides a way to securely store private data associated with an instance.

Object.defineProperty
This method is useful when you need to create getters or setters that appear
like properties but are backed by methods. It allows for fine-grained control
over property characteristics.

StevenCodeCraft.com
Page 135
StevenCodeCraft.com

ES6 Getters/Setters
These provide syntactic sugar that makes accessing and modifying properties
intuitive and similar to accessing traditional class properties. This method is
particularly useful for validation, logging, or handling side effects during
property access.

Conclusion
These different approaches allow JavaScript developers to encapsulate and
protect data within classes effectively, ensuring that implementation details
are hidden and that public interfaces are clean and easy to use. Using these
features appropriately can help maintain and scale large codebases, making
your code more robust and easier to manage.

Inheritance
We'll start with a base Programmer class that has a constructor for
initializing a programmer with a name and a basic method for coding:

StevenCodeCraft.com
Page 136
StevenCodeCraft.com

Derived Class: FrontEndProgrammer


Next, we extend the Programmer class to create a FrontEndProgrammer
class that includes additional properties and methods relevant to front-end
programming: So the derived class will inherit from the base class when we
use the extend keyword. The FrontEndProgrammer is a Programmer. Use the
extend keyword as this is how you would achieve inheritance using the ES6
class syntax. We create an instance of the FrontEndProgrammer class and
demonstrate the use of both inherited and specific methods:

StevenCodeCraft.com
Page 137
StevenCodeCraft.com

Discussion
Inheritance
The FrontEndProgrammer class inherits from the Programmer class, gaining
access to its methods while also being able to introduce new properties and
methods or override existing ones.

Super
The super keyword is essential for calling the constructor of the base class
(Programmer) from the derived class (FrontEndProgrammer). It ensures that
all the initialization logic in the superclass is properly executed before
additional setup in the subclass.

Method Overriding
The code method is overridden in FrontEndProgrammer to extend its
functionality, but it also calls the superclass's code method to maintain the
general coding behavior.

StevenCodeCraft.com
Page 138
StevenCodeCraft.com

Conclusion
This example clearly illustrates how to effectively use inheritance in JavaScript
to create a hierarchy of classes, where derived classes extend and specialize
the behavior of their base classes. This pattern is crucial for structuring
complex applications in a way that promotes code reuse and logical
organization.

Method Overriding
Let's go over method overriding with ES6 classes we will start with a base
Programmer class that has a generic work method. Then, we'll create a
specialized FrontEndProgrammer class that extends Programmer and
overrides the work method to add specific behavior.

Base Class: Programmer

StevenCodeCraft.com
Page 139
StevenCodeCraft.com

Derived Class: FrontEndProgrammer


The FrontEndProgrammer class extends Programmer and overrides the work
method to include specific front-end programming tasks. It uses super.work()
to include the behavior of the base class's work method.

Using the Classes


Create an instance of FrontEndProgrammer and call its work method to see
how it utilizes both the derived and base class methods:

StevenCodeCraft.com
Page 140
StevenCodeCraft.com

Understanding the Method Lookup and super


Method Lookup
When a method is called, the JavaScript engine first looks for it on the object
itself. If it can't find the method, it walks up the prototype chain to find the
method in the parent class.

Using 'super'
The super keyword is crucial in subclass methods to call corresponding
methods defined in the superclass. This feature allows subclasses to enhance
or modify the behavior of methods they inherit without completely rewriting
them, thus promoting code reuse and reducing duplication.

Conclusion
By overriding methods and using the super keyword effectively, you can
ensure that subclasses not only tailor inherited methods to their specific
needs but also leverage and extend the functionality provided by their
superclasses. This approach is beneficial for maintaining logical and
maintainable code in object-oriented JavaScript applications, as it minimizes
redundancy and ensures a clear and efficient inheritance structure.

StevenCodeCraft.com
Page 141
StevenCodeCraft.com

Summary
ES6 Classes
We explored how ES6 class syntax provides a clearer, more concise way to
define constructor functions and manage inheritance. Classes make the
structure of object-oriented JavaScript cleaner and more similar to other
programming languages, which helps in organizing code and improving
readability.

Hoisting
The concept of hoisting was discussed to understand how JavaScript
interprets function declarations and variable declarations differently. We
noted that function declarations are hoisted to the top of their containing
scope, allowing them to be used before they appear in the code.

Static Methods
We learned about static methods, which are called on the class rather than
on instances of the class. These methods are useful for utility functions that
operate independently of class instances.

StevenCodeCraft.com
Page 142
StevenCodeCraft.com

The 'This' Keyword


The behavior of the this keyword in different contexts was examined,
especially how it can vary depending on the scope and the manner in which
functions are called.

Private Members Using Symbols


The use of ES6 Symbols as a way to somewhat hide implementation details
was covered. Symbols provide a way to create properties that are not easily
accessible from outside the class.

Private Members Using WeakMaps


We discussed using WeakMaps to securely store private data for objects,
providing true encapsulation by ensuring that these details are inaccessible
from outside the class and are eligible for garbage collection when no longer
needed.

Getters and Setters


The implementation and benefits of getters and setters were explored. These
are used to control access to the properties of an object, allowing for
validation, logging, and other side effects when properties are accessed or
modified.

StevenCodeCraft.com
Page 143
StevenCodeCraft.com

Inheritance
We delved into how classes can inherit from other classes, allowing for
shared behavior across different types of objects and reducing code
redundancy.

Method Overriding
The ability to override inherited methods was demonstrated, showing how
subclasses can modify or extend the behavior of methods defined by their
superclasses.

This section aimed to deepen your understanding of modern JavaScript


features and best practices, enhancing your ability to write clean,
maintainable, and efficient JavaScript code in a professional development
environment.

StevenCodeCraft.com
Page 144
StevenCodeCraft.com

Active Recall Study Questions


48) How does the class syntax in modern ES6 JavaScript improve the process
of defining constructor functions and managing prototypical inheritance?
49) What are the benefits of using class syntax over traditional constructor
functions in JavaScript?
50) What is the main difference in behavior between function declarations
and function expressions regarding hoisting in JavaScript?
51) Why is it important to understand hoisting rules when working with class
declarations in JavaScript?
52) What is the difference between instance methods and static methods in
JavaScript classes?
53) What are the potential consequences of detaching a method from its
object context in JavaScript?
54) How does strict mode in JavaScript enhance code safety and consistency,
particularly in the context of class bodies?
55) What are the benefits and limitations of using Symbols for creating
private properties in JavaScript classes?
56) How do private class fields introduced in ES2022 improve data
encapsulation compared to previous approaches?
57) How does using WeakMap for private properties and methods enhance
data encapsulation and security in JavaScript classes?

StevenCodeCraft.com
Page 145
StevenCodeCraft.com

58) What are the benefits of using WeakMap in terms of garbage collection
and how does this prevent memory leaks in JavaScript applications?
59) What distinguishes WeakMap from the ES2022 hash prefix syntax (#) for
private properties and methods, and why might you choose to use
WeakMap?
60) How do ES6 getters and setters enhance the encapsulation and control of
property access within classes?
61) What are the benefits of using the get and set keywords in ES6 classes
compared to traditional methods for accessing and modifying properties?
62) How is the super keyword used in the FrontEndProgrammer class?
63) What are the benefits of using inheritance when extending the
Programmer class to create the FrontEndProgrammer class?
64) What is method overriding in ES6 JavaScript classes?
65) How does method lookup work when calling a method on an object in
JavaScript?
66) What is the role of the super keyword in subclass methods in JavaScript?

StevenCodeCraft.com
Page 146
StevenCodeCraft.com

Active Recall Answers


48) How does the class syntax in modern ES6 JavaScript improve the process
of defining constructor functions and managing prototypical inheritance?
The class syntax in modern JavaScript makes it easier and clearer to create
objects and their prototypes (prototype refers to the parent object which
contains properties and methods to inherit). It provides a straightforward
way to define constructor functions and methods, making the code more
readable and organized compared to the older syntax. Even though it looks
different, it still works the same under the hood as it uses prototypical
inheritance.

49) What are the benefits of using class syntax over traditional constructor
functions in JavaScript?
There are three main benefits of using class syntax over traditional
constructor functions in JavaScript. The first being clarity as class syntax is
easier to read and understand. The second benefit is encapsulation as it
helps organize code better by keeping related methods and properties
together. The third benefit is standardization. Class syntax aligns with how
other programming languages handle classes, making it easier to learn and
use for programmers who are familiar with Python, JavaScript, or C#.

StevenCodeCraft.com
Page 147
StevenCodeCraft.com

50) What is the main difference in behavior between function declarations


and function expressions regarding hoisting in JavaScript?
The main difference is that function declarations are hoisted to the top of
their scope. So you can call them before they are defined in your program
Function expressions are not hoisted, so you must define them before calling
them, otherwise you will get a reference error.

51) Why is it important to understand hoisting rules when working with class
declarations in JavaScript?
It’s important to understand hoisting rules because class declarations are
not hoisted. This means you must define a class before you use it or you’ll get
an error. Knowing this helps you avoid mistakes and ensures your code runs
correctly.

52) What is the difference between instance methods and static methods in
JavaScript classes?
Static methods are useful for tasks that don’t depend on the data of a specific
object, but rather they depend on the parameters provided to the static
method. Static methods should be used for general utility functions for
operations involving multiple instances of the class. Instance methods are
specific to a particular object instance and utilize the object’s internal/private
properties.

StevenCodeCraft.com
Page 148
StevenCodeCraft.com

53) What are the potential consequences of detaching a method from its
object context in JavaScript?
Detaching a method from its object context in JavaScript can cause the ‘this’
keyword to lose its original reference. So instead of pointing to the object it
belongs to, the ‘this’ keyword may point to the global object or undefined if
you are using strict mode. (In browsers the global object would be the
Window object if strict mode is not being used.) This can lead to unexpected
behavior and bugs because the method no longer has access to the object’s
properties or methods.

54) How does strict mode in JavaScript enhance code safety and consistency,
particularly in the context of class bodies?
Strict mode in JavaScript makes code safer and more consistent. By changing
how certain things work in class bodies, it ensures that the ‘this’ keyword is
not accidentally pointing to the global object. Instead if the ‘this’ keyword is
not set, it will be undefined, which helps prevent bugs. Strict mode also stops
you from making common mistakes such as creating global variables by
accident, making the code easier to understand and less prone to errors.

StevenCodeCraft.com
Page 149
StevenCodeCraft.com

55) What are the benefits and limitations of using Symbols for creating
private properties in JavaScript classes?
Using Symbols for private properties and JavaScript classes has benefits and
limitations. The benefits are improved privacy as Symbols make properties
less accessible compared to normal methods defined without a Symbol.
Symbols add a layer of privacy. Another benefit is unique identifiers. Symbols
create unique property keys, reducing the risk of naming collisions. Now the
limitation is that it’s not fully private. Properties created with symbols can still
be accessed using methods like Object.getOwnPropertySymbols(). Another
limitation is complexity as using Symbols can make the code harder to read
and understand as these properties are not straightforward.

56) How do private class fields introduced in ES2022 improve data


encapsulation compared to previous approaches?
Private class fields introduced in ES2022 improve data encapsulation by
providing true privacy for properties and methods using the hash prefix.
These private fields are completely hidden from outside access,. ensuring
that they can only be used within the class. This prevents accidental or
unauthorized access, making the code more secure and easier to maintain
compared to previous approaches like naming conventions or Symbols which
don’t fully hide the data.

StevenCodeCraft.com
Page 150
StevenCodeCraft.com

57) How does using WeakMap for private properties and methods enhance
data encapsulation and security in JavaScript classes?
Using WeakMap for private properties and methods enhances data
encapsulation and security by ensuring that private data is not directly
accessible from outside the class. This keeps the implementation details
hidden, providing a secure way to manage private data within objects.

58) What are the benefits of using WeakMap in terms of garbage collection
and how does this prevent memory leaks in JavaScript applications?
In JavaScript applications, WeakMap allows objects to be garbage collected
when there are no other references to them, even if they have private data
stored in the WeakMap. This helps prevent memory leaks by ensuring that
memory used by objects no longer in use, is freed up automatically.

59) What distinguishes WeakMap from the ES2022 # prefix syntax for private
properties and methods, and why might you choose to use WeakMap?
WeakMap allows you to store private data for objects without preventing
their garbage collection when they’re no longer in use, making it useful for
managing memory efficiently. The ES2022 hash syntax provides built-in
support for private properties and methods, making them truly private and
not accessible from outside the class. You might use WeakMap if you need
private data that can be automatically cleaned up by the garbage collection
to prevent memory leaks.

StevenCodeCraft.com
Page 151
StevenCodeCraft.com

60) How do ES6 getters and setters enhance the encapsulation and control of
property access within classes?
ES6 getters and setters enhance encapsulation and control by allowing you
to define methods that get or set property values. This provides a way to add
logic when accessing or modifying properties such as validation or
transformation while keeping the syntax similar to regular property access.

61) What are the benefits of using the get and set keywords in ES6 classes
compared to traditional methods for accessing and modifying properties?
Using the get and set keywords in ES6 classes allows for more intuitive and
cleaner syntax for accessing and modifying properties. Similar to regular
property access, they also enable adding custom logic such as validation
when getting or setting a property, enhancing encapsulation and control.

62) How is the super keyword used in the FrontEndProgrammer class?


The super keyword in the FrontEndProgrammer class class the constructor
of the Programmer base class, ensuring that the name property is initialized
correctly before adding more properties or methods specific to the
FrontEndProgrammer.

StevenCodeCraft.com
Page 152
StevenCodeCraft.com

63) What are the benefits of using inheritance when extending the
Programmer class to create the FrontEndProgrammer class?
Using inheritance to extend the Programmer class allows the
FrontEndProgrammer class to reuse existing code, add new properties and
methods, and override existing ones, making the code more organized and
easier to manage.

64) What is method overriding in ES6 JavaScript classes?


Method overriding in ES6 JavaScript classes is when a subclass provides a
specific implementation for a method that is already defined in its parent
class, replacing the parent’s method with its own version.

65) How does method lookup work when calling a method on an object in
JavaScript?
When calling a method on an object in JavaScript, the JavaScript engine first
looks for the method on the object itself. If it’s not found, it checks the
object’s prototype and continues up the prototype chain until it finds the
method or reaches the end of the chain.

66) What is the role of the super keyword in subclass methods in JavaScript?
The super keyword in subclass methods in JavaScript is used to call methods
from the parent class, allowing the subclass to build on or modify the
behavior of those methods.

StevenCodeCraft.com
Page 153
06

ES6 Tooling

StevenCodeCraft.com
Page 154
StevenCodeCraft.com

Introduction to Modularity in
JavaScript
As applications grow in size and complexity, managing all the code in a single
file becomes impractical and hard to maintain. To address this issue, it's
beneficial to split the code into multiple smaller files, each encapsulating a
specific functionality or feature. These smaller files are called modules.

The Benefits of Modularity


Maintainability
Smaller, modular files are easier to understand, debug, and test.

Reuse
Modules can be reused across different parts of an application or even in
different projects, reducing code duplication.

Abstraction
Modules allow developers to hide implementation details while exposing a
clear interface, making it easier to interact with the code.

StevenCodeCraft.com
Page 155
StevenCodeCraft.com

Practical Implementation
Imagine you have a Programmer class and its related functionalities divided
across multiple modules for better organization. You would place the
Programmer base class in its own file and the FrontEndProgrammer class in
a separate file. As these classes grow and gain more methods and enhanced
functionality, managing them becomes easier since each class is in its own
file, or in other words, its own module.

Module Systems in JavaScript


Historically, JavaScript did not support modules natively, and various
solutions were developed:

AMD (Asynchronous Module Definition)


Primarily used in browsers, this format allows modules to be loaded
asynchronously.

CommonJS
Used in Node.js, this format is designed for server-side development.

UMD (Universal Module Definition)


Combines the approaches of AMD and CommonJS, making modules usable in
both the browser and Node.js environments.

StevenCodeCraft.com
Page 156
StevenCodeCraft.com

ES6 Modules
With the introduction of ES6, JavaScript now natively supports modules in the
browser. This system uses import and export statements to handle
dependencies.

Course Focus: CommonJS and ES6 Modules


This course will cover CommonJS for server-side applications and ES6
Modules for browser-based JavaScript, providing you with the skills to work
flexibly with JavaScript in multiple environments.

Conclusion
Understanding and using modules in JavaScript not only improves the
structure and maintainability of your code but also enhances its scalability
and reusability. By adopting modern JavaScript module standards like ES6
modules and CommonJS, you ensure that your applications are robust,
maintainable, and ready for growth.

StevenCodeCraft.com
Page 157
StevenCodeCraft.com

Implementing Modularity with the Programmer


Class
Modularity is a fundamental concept in software architecture that helps
organize code in a way that enhances maintainability, reusability, and
scalability. Let’s apply these principles using the Programmer class in a
Node.js environment.

Principle of Cohesion
Cohesion refers to how closely related and focused the responsibilities of a
single module are. High cohesion within modules often leads to better code
maintainability and understanding.

Modularizing the Programmer Class


In the context of Node.js, you can create a module for the Programmer class.
Each module in Node.js has its own scope, and anything defined in the
module is private unless it is explicitly exported.

StevenCodeCraft.com
Page 158
StevenCodeCraft.com

Using the Programmer Module


In another file, such as index.js, you can import and use the Programmer
class. The importation utilizes the require function provided by CommonJS in
Node.js.

StevenCodeCraft.com
Page 159
StevenCodeCraft.com

Encapsulation and Privacy


By structuring your application into modules and only exporting what is
necessary, you enhance encapsulation. For example, if there were private
methods or properties within the Programmer class (like a private _skills
WeakMap), these would not be accessible to consumers of the Programmer
module, who only receive what is explicitly exported.

Running the Code


To execute the code in a Node.js environment:

$ node index.js

This command runs the index.js file, where the Programmer class is
used. The encapsulation ensures that only the intended public interface of
the Programmer class is exposed and used.

Conclusion
This lesson highlights the importance of modularity in software development,
particularly in Node.js using CommonJS modules. By organizing code into
cohesive modules and properly managing exports, developers can create
maintainable, reusable, and scalable applications. The modular approach not
only supports good software design principles but also ensures that each
part of the application can evolve independently.

StevenCodeCraft.com
Page 160
StevenCodeCraft.com

ES6 Modules
Consider the Scenario: We want to ensure that certain properties of our
Programmer class, such as skills, are kept private and managed through a
WeakMap. We'll separate the concerns into two modules: one for the
WeakMap and the other for the Programmer class itself.

StevenCodeCraft.com
Page 161
StevenCodeCraft.com

Using the Programmer Class in an Application


In your main application file, such as index.js, you can import and use the
Programmer class. This example will be for a browser environment that
supports ES6 modules.

index.js

HTML Setup
To ensure that your browser treats the index.js file as a module, update your
HTML script tag accordingly:

<!-- index.html -->


<script type="module" src="index.js"></script>

StevenCodeCraft.com
Page 162
StevenCodeCraft.com

Key Points
Modularity and Privacy
By separating the Programmer class and its skill management into different
modules, we keep implementation details hidden and only expose what is
necessary through exports.

ES6 Modules
Using import and export statements, we can clearly define dependencies and
how modules interact. This approach is cleaner and more maintainable than
older module systems.

Webpack and Browser Modules


While modern browsers understand ES6 modules natively, complex
applications might benefit from tools like Webpack for bundling modules,
managing dependencies, and optimizing load times.

Conclusion
This setup illustrates how to effectively use JavaScript modules to
encapsulate logic, manage privacy, and expose a clean public API. This
modular approach not only aids in code organization but also enhances
security and scalability in web development projects.

StevenCodeCraft.com
Page 163
StevenCodeCraft.com

ES6 Tooling
Modern JavaScript Development Tools

In the landscape of modern JavaScript development, especially when building


browser applications, the roles of transpilers and bundlers are crucial. These
tools help manage and adapt code for various environments, ensuring
compatibility and efficiency.

Transpilers
A transpiler is a type of tool that transforms source code written in one
programming language or version (like modern JavaScript ES6+) into another
version that may be more compatible with current environments (like older
JavaScript ES5 that all browsers can understand).

Babel
One of the most widely used transpilers, Babel allows developers to write
modern JavaScript while ensuring that the code runs smoothly in
environments that only support older standards. For instance, features like
classes, let/const, arrow functions, and template literals introduced in ES6
are not universally supported in older browsers.

StevenCodeCraft.com
Page 164
StevenCodeCraft.com

Suppose you have a Programmer class defined with modern JavaScript


features. Using Babel, this can be transpiled to be compatible with
environments that do not support the latest JavaScript syntax.

Bundlers
A bundler aggregates all the modules in your project (and sometimes their
dependencies) into a single file or a few files. This process is crucial for
optimizing load times and managing resources efficiently.

Webpack
The most popular bundler in the modern web development toolkit, Webpack
not only bundles files but also minifies code and provides optimizations like
tree shaking (removing unused code).

Example with Webpack


You can configure Webpack to bundle your JavaScript modules, including the
Programmer class module, into a single file. This bundled file is then included
in your web application, ensuring that all script dependencies are resolved
and managed efficiently.

StevenCodeCraft.com
Page 165
StevenCodeCraft.com

Why Use These Tools?


Compatibility
Ensures that modern code works in environments that are not up-to-date
with the latest ECMAScript standards.

Performance
Reduces the size of the code and the number of server requests required to
load your application.

Development Efficiency
Simplifies development by allowing you to use modern JavaScript features
without worrying about browser compatibility.

Application to Node.js
While these tools are essential for browser applications due to the varied
support for JavaScript standards across browsers, Node.js environments
generally do not require such tools for running JavaScript code, as Node.js is
typically up-to-date with the latest JavaScript features. However, bundlers like
Webpack might still be used in Node.js applications for optimizing the
application deployment.

StevenCodeCraft.com
Page 166
StevenCodeCraft.com

Conclusion
Understanding and utilizing transpilers like Babel and bundlers like Webpack
is essential for any modern web developer looking to create efficient,
maintainable, and compatible web applications. They are especially
important when the application needs to be scaled or maintained across
different browser environments.

Babel
Installing Essential Tools with npm

Before you start, ensure that Node.js is installed on your machine, as it


comes with npm (Node Package Manager), which is crucial for managing
third-party libraries and development tools.

1) Check Node Installation


Ensure that Node.js is properly installed by checking its version in the
terminal:

$ node -v

StevenCodeCraft.com
Page 167
StevenCodeCraft.com

2) Create a New Project Directory:


Make a new directory for your project and navigate into it:

$ mkdir es6-tooling
$ cd es6-tooling

JSON
JSON (JavaScript Object Notation) is a lightweight data interchange format
that is easy for humans to read and write, and easy for machines to parse
and generate. It is often used to transmit data between a server and web
application, serving as an alternative to XML. (extensible markup language)
JSON data is structured as key-value pairs within curly braces and supports
nested objects and arrays.

3) Initialize a New npm Project:


Run the following command to create a package.json file, which will manage
all your project's dependencies and other configurations:

$ npm init --yes

StevenCodeCraft.com
Page 168
StevenCodeCraft.com

Setting Up Babel
To transpile modern JavaScript (ES6+) to backward-compatible JavaScript
(ES5), use Babel

1) Install Babel Packages


https://babel.dev/docs/usage

Install the necessary Babel packages using npm. These include babel-cli,
babel-core, and a preset package babel-preset-env which includes all the
latest ECMAScript features:

$ npm install @babel/cli @babel/core @babel/preset-env --save-dev

babel-cli
Provides Babel's command line interface for running Babel from the
command line.

babel-core
Contains the core functionality of Babel.

babel-preset-env
Includes plugins to enable transformation for all modern JavaScript features.

StevenCodeCraft.com
Page 169
StevenCodeCraft.com

2) Configure Babel
Update the package.json to include a script to run Babel, specifying the
preset to use and the files to transpile:

Add the file .babelrc, .babelrc is a configuration file used by Babel, a JavaScript
compiler, to specify options and presets for transforming your JavaScript
code. The name .babelrc stands for "Babel Resource Configuration."

Purpose

It tells Babel how to transpile your code by defining presets and plugins that
Babel should use. These configurations determine how the JavaScript code is
transformed from ES6+ (or other versions) to a compatible version like ES5.
The file is written in JSON format and typically contains an object with
properties such as presets and plugins.

Then add:

StevenCodeCraft.com
Page 170
StevenCodeCraft.com

3) Create a Build Directory


Make a directory where the transpiled JavaScript files will be stored:

$ mkdir build

4) Run Babel
Execute the Babel script to transpile your index.js file to ES5 syntax:

$ npm run babel

Check the generated ES5 code in the build directory to ensure everything is
transpiled correctly.

Integrating Webpack for More Efficient Builds


While Babel is excellent for single files, Webpack is more suitable when your
project consists of multiple files, as it bundles all your project's JavaScript into
one or a few files: Webpack will minify both your code and the final bundles to
reduce file sizes. Minifying JavaScript code involves removing unnecessary
characters like spaces, comments, and line breaks to reduce file size and
improve load times without changing its functionality.

StevenCodeCraft.com
Page 171
StevenCodeCraft.com

1) Set Up Webpack
Install Webpack and configure it to work with Babel to handle the entire
project, not just individual files.

2) Automate with npm Scripts


Update package.json to include a script that runs Webpack, ensuring all your
JavaScript files are processed through Babel and bundled correctly.

Conclusion
By following these steps, you set up a modern JavaScript development
environment capable of handling ES6+ syntax and producing code
compatible with older browsers. This setup ensures that your development
process is efficient, and your applications are robust and maintainable.

StevenCodeCraft.com
Page 172
StevenCodeCraft.com

Webpack
Introduction to Webpack and Babel
Webpack is a powerful tool that bundles JavaScript files into a single file,
optimizing load times and simplifying deployments. Babel, on the other hand,
transpiles modern JavaScript (ES2015+) into backwards-compatible versions.
Note that webpack does not require a configuration file as it does provide a
default configuration and convention but you can provide a custom
configuration file. Webpack is a very large topic and I won't be covering
everything in this lesson, rather I will provide just a high level overview of
what it does and you can refer to the documentation at webpack.js.org to
learn more. Here’s how to set up and use these tools in your development
workflow

Step-by-Step Setup
1) Install Webpack CLI Globally
To start, install the Webpack CLI globally on your system. This allows you to
initialize Webpack projects easily.

2) Initialize Webpack
Navigate to your project directory and run the initialization command. This
will prompt you to answer a few questions to configure your project.

StevenCodeCraft.com
Page 173
StevenCodeCraft.com

$ npx webpack-cli init

npx stands for node package execute and it enables you to execute Node.js
executables or in other words npm packages without installing them globally
on your system

When asked if you'll be using ES2015, answer "Y". This response triggers the
CLI to set up Babel automatically for transpiling your code to ES5.

So that is the benefit of using webpack-cli as it will generate the config files for
us.

3) Automatic Setup
The CLI will handle the generation of a webpack.config.js file and install
necessary packages like babel-loader, which is a Webpack plugin for
transpiling files.

4) Organize JavaScript Files


Place all your JavaScript files in a single folder to keep your project organized
and manageable.

5) npm Initialization
If not already done, initialize your npm project to create a package.json file:

StevenCodeCraft.com
Page 174
StevenCodeCraft.com

$ npm init --yes

6) Configure npm Scripts


Define a build script in your package.json that runs Webpack:

7) Building the Project


Compile and bundle your project

$ npm run build

This command will generate a main.bundle.js file in the dist folder. The code
will be minified to not be human-readable, optimizing for performance.

8) Serving the Bundled File


Include the bundled JavaScript file in your HTML:

<script src="dist/main.bundle.js"></script>

StevenCodeCraft.com
Page 175
StevenCodeCraft.com

9) Automating the Build Process


To make development more efficient, modify the build script to watch for file
changes:

-w stands for watch, as in watch for changes in your files

The -w flag makes Webpack monitor your project files for changes and
automatically recompile the code when modifications are detected.

Executing the Build Script


Run the build script to continuously monitor your files:

$ npm run build

StevenCodeCraft.com
Page 176
StevenCodeCraft.com

Conclusion
Using Webpack and Babel, you can streamline your JavaScript development
process by automatically bundling all project files into a single, optimized file.
This setup reduces the need for manual rebuilds and ensures that your code
is compatible with a wide range of browsers. By leveraging these tools, you
can focus more on development and less on the complexities of managing
dependencies and browser inconsistencies.

Summary
Course Section Summary: Modern JavaScript Development Tools and
Practices

Modules
This lesson introduced the concept of modules in JavaScript, explaining how
they help organize and maintain code by breaking it into manageable,
reusable pieces.

CommonJS
We explored CommonJS, a standard for modularizing JavaScript that allows
the encapsulation of functionality into reusable packages, which has been
widely used in Node.js.

StevenCodeCraft.com
Page 177
StevenCodeCraft.com

ES6 Modules
The course covered ES6 modules, a native JavaScript feature that supports
static import and export of code, offering a more integrated and optimized
way to handle modules directly in the browser or in Node.js environments.

ES6 Tooling
This topic addressed various tools and technologies that support ES6
development, enhancing code compatibility across different browsers and
environments.

Babel
We discussed Babel, a JavaScript compiler that allows developers to write
modern JavaScript code that is then transpiled into backward-compatible
versions for better support across all platforms.

Webpack
The lessons on Webpack provided insights into how this powerful tool
bundles JavaScript files and dependencies into a single file, improving site
speed and efficiency.

Each topic was designed to equip you with the necessary skills to effectively
use modern JavaScript tools and practices, streamlining your development
process and ensuring your projects are robust and maintainable.

StevenCodeCraft.com
Page 178
StevenCodeCraft.com

Active Recall Study Questions


67) What are the key benefits of using modularity in JavaScript applications
and how do modules improve maintainability, code reuse, and abstraction?
68) How do CommonJS and ES6 Modules differ and why is it important to
understand both for developing JavaScript applications in various
environments?
69) Why is high cohesion important in software modules, and how does it
contribute to the maintainability and understandability of the code?
70) Explain the concept of encapsulation in the context of modular
programming. How does using modules in Node.js enhance encapsulation,
and what are the benefits of this approach?
71) What is the syntax for exporting a class or function as the default export
in a CommonJS module and how do you import and use this default export in
another file?
72) How do ES6 modules enhance code maintainability and organization
compared to older module systems?
73) What are the benefits of using tools like Webpack in conjunction with ES6
modules for modern web development?
74) What is the primary function of a transpiler in modern JavaScript
development and why is it important?
75) How does Babel help in making modern JavaScript features compatible
with older browsers?

StevenCodeCraft.com
Page 179
StevenCodeCraft.com

76) What role does a bundler like Webpack play in optimizing web application
performance and resource management?
77) Why might Node.js applications generally not require transpilers like
Babel but still benefit from using bundlers like Webpack?
78) What command do you use to initialize a new npm project and what file
does it create?
79) Why is Babel used in a JavaScript development environment and what
does it transform?
80) What is the purpose of the .babelrc file and how is it typically structured?

StevenCodeCraft.com
Page 180
StevenCodeCraft.com

Active Recall Answers


67) What are the key benefits of using modularity in JavaScript applications
and how do modules improve maintainability, code reuse, and abstraction?
The key benefits of using modularity in JavaScript applications are the
following: Maintainability This leads to smaller modular files that are easier to
understand, debug, and test. Code Reuse Modules can be reused across
different parts of an application or in different projects, reducing code
duplication. Abstraction Modules allow developers to hide implementation
details while exposing a clear interface, making it easier to interact with the
code. By organizing code into modules, you make the application more
manageable, enhancing its scalability, and improve overall code quality.

StevenCodeCraft.com
Page 181
StevenCodeCraft.com

68) How do CommonJS and ES6 Modules differ and why is it important to
understand both for developing JavaScript applications in various
environments?
CommonJS and ES6 Modules differ primarily in their usage and syntax. With
CommonJS, it is used in Node.js for server-side development and it uses the
require() function to import modules and module.export in order to export
them. With ES6 Modules (this is supported natively in browsers), and it uses
import and export statements to manage dependencies. Understanding both
is important because it allows you to work flexibly across different JavaScript
environments using CommonJS for server-side applications and ES6 Modules
for browser based applications, ensuring compatibility and leveraging the
strengths of each module system.

StevenCodeCraft.com
Page 182
StevenCodeCraft.com

69) Why is high cohesion important in software modules, and how does it
contribute to the maintainability and understandability of the code?
High cohesion in software modules means that the functions and
responsibilities within a module are closely related and focused on a specific
task. This is important because: Maintainability Modules with high cohesion
are easier to update and debug since changes are localized to a specific area
with related functionality. This reduces the risk of introducing errors
elsewhere in the system. Understandability High cohesion makes the code
more intuitive and easier to understand. When a module has a single, well-
defined purpose, developers can quickly grasp what it does and how it works
without needing to sift through unrelated code. In summary, high cohesion
leads to more organized, manageable, and comprehensible code, making the
development process smoother and more efficient.

StevenCodeCraft.com
Page 183
StevenCodeCraft.com

70) Explain the concept of encapsulation in the context of modular


programming. How does using modules in Node.js enhance encapsulation,
and what are the benefits of this approach?
Encapsulation in modular programming refers to the practice of keeping a
module’s internal details private and only exposing a limited, necessary
interface to other parts of the application. In Node.js, using modules
enhances encapsulation by: Scope Isolation Each module has its own scope,
meaning its variables and functions are not accessible outside unless
explicitly exported. Controlled Access By only exporting necessary parts of a
module, you control what other parts of the application can interact with,
reducing the risk of unintended interference and misuse. Benefits Improved
Security Sensitive or complex logic is hidden, preventing unauthorized access
and potential misuse. Ease of Maintenance Encapsulated modules can be
updated or refactored independently without affecting other parts of the
application. Clear Interfaces Well-defined interfaces make the system easier
to understand and use, promoting better code organization and
collaboration. In summary, encapsulation via modules in Node.js leads to
safer, more maintainable, and better-organized code.

StevenCodeCraft.com
Page 184
StevenCodeCraft.com

71) What is the syntax for exporting a class or function as the default export
in a CommonJS module and how do you import and use this default export in
another file?
In CommonJS, to export a class or function as the default export, you use
module.exports. To import and use this default export in another file, you use
the require() function.

72) How do ES6 modules enhance code maintainability and organization


compared to older module systems?
ES6 modules improve maintainability by providing a clear structure for
defining and managing dependencies with import and export statements.
This helps keep the code organized and easier to manage.

73) What are the benefits of using tools like Webpack in conjunction with ES6
modules for modern web development?
Using tools like Webpack using ES6 modules helps bundle and optimize code,
managing dependencies more efficiently. This improves load times and
ensures that all necessary modules are correctly included, making the
development process smoother and the application faster.

StevenCodeCraft.com
Page 185
StevenCodeCraft.com

74) What is the primary function of a transpiler in modern JavaScript


development and why is it important?
The primary function of a transpiler in modern JavaScript development is to
convert code written in newer versions of JavaScript (like ES6+) into an older
version (like ES5) that is compatible with a wider range of environments,
including older browsers.

75) How does Babel help in making modern JavaScript features compatible
with older browsers?
Babel helps make modern JavaScript features compatible with older browsers
by converting newer JavaScript syntax and features (like classes, let/const,
and arrow functions) into older syntax that all browsers can understand. This
process, called transpiling, ensures that code written with the latest
JavaScript standards can run on any browser, regardless of its version.

76) What role does a bundler like Webpack play in optimizing web application
performance and resource management?
A bundler like Webpack optimizes web application performance and resource
management by combining all your JavaScript modules and their
dependencies into a single file or a few files. It also minifies the code and
removes unused parts (tree shaking), which reduces the file size and the
number of server requests needed to load the application. This leads to
faster load times and more efficient resource usage.

StevenCodeCraft.com
Page 186
StevenCodeCraft.com

77) Why might Node.js applications generally not require transpilers like
Babel but still benefit from using bundlers like Webpack?
Node.js applications generally do not require transpilers like Babel because
Node.js often supports the latest JavaScript features. However they can still
benefit from using bundlers like Webpack to optimize performance by
combining multiple files into one, reducing file size through minimization,
and managing dependencies more efficiently. This makes the application
faster and easier to deploy.

78) What command do you use to initialize a new npm project and what file
does it create?
To initialize a new npm project, you use the command, npm init --yes This
command creates a file called package.json which manages your project’s
dependencies and configuration.

79) Why is Babel used in a JavaScript development environment and what


does it transform?
Babel is used in a JavaScript development environment to convert modern
JavaScript code (ES6 and newer) into an older version (ES5) that can run in
any browser. This ensures that new features and syntax can be used without
worrying about browser compatibility.

StevenCodeCraft.com
Page 187
StevenCodeCraft.com

80) What is the purpose of the .babelrc file and how is it typically structured?
The purpose of the .bashrc file is to configure Babel, specifying how JavaScript
code should be transformed. It is structured in JSON format and usually
contains an object with properties like presets and plugins that define which
transformations to apply.

StevenCodeCraft.com
Page 188
07

Node Module
System

StevenCodeCraft.com
Page 189
StevenCodeCraft.com

What is Node
Introduction to Node.js

Node.js is an open-source, cross-platform runtime environment that enables


the execution of JavaScript code outside of a web browser. Primarily used for
building server-side applications and networking tools, Node.js supports the
development of back-end services, commonly known as APIs, which are
essential for powering client applications such as web and mobile apps.

Key Features of Node.js


Versatility in Back-End Development
Node.js is well-suited for creating highly-scalable, data-intensive, and real-
time applications. Its non-blocking, event-driven architecture allows it to
handle numerous simultaneous connections with high throughput, making it
an excellent choice for developing services like chat applications and online
gaming servers.

Quick to Start, Agile Friendly


Node.js is renowned for its ease of use and minimal setup, enabling
developers to quickly start building applications. This attribute aligns well
with agile development practices, where time to market and adaptability are
critical.

StevenCodeCraft.com
Page 190
StevenCodeCraft.com

Real-World Usage
Prominent companies such as PayPal, Uber, Netflix, and Walmart leverage
Node.js in their production environments. These organizations have
reported benefits such as reduced development time, decreased number of
code lines and files, which in turn contribute to enhanced performance in
terms of request handling and response times.

JavaScript Universality
One of Node.js’s strongest advantages is its use of JavaScript, the language of
the web. This allows developers to use the same language for both server-
side and client-side scripts, promoting skill reuse and reducing the learning
curve for new developers. Having a uniform programming language across
the stack can lead to a clearer and more consistent codebase.

Rich Ecosystem
Node.js benefits from a vast ecosystem of open-source libraries available
through npm (Node Package Manager), which simplifies the addition of
functionalities and accelerates the development process.

StevenCodeCraft.com
Page 191
StevenCodeCraft.com

Practical Benefits of Node.js


Enhanced Efficiency
Reports from corporations that have adopted Node.js indicate that
applications can be built twice as fast by smaller teams, citing a significant
reduction in the number of lines of code and files required compared to
other technologies.

Performance Gains
Applications built with Node.js can handle twice the number of requests per
second, and the response times can be up to 35% faster than those
developed with other server-side technologies.

Conclusion
Node.js is a popular choice for developers looking to build efficient and
scalable web applications. Its ability to run JavaScript on the server-side,
along with its robust tooling and supportive community, makes it a
dependable and practical choice for modern web development.

StevenCodeCraft.com
Page 192
StevenCodeCraft.com

Node Architecture
So let's go over Understanding Runtime Environments A runtime
environment is a setting where programs are executed. It provides built-in
libraries and manages the program's execution, offering various services
such as handling I/O or network requests.

JavaScript in Browsers
Historically, JavaScript was primarily used within web browsers. Each browser
comes with its own JavaScript engine, which interprets and executes
JavaScript code:

Microsoft uses the Chakra engine.

Firefox uses the SpiderMonkey engine.

Chrome uses the V8 engine.

These different engines can lead to variations in JavaScript behavior across


browsers. Within a browser, JavaScript interacts with the Document Object
Model (DOM) to manipulate web pages, using methods like
document.getElementById('root').

StevenCodeCraft.com
Page 193
StevenCodeCraft.com

Evolution to Node.js
In 2009, Ryan Dahl, the creator of Node.js, proposed an innovative idea:
enabling JavaScript to run outside the browser. He leveraged Google
Chrome’s V8 engine, known for its speed, and embedded it within a C++
program. This integration birthed Node.js, which is essentially an executable
or .exe program that extends JavaScript's capabilities beyond web browsers.

Node.js: More Than Just a Browser Environment


Node.js does not have browser-specific objects like the DOM because it is not
intended for controlling web page content. Instead, it includes additional
modules that provide JavaScript the ability to interact with the file system,
create servers, handle network protocols, and more. For example:

fs.readFile(): For reading files from the system


http.createServer(): For creating a web server

These functionalities demonstrate that Node.js is equipped to handle


backend services, making it ideal for building scalable network applications

StevenCodeCraft.com
Page 194
StevenCodeCraft.com

What Node.js Is and Is Not


Node.js is not a programming language: It uses JavaScript as its scripting
language.
Node.js is not a framework: It does not impose any specific way of
organizing your project; it merely provides a runtime environment with
useful libraries.
Node.js is a runtime environment: It extends JavaScript's reach to the
server-side, offering tools and libraries that are not available in a
browser context.

Conclusion
Node.js revolutionized JavaScript programming by expanding its scope from
the client-side in browsers to include server-side applications. This
advancement has made JavaScript a versatile, powerful choice for full-stack
development. Node.js continues to thrive as a popular runtime environment
due to its efficiency and the vast npm ecosystem, which provides numerous
libraries and tools to enhance functionality.

StevenCodeCraft.com
Page 195
StevenCodeCraft.com

How Node Works


Node.js and Its Asynchronous Nature

Node.js operates on a non-blocking, asynchronous model, which can be


likened to a highly efficient restaurant service.

Asynchronous Operations
Restaurant Analogy
Think of Node.js like a single waiter serving multiple tables. Instead of waiting
for one table’s meal to cook before taking another table’s order, the waiter
keeps taking orders and serving food as it becomes ready.

Technical Explanation
In Node.js, when a request is made (e.g., a database query), the request is
processed without blocking other operations. Node does not wait for the
database response but instead places a callback in the event queue that will
be executed once the response is ready. This allows the Node server to
handle other requests in the meantime.

StevenCodeCraft.com
Page 196
StevenCodeCraft.com

Contrast with Synchronous Operations


Traditional Model
In more traditional synchronous models, like those used by frameworks such
as Rails, each request might be handled by one thread. If the thread is busy
(e.g., waiting for a database query), it cannot take on more work. This can lead
to inefficiencies, as other requests have to wait until free threads are
available, or additional hardware resources must be provided.

Blocking Behavior
In such synchronous environments, a thread handling a database query will
remain idle until the data is returned, which is inefficient compared to Node’s
asynchronous approach.

Benefits of Node.js's Model


Efficiency
Node.js can handle numerous simultaneous operations on a single thread
thanks to its event-driven model. This increases efficiency as the server can
manage multiple tasks without waiting for any single task to complete.

StevenCodeCraft.com
Page 197
StevenCodeCraft.com

Scalability
The ability to handle many requests on few threads allows Node.js
applications to scale effectively without requiring significant hardware
resources. This is particularly beneficial for I/O-intensive applications (e.g.,
web APIs, real-time data processing).

Resource Management
Node’s model promotes better resource utilization, as the server remains
active and non-idle, even when data operations are pending.

Ideal Use Cases for Node.js


Node.js is well-suited for applications that require real-time data processing,
such as online gaming applications, chat applications, and live streaming
services. It excels in environments where the application needs to handle a
large volume of short messages or commands that require low latency.

Limitations of Node.js
However, Node.js is less ideal for CPU-intensive tasks:

StevenCodeCraft.com
Page 198
StevenCodeCraft.com

CPU-Intensive Work
For applications that require intensive data processing tasks such as video
encoding, image manipulation, or large-scale mathematical calculations,
Node.js might not be the best choice. Its single-threaded nature means CPU-
heavy tasks can block the entire system, leading to delays in processing other
concurrent operations.

Conclusion
Node.js leverages an asynchronous, non-blocking model to provide efficient
and scalable solutions for many modern web applications. While it excels in
handling I/O operations, it’s less suited for tasks that are heavily CPU-bound.
Understanding when and where to use Node.js can help developers
maximize their application performance and efficiency.

Intro to the Node Module System


Node.js's module system is a powerful feature that allows developers to
organize the code of large applications efficiently. Modules are discrete units
of functionality that can be imported and used in other parts of a Node.js
application, or even across different Node.js applications.

StevenCodeCraft.com
Page 199
StevenCodeCraft.com

What Are Modules?


A module in Node.js encapsulates related code into a single unit of
functionality. This can be anything from a library of utilities to a set of related
functions that handle specific functionalities like database interactions, file
operations, or network communication.

Why Do We Need Modules?


Modules are essential for several reasons:

Maintainability
By dividing applications into smaller, manageable pieces, modules make the
codebase easier to understand and maintain.

Reusability
Modules can be reused across different parts of an application or even in
different projects, which reduces code duplication and effort.

Namespace Management
Using modules helps avoid global namespace pollution by confining variables
and functions within a local scope rather than cluttering the global scope.

StevenCodeCraft.com
Page 200
StevenCodeCraft.com

How Do Modules Work in Node.js?


In Node.js, each file is treated as a separate module. Node.js provides a
simple and efficient way to create modules and expose them to other parts
of your application:

1) Creating a Module
You can create a module by placing the code into a separate JavaScript file.

2) Exporting Module Contents:


Node.js uses exports and module.exports to make functions and objects
available to other files.

3) Importing a Module
Use require() to include the module in another file.

Creating Your Own Modules


Creating your own modules is as simple as writing any regular JavaScript file
and then using module.exports to expose parts of the module to other parts
of your application. You can expose objects, functions, or any JavaScript data
structure.

StevenCodeCraft.com
Page 201
StevenCodeCraft.com

Conclusion
Understanding how to effectively use the Node.js module system is crucial
for developing scalable and maintainable Node.js applications. By leveraging
modules, developers can create well-organized, modular code that enhances
code quality and development efficiency.

Global Object
Introduction to Global Objects in Node.js

In Node.js, global objects are special objects that are available in all modules.
These objects provide essential functionality that can be accessed anywhere
within a Node.js application, making them a fundamental part of the Node.js
runtime environment.

Built-In Global Objects


Node.js comes with a set of predefined global objects that are readily
available for use. These objects serve various purposes, from fundamental
JavaScript objects to Node-specific objects that provide functionality unique
to the Node.js environment:

StevenCodeCraft.com
Page 202
StevenCodeCraft.com

global
Similar to the window object in browsers, global is the top-level object in
Node.js. Almost all other objects are either properties of this object or
derived from it.

process
This is one of the most important global objects in Node.js. It provides
information about, and control over, the current Node.js process. Through
process, you can access environment information, read environment
variables, communicate with the terminal, or exit the current process.

console
Used to print information to the stdout and stderr. It acts similarly to the
console object in browsers.

Buffer
Used to handle binary data directly. It is built specifically for managing
streams of binary data in Node.js.

StevenCodeCraft.com
Page 203
StevenCodeCraft.com

setImmediate, clearImmediate, setTimeout,


clearTimeout, setInterval, and clearInterval
These globals are timers functions that help you schedule tasks to run after a
specified duration or at regular intervals.

Creating Your Own Global Objects


While Node.js manages a variety of built-in globals, developers can also
define their own global variables that need to be accessible throughout the
application. Here’s how to define and use custom global objects in Node.js:

1) Defining a Global Object


You can attach your custom properties or objects to the global object. This
makes them accessible from anywhere in your Node.js application.

StevenCodeCraft.com
Page 204
StevenCodeCraft.com

2) Using the Global Object


Once a property is attached to the global object, it can be accessed in any
module without requiring imports or passing it explicitly.

Caution When Using Global Objects


While global objects can be incredibly useful for sharing information across
modules, their use should be minimized due to the potential for creating
tightly coupled components and the difficulty in tracking changes throughout
the application. It’s generally better to explicitly pass objects you need within
modules through module exports and require statements.

Conclusion
Global objects in Node.js are powerful tools for developing applications with
shared functionalities. Understanding both built-in and custom globals can
help you effectively manage application-wide settings and maintain state
across various parts of your application. However, limited use of globals is
recommended to maintain a clean and manageable codebase.

StevenCodeCraft.com
Page 205
StevenCodeCraft.com

Modules
Understanding Global Objects

Global objects provide essential functions and variables that can be accessed
from anywhere within a JavaScript environment, whether in a browser or a
Node.js application.

Common Global Functions


console.log()
A global function that outputs information to the console, useful for
debugging and logging application data.

setTimeout()
Allows you to schedule a function to be executed after a specified delay. This
function is very handy for executing code after a pause.

clearTimeout()
Used to cancel a timeout previously established by calling setTimeout().

StevenCodeCraft.com
Page 206
StevenCodeCraft.com

setInterval()
Schedules a function to be run repeatedly at specified intervals. This is useful
for tasks like updating the UI at regular intervals.

clearInterval()
Stops the repeated execution set using setInterval().

Global Scope in Browsers


In browser-based JavaScript, the global object is window. All global variables
and functions are attached to this object:

Accessing Globals
When you declare a variable or a function at the global level, it's accessible
through the window object, e.g., window.setTimeout or window.message.

Global Scope in Node.js


Node.js does not have a window object because it does not run in a browser
environment. Instead, it uses a global object to provide a similar functionality:

Node.js Globals
In Node.js, you can access built-in modules and global functions through the
global object, e.g., global.console.log or global.setTimeout.

StevenCodeCraft.com
Page 207
StevenCodeCraft.com

Variables and Global Scope


Unlike in browsers, if you declare a variable with var, let, or const at the global
level in Node.js, it does not attach to the global object. This behavior
encapsulates the variables within the module scope, avoiding unintentional
conflicts.

Best Practices
While global objects and functions are extremely useful, you should limit their
use.

Avoid Polluting the Global Scope


Adding too many objects to the global scope can lead to conflicts and
difficulties in maintaining the code. It’s generally better to keep global usage
minimal.

Use Local Scope Where Possible


Encapsulate variables and functions within a local scope (such as within
functions or modules) to avoid unintended interactions and make the code
easier to manage and debug.

StevenCodeCraft.com
Page 208
StevenCodeCraft.com

Conclusion
Global objects and functions are fundamental in both browser and Node.js
environments, providing developers with powerful tools for performing
common tasks. Understanding how to use these tools effectively and
responsibly is crucial for developing robust applications.

Creating a Module
Understanding Global Scope and Its Pitfalls

In JavaScript, defining variables or functions directly in the global scope is


straightforward but can lead to complications in larger applications.

Global Scope in Browsers


In a browser environment, when you declare a function or a variable using
var at the top level, it is added to the global scope, which means it is
accessible through the window object:

Problems with Global Scope


While convenient, using the global scope can lead to several issues.

StevenCodeCraft.com
Page 209
StevenCodeCraft.com

Namespace Pollution
The global namespace can become cluttered with too many variables and
functions, making it difficult to track down where specific things are defined.

Accidental Overwriting
If multiple parts of an application inadvertently use the same global variable
names, they can overwrite each other, leading to bugs that are hard to
diagnose. For example, defining sayHello in multiple files will cause the last
loaded script to override all earlier definitions.

The Importance of Modularity


To mitigate the risks associated with global scope, it's essential to adopt
modularity in your development approach.

Modules in Node.js
In Node.js, every file is treated as a module, and anything defined within that
file is local to the module unless explicitly exported. This encapsulation
ensures that variables and functions do not inadvertently interfere with one
another across different parts of an application.

StevenCodeCraft.com
Page 210
StevenCodeCraft.com

Private by Default
Variables and functions are private to their module, providing a clean
namespace and preventing accidental interference.

Explicit Exporting
To make a function or variable available outside its module, it must be
explicitly exported, adding a layer of control over what is exposed.

Advantages of Using Modules


Clarity
Each module has a clear purpose and scope, reducing complexity and
enhancing readability.

Reusability
Modules can be reused across different parts of an application or even in
different projects.

Maintainability
Changes in a module are localized, impacting fewer parts of an application
and thus reducing the risk of bugs.

StevenCodeCraft.com
Page 211
StevenCodeCraft.com

Conclusion
Avoiding global variables and embracing modularity is crucial in JavaScript
development, especially in larger applications or when working in a team
environment. By using modules, developers can ensure their code is
organized, maintainable, and less prone to unexpected behaviors caused by
namespace pollution.

Loading a Module
Creating and Managing a Module in Node.js

In this lesson, we'll go through the process of creating a simple logging


module in Node.js. This module will encapsulate all the functionality related
to logging messages and can be reused in different parts of your application
or even in other applications.

Setting Up the Logger Module


1) Create the Module File
Start by creating a new JavaScript file named logger.js:

$ touch logger.js

StevenCodeCraft.com
Page 212
StevenCodeCraft.com

2) Adding Functionality
Inside logger.js, define the functionality you want to encapsulate. Here, we'll
include a URL for a hypothetical logging service and a function to log
messages:

Making Module Contents Public

Since variables and functions in a Node.js module are private by default, you
need to explicitly export any data or functions you want to make available to
other parts of your application.

3) Exporting Functionality
Use module.exports to make the log function and URL available outside the
module:

StevenCodeCraft.com
Page 213
StevenCodeCraft.com

Alternatively, you can rename exports to provide a more intuitive interface to


module consumers:

Best Practices for Exporting


Selective Exporting
In real-world applications, modules can contain numerous variables and
functions. It's crucial to export only what is necessary to keep the module
easy to use and understand. This practice helps maintain a clean public
interface.

Stability of Public Interface


The public interface of a module should be stable and not expose too many
implementation details. This approach ensures that changes inside the
module do not affect other parts of your application that depend on it.

Using the Logger Module


In another file, such as app.js, you can import and use the logger module:

StevenCodeCraft.com
Page 214
StevenCodeCraft.com

Conclusion
By creating modules in Node.js, you encapsulate specific functionalities and
expose a controlled interface to the rest of your application. This modularity
enhances code reuse, simplifies maintenance, and keeps your application
organized. When designing modules, it's essential to consider what should be
private to maintain module integrity and what should be public to ensure
usability.

Module Wrapper Function


Loading Modules in Node.js with require

Node.js uses the require function to import modules, which is a fundamental


aspect of managing dependencies and modularizing your application. This
system differs from how scripts are loaded in a browser and is specific to the
Node.js environment.

Basic Usage of require


When you need to use functionality defined in another file or module, you
use require to load it. Here’s how it works:

StevenCodeCraft.com
Page 215
StevenCodeCraft.com

1) Loading a Module
To load a module, pass the path of the JavaScript file to the require function.
Node.js resolves the path and loads the module. Node.js automatically
assumes .js as the file extension if it's not specified.

2) Accessing Exported Members


The require function returns an object that represents the exported API of
the module. This object contains all the members that are exported from the
module. You can then use these members in your application:

Best Practices for Using require


Use const for Requiring Modules
It's a best practice to use const when requiring modules. This prevents
accidental reassignment of the module variable within your code, which can
lead to runtime errors. Attempting to reassign a constant will result in an
error, helping catch mistakes during development.

StevenCodeCraft.com
Page 216
StevenCodeCraft.com

Tooling for Code Quality


Tools like JSHint can be used to scan your JavaScript code for potential errors
and bad practices. Running JSHint on your code can help identify issues like
improper use of variables before they cause problems at runtime.

$ jshint app.js

This tool will report problems related to your usage of JavaScript, potentially
saving you from bugs that are difficult to diagnose.

Exporting a Single Function


Sometimes, you might want to export a single function from a module rather
than an object. This can simplify the usage of the module when only one
functionality is being provided:

StevenCodeCraft.com
Page 217
StevenCodeCraft.com

Conclusion
The require function in Node.js is essential for modular programming,
allowing you to include and use JavaScript files and modules efficiently. By
following best practices for module loading and leveraging code quality tools,
you can ensure your Node.js applications are robust, maintainable, and
error-free.

Path Module
How Node.js Processes Modules

Node.js has a unique way of handling JavaScript code, which differs


significantly from how scripts are executed in a browser environment.

Module Wrapping in Node.js


When Node.js executes a module, it does not run the code directly as written.
Instead, it wraps the module code inside a function. This approach is not
immediately apparent to the developer but is fundamental to how Node.js
operates. Here is what happens under the hood:

StevenCodeCraft.com
Page 218
StevenCodeCraft.com

1) Function Wrapping
Each module is wrapped in a function before it is executed. The function
wrapper helps to isolate the module from the global scope, which means
variables and functions defined in a module do not pollute the global object.

2) The Module Wrapper


The function that wraps each module takes several important parameters:

exports: This is used to export module functions and variables.


require: A function used to import the exports of another module.
module: Represents the current module.
__filename: The filename of the module.
__dirname: The directory name of the module.

3) Exporting from Modules


You can add properties to exports or module.exports to make them
accessible to other modules. However, replacing exports entirely won’t affect
module.exports, because exports is just a reference to module.exports.

StevenCodeCraft.com
Page 219
StevenCodeCraft.com

Using Built-In Modules


Node.js comes with a variety of built-in modules that provide rich
functionalities like file system manipulation, HTTP server creation, and
handling system paths. These modules are essential tools for Node.js
developers and are thoroughly documented in the Node.js API
documentation.

Conclusion
Understanding how Node.js executes module code and utilizes function
wrapping provides a clearer picture of the runtime environment. This
process ensures that global variables and functions from one module do not
interfere with another, fostering a more organized and secure coding
environment. Furthermore, Node.js’s built-in modules are powerful tools that
extend the functionality of Node applications, enabling developers to handle
a wide range of system-level tasks efficiently.

StevenCodeCraft.com
Page 220
StevenCodeCraft.com

OS Module
Retrieving System Information with Node.js os Module

When developing applications that require system-level data, Node.js


provides a built-in module called os that allows you to gather information
about the underlying operating system.

Using the os Module

1) Import the Module


Start by requiring the os module at the beginning of your Node.js script.

const os = require('os');

2) Retrieve Memory Information


The os module provides several methods to get system information, such as
os.totalmem() and os.freemem(), which return the total and free memory of
the system, respectively.

StevenCodeCraft.com
Page 221
StevenCodeCraft.com

Improving Output with Template Strings


To make the output more readable and to avoid the pitfalls of string
concatenation, you can use template literals. Template literals are string
literals allowing embedded expressions, introduced in ECMAScript 2015 (ES6).

Heading

StevenCodeCraft.com
Page 222
StevenCodeCraft.com

Running the Script


Execute the script using Node.js to see the results printed to the console:

$ node app.js

This command runs your application script, and it will output the total and
free memory of the system, providing insights that are typically not
accessible from client-side JavaScript running in a browser.

Conclusion
The os module is a powerful tool in Node.js for accessing operating system-
level information. This can be particularly useful for applications that need to
monitor or manage system resources. Using Node.js, developers can write
server-side code that interacts directly with the operating system, offering
capabilities beyond what is possible in a browser environment. This makes
Node.js an excellent choice for building more complex, resource-sensitive
back-end applications.

StevenCodeCraft.com
Page 223
StevenCodeCraft.com

File System Module


Understanding the Node.js File System Module

Node.js provides a powerful built-in module called fs for interacting with the
file system. This module is essential for reading from and writing to files on
the server.

Importing the fs Module


Start by requiring the fs module at the top of your Node.js script:

const fs = require('fs');

Methods in the fs Module


The fs module includes a variety of methods to handle file operations, which
come in two primary forms: synchronous (blocking) and asynchronous (non-
blocking).

1) Synchronous Methods
Synchronous methods block the execution of further JavaScript until the file
operation completes. They are straightforward to use but can significantly
slow down your application, especially under heavy load.

StevenCodeCraft.com
Page 224
StevenCodeCraft.com

2) Asynchronous Methods
Asynchronous methods, on the other hand, perform operations in the
background and accept a callback function that runs once the operation
completes. This approach is non-blocking and allows Node.js to handle other
tasks while waiting for the file operation to finish.

Example of reading directories asynchronously:

StevenCodeCraft.com
Page 225
StevenCodeCraft.com

Best Practices for Using fs Methods


Avoid Synchronous Methods in Production
While synchronous methods are easy to use and understand, they should
generally be avoided in production, especially for I/O-heavy operations. A
Node.js process runs on a single thread, and using synchronous methods
can block this thread, leading to performance bottlenecks when handling
multiple requests.

Prefer Asynchronous Methods


Asynchronous methods allow your Node.js server to remain responsive by
freeing up the main thread to handle other requests while waiting for file
operations to complete. This approach is crucial for maintaining
performance in applications that serve many clients simultaneously.

StevenCodeCraft.com
Page 226
StevenCodeCraft.com

Exploring the Documentation


For a complete list of available methods and additional details, visit the
official Node.js documentation for the fs module. This documentation
provides comprehensive insights and examples for both synchronous and
asynchronous methods.

Conclusion
Understanding and utilizing the fs module in Node.js is crucial for performing
file operations effectively. By preferring asynchronous methods, you can
ensure that your Node.js applications remain efficient and responsive under
load, making full use of Node.js's non-blocking architecture.

Events Module
Understanding Events in Node.js

Node.js is built around an event-driven architecture, primarily using an event-


loop mechanism, which makes it efficient for I/O-heavy operations.

Core Concept: Events


In Node.js, an event signifies that something has occurred within the
application. For example, when a server receives a request at a port, it's an
event. The Node.js runtime handles this event by generating a response.

StevenCodeCraft.com
Page 227
StevenCodeCraft.com

Using the EventEmitter Class


EventEmitter is a core class in Node.js, used for handling events within your
applications. Here’s how you can use it:

1) Importing EventEmitter
First, you need to import the EventEmitter class from the 'events' module.

const EventEmitter = require('events');

2) Creating an Instance
EventEmitter is a class, and you must create an instance of this class to use it.
This instance will manage the events for your application.

const emitter = new EventEmitter();

3) Registering Listeners
Listeners are functions that are called when a specific event is emitted. Use
the .on() method to register a listener for an event.

StevenCodeCraft.com
Page 228
StevenCodeCraft.com

3) Registering Listeners
Listeners are functions that are called when a specific event is emitted. Use
the .on() method to register a listener for an event.

4) Emitting Events
The .emit() method is used to trigger an event. When this method is called, all
registered listeners for the event are invoked.

emitter.emit('messageLogged');

It’s important to register listeners before emitting the event, as they will not
be triggered retroactively.

StevenCodeCraft.com
Page 229
StevenCodeCraft.com

Event Arguments
Events can also pass data to their listeners. You can send multiple
parameters to the listener function by passing them as additional arguments
to .emit().

Practical Usage
EventEmitter is widely used in Node.js core modules, like handling HTTP
requests in web servers or reading files asynchronously. Developers can use
this pattern to handle custom events in their applications, enhancing the
modularity and separation of concerns.

Conclusion
The EventEmitter class is a fundamental part of Node.js that helps manage
events and listeners, facilitating the event-driven architecture that Node.js is
known for. Proper use of this class can help you build robust and
maintainable Node.js applications by organizing operations around the
handling of various system and custom events.

StevenCodeCraft.com
Page 230
StevenCodeCraft.com

Handling Data with Events in Node.js


When working with the Node.js EventEmitter class, it’s common to send
additional data about an event. This can help listeners perform their tasks
more effectively by providing them with the necessary context.

Emitting Events with Data


When triggering an event, you can pass data as arguments to the emit
method. This data can be accessed by the event listeners.

1) Passing Multiple Arguments:


While you can pass multiple arguments separately, it is generally more
manageable to encapsulate them in an object.

StevenCodeCraft.com
Page 231
StevenCodeCraft.com

By passing an object, you can include multiple pieces of data under a single
event argument, which makes handling this data in listeners simpler and
more organized.

Registering Listeners to Handle Data


Listeners can be set up to receive event data. Here's how to define listeners
that handle the data passed to them:

2) Using a Function to Listen for Events


You can define listeners using either traditional function syntax or ES6 arrow
functions. Both methods allow you to access the event data passed from the
emit call.

In both cases, the arg (also commonly named eventArg or simply e)


represents the data object passed from the emit method.

StevenCodeCraft.com
Page 232
StevenCodeCraft.com

Practical Example: Custom Logging Event


3) Raising and Handling a Custom Logging Event
You can set up a specific event for logging messages and emit data related to
those messages. Here, the logging event is configured to pass an object
containing a message, and the listener logs this message when the event is
emitted.

Here, the logging event is configured to pass an object containing a message,


and the listener logs this message when the event is emitted.

StevenCodeCraft.com
Page 233
StevenCodeCraft.com

Conclusion
Using EventEmitter in Node.js to handle events with additional data is a
powerful pattern for developing modular and responsive applications. By
encapsulating data in an object and passing it through events, your
application can maintain clear and manageable communication between
different parts of your system. This approach not only keeps your code
organized but also enhances its flexibility and scalability.

Extending EventEmitter
Integrating EventEmitter into Custom Classes

In real-world applications, it's uncommon to use EventEmitter directly.


Instead, you typically extend EventEmitter in a custom class to encapsulate
related functionalities along with event handling capabilities.

Why Extend EventEmitter?


Extending EventEmitter allows you to build self-contained components that
can generate events and handle their own functionalities. This is particularly
useful for creating modules that need to perform actions and then notify
other parts of your application that those actions have completed.

StevenCodeCraft.com
Page 234
StevenCodeCraft.com

Example:
Let's go through the process of creating a Logger class that extends
EventEmitter to handle logging operations and emit events.

1) Defining the Logger Class


In logger.js, define a class Logger that extends EventEmitter. This class will
have a method log that triggers an event when called.

StevenCodeCraft.com
Page 235
StevenCodeCraft.com

2) Using the Logger Class in Your Application


In app.js, import the Logger class, create an instance of it, and listen for the
messageLogged event.

Benefits of This Approach


Encapsulation
The Logger class encapsulates both the logging functionality and the event
handling, making it modular and easy to manage.

Reusability
By abstracting the event-emitting behavior into a class, you can reuse and
extend this class wherever needed without rewriting the event handling logic.

StevenCodeCraft.com
Page 236
StevenCodeCraft.com

Maintainability
Having a dedicated class for logging and event emission helps maintain and
modify logging behavior independently from the rest of your application.

Common Mistake
A common mistake when starting with EventEmitter is to create multiple
instances of the emitter when only one is needed, or to bind listeners to
different instances than the emitter that emits the event. This is why
encapsulating the EventEmitter within a class, as shown, ensures that the
event listeners are correctly associated with the specific instance of the class
that emits the events.

Conclusion
By extending EventEmitter in custom classes like Logger, Node.js applications
can handle complex functionalities while efficiently communicating events
across different modules. This pattern enhances the modular architecture of
the application, promoting clean and manageable code that adheres to
modern software design principles.

StevenCodeCraft.com
Page 237
StevenCodeCraft.com

HTTP Module
Creating a Web Server with Node.js
Node.js provides the http module, a powerful tool for developing networking
applications, such as web servers that listen for HTTP requests on a specified
port.

Basic Web Server Setup


1) Import the http Module
Begin by importing the http module, which includes the functionality
necessary to create server instances.

const http = require('http');

2) Create a Server
Use the createServer method to create a new server. This server can handle
HTTP requests and responses.

const server = http.createServer();

StevenCodeCraft.com
Page 238
StevenCodeCraft.com

3) Listening to Events
The server object created by http.createServer() is an instance of
EventEmitter. This means you can listen to events like connection for lower-
level network events.

4) Starting the Server


Have the server listen on a port, such as 3001, to handle incoming
requests. Testing this setup in a browser with localhost:3001 will display the
connection logs in the terminal.

StevenCodeCraft.com
Page 239
StevenCodeCraft.com

Handling HTTP Requests More Efficiently


While the above method works for simple demonstrations, handling HTTP
requests based on different routes can be done more effectively.

5) Refining the Server to Handle Requests


Instead of listening to the connection event, you can directly handle HTTP
requests by providing a callback to createServer that deals with requests and
responses. This setup directly responds to HTTP GET requests at the root (/)
and at /api/courses, sending a JSON response in the latter case.

StevenCodeCraft.com
Page 240
StevenCodeCraft.com

Using Express for Complex Applications


For more complex web applications, managing routes with the native http
module becomes cumbersome. This is where Express, a web application
framework built on top of the http module, comes in handy.

Advantages of Express
Simplification of Routing
While Express uses the underlying http module, it simplifies many tasks and
adds powerful new features.

Middleware Support
Express allows you to use middleware to respond to HTTP requests, handle
errors, and process data.

Enhanced Server Capabilities


While Express uses the underlying http module, it simplifies many tasks and
adds powerful new features.

StevenCodeCraft.com
Page 241
StevenCodeCraft.com

Conclusion
Starting with Node.js's http module is great for learning the basics of network
communication in JavaScript. However, for building more sophisticated
applications, using a framework like Express can greatly simplify your code
and enhance your server's functionality, making it easier to maintain and
expand.

StevenCodeCraft.com
Page 242
StevenCodeCraft.com

Summary
Overview of Node Module System
Introduction to Node.js
This part of the course provides a foundational understanding of what
Node.js is and the benefits of using it.

Node.js Architecture
Explains the internal architecture of Node.js, including its non-blocking,
event-driven nature that allows for high performance across many real-world
applications.

Deep Dive into Node.js Functionalities


How Node Works
An exploration of the underlying mechanisms that power Node.js, such as the
event loop and asynchronous programming.

Introduction to the Node Module System


Discusses the modular structure of Node.js applications, emphasizing how
modularity is achieved and managed.

StevenCodeCraft.com
Page 243
StevenCodeCraft.com

Working with Node.js Modules


Global Objects
Covers built-in global objects in Node.js, which are accessible across all
modules.

Modules
Focuses on the importance of modules in Node.js for organizing and
maintaining code.

Creating and Loading Modules


Practical guidance on how to create your own modules and how to import
existing ones.

Module Wrapper Function


Explains how Node.js wraps module code within a function to maintain
module scope and privacy.

StevenCodeCraft.com
Page 244
StevenCodeCraft.com

Specific Node.js Modules


Path Module
Introduces utilities for handling and transforming file paths.

OS Module
Provides information about the computer's operating system where the
Node.js application is running.

File System Module


Demonstrates how to work with the file system for reading from and writing
to files.

Events Module
Discusses the EventEmitter class and how to handle custom events in
applications.

Advanced Topics
Event Arguments
Details how to pass and handle data with events using the EventEmitter.

StevenCodeCraft.com
Page 245
StevenCodeCraft.com

Extending EventEmitter
Teaches how to enhance and customize the EventEmitter for more complex
event handling scenarios.

HTTP Module
Outlines how to use Node.js to create web servers and handle HTTP requests
effectively.

Conclusion
This section covered the fundamentals use Node.js and its module system for
building scalable and efficient applications. By understanding and utilizing
the core modules and architecture principles of Node.js, developers can
create robust back-end services and applications suited for a variety of real-
world tasks.

StevenCodeCraft.com
Page 246
StevenCodeCraft.com

Active Recall Study Questions


80) What is Node.js?
81) What is a process?
82) What is a thread?
83) How does Node.js’s asynchronous, non-blocking model improve server
efficiency and scalability compared to traditional synchronous models?
84) What types of applications are ideally suited for Node.js, and what
limitations should developers consider when choosing it for their projects?
85) How does the Node.js module system contribute to the maintainability
and scalability of large applications?
86) What advantages does the modular approach in Node.js offer in terms of
code organization and reuse across different projects?
87) How do built-in global objects in Node.js facilitate application-wide
functionality and control?
88) What are the potential risks for using custom global objects in Node.js
and how can they be mitigated?
89) How do global objects differ in scope and behavior between browser-
based JavaScript and Node.js environments?
90) What are the best practices for using global functions and variables in
JavaScript to maintain code quality and avoid conflicts?

StevenCodeCraft.com
Page 247
StevenCodeCraft.com

91) What challenges can arise from using the global scope in JavaScript,
especially in larger applications?
92) How does modularity in JavaScript help in maintaining clean and
manageable codebases?
93) What are the benefits of encapsulating functionality into modules when
developing Node.js applications?
94) How does careful management of a module’s public interface contribute
to the maintainability and stability of a Node.js application?
95) What role does the require function play in managing dependencies and
modularity within a Node.js application?
96) How can following best practices when using the require function
contribute to the maintainability and reliability of a Node.js codebase?
97) How does Node.js’s module wrapping mechanism contribute to isolating
module scope and preventing global namespace pollution?
98) What benefits do Node.js’s built-in modules provide for handling system-
level tasks in application development?
99) How can the Node.js ‘os’ module be utilized to gather essential system
information for applications that require insights into the operating system’s
resources?
100) What advantages does Node.js offer for building resource-sensitive
back-end applications that need to interact with the operating system
directly?

StevenCodeCraft.com
Page 248
StevenCodeCraft.com

101) What are the benefits and drawbacks of using synchronous versus
asynchronous methods when performing file operations in Node.js?
102) Why is it important to prioritize asynchronous methods for file system
operations in production environments when using Node.js?
103) How does Node.js’s event-driven architecture enhance the efficiency of
handling I/O operations?
104) What role does the EventEmitter class play in managing events and
listeners within Node.js applications?
105) How does passing data through events improve communication and
modularity within a Node.js application?
106) What are the benefits of encapsulating event data in an object when
using the Node.js EventEmitter class?
107) What are the advantages of integrating the EventEmitter class into
custom classes when building modular components in Node.js?
108) How does extending EventEmitter in custom classes enhance the
reusability and maintainability of Node.js applications?
109) What are the benefits of using the Node.js http module to create a basic
web server and when might you consider using a framework like Express
instead?
110) How does the event-driven nature of Node.js influence the design and
functionality of a web server created with the http module?

StevenCodeCraft.com
Page 249
StevenCodeCraft.com

Active Recall Answers


80) What is Node.js?
Node.js is a runtime environment for executing JavaScript outside of a web
browser. With Node.js, developers can use JavaScript for both server-side and
client-side applications. Node.js is open source, cross-platform, non-blocking,
event-driven architecture and enables the creation of highly scalable and
efficient back-end services. Node.js has a rich ecosystem of open-source
libraries available through npm (Node Package Manager) to accelerate the
development process.

81) What is a process?


A process is a program in execution.

82) What is a thread?


A thread is the execution path within a process, handling operations
sequentially, line by line.

StevenCodeCraft.com
Page 250
StevenCodeCraft.com

83) How does Node.js’s asynchronous, non-blocking model improve server


efficiency and scalability compared to traditional synchronous models?
Node.js’s asynchronous, non-blocking model improves server efficiency and
scalability by allowing a single thread to handle multiple requests
simultaneously. Unlike traditional synchronous models where a thread waits
for a task (like a database query) to complete before moving on, Node.js
processes requests without waiting. It places tasks in an event queue and
continues handling other operations. This approach prevents idle waiting,
maximizes resource utilization, and allows Node.js servers to manage more
requests with fewer hardware resources, making them highly efficient and
scalable.

84) What types of applications are ideally suited for Node.js, and what
limitations should developers consider when choosing it for their projects?
Node.js is ideally suited for applications that require real-time data
processing and handle many simultaneous connections, such as online
gaming, chat applications, and live streaming services. It excels in managing a
large volume of short, low-latency messages. However, developers should
consider its limitations for CPU-intensive tasks, like video encoding, or large-
scale mathematical calculations. Node.js’s single-threaded nature can cause
performance bottlenecks in such scenarios, making it less suitable for
applications requiring heavy data processing.

StevenCodeCraft.com
Page 251
StevenCodeCraft.com

85) How does the Node.js module system contribute to the maintainability
and scalability of large applications?
The Node.js module system contributes to maintainability and scalability by
organizing code into smaller, reusable units, making it easier to manage,
understand, and update individual parts of a larger application without
affecting the entire codebase.

86) What advantages does the modular approach in Node.js offer in terms of
code organization and reuse across different projects?
The modular approach in Node.js improves code organization by
encapsulating functionality into separate units, reducing complexity. It also
enhances code reuse, allowing modules to be easily shared and used across
different projects.

87) How do built-in global objects in Node.js facilitate application-wide


functionality and control?
Built-in global objects in Node.js provide essential tools that can be accessed
from anywhere in the application, allowing for easy control and management
of the Node.js environment. They enable functionalities like process
management, timing operations, and handling binary data without needing
to import or define them explicitly in each module. This makes them crucial
for application-wide tasks and system-level operations.

StevenCodeCraft.com
Page 252
StevenCodeCraft.com

88) What are the potential risks for using custom global objects in Node.js
and how can they be mitigated?
Using custom global objects in Node.js can lead to tightly coupled code,
making it harder to track changes and debug issues. Globals are accessible
everywhere, so unexpected modifications can cause bugs that are difficult to
trace. To mitigate these risks, it’s better to minimize the use of globals and
instead pass needed objects explicitly through module exports and imports.
This keeps the codebase cleaner, more modular, and easier to maintain.

89) How do global objects differ in scope and behavior between browser-
based JavaScript and Node.js environments?
In browser-based JavaScript, the global object is ‘window’, and global
variables and functions are attached to it. In Node.js, the global object is
‘global’, but variables declared with var, let, or const are not attached to it,
they’re scoped to the module. This difference helps prevent global scope
pollution in Node.js, making it easier to manage variables and avoid conflicts.

90) What are the best practices for using global functions and variables in
JavaScript to maintain code quality and avoid conflicts?
Best practices for using global functions and variables in JavaScript include
minimizing their use to avoid polluting the global scope, which can lead to
conflicts and difficult-to-maintain code. Instead, encapsulate variables and
functions within local scopes, such as functions or modules, to keep your
code organized and reduce the risk of unintended interactions. This
approach helps maintain code quality and makes debugging easier.

StevenCodeCraft.com
Page 253
StevenCodeCraft.com

91) What challenges can arise from using the global scope in JavaScript,
especially in larger applications?
Using the global scope in JavaScript can lead to several challenges in larger
codebases, including namespace pollution, where too many global variables
and functions make it hard to manage code, and accidental overwriting,
where different parts of the application unintentionally use the same global
names, causing conflicts and bugs that are difficult to track down. This can
result in code that is harder to maintain and debug.

92) How does modularity in JavaScript help in maintaining clean and


manageable codebases?
Modularity in JavaScript helps maintain clean and manageable codebases by
encapsulating code into self-contained units (modules). Each module has its
own scope, reducing the risk of name collisions and accidental overwrites.
This makes the code easier to understand, debug, and reuse, as changes in
one module are less likely to impact other parts of the application. It also
promotes better organization and clarity, leading to more maintainable and
scalable applications.

StevenCodeCraft.com
Page 254
StevenCodeCraft.com

93) What are the benefits of encapsulating functionality into modules when
developing Node.js applications?
Encapsulating functionality into modules in Node.js helps to organize code,
making it more manageable and reusable. It allows developers to isolate
specific functions or data, reducing the risk of conflicts and making the
codebase easier to maintain. Modules also enable better code reuse across
different parts of an application or even in other projects, leading to a more
modular and scalable development approach.

94) How does careful management of a module’s public interface contribute


to the maintainability and stability of a Node.js application?
Careful management of a module’s public interface in Node.js ensures that
only necessary functions and variables are exposed, keeping the internal
implementation details hidden. This reduces the risk of unintended
interference with other parts of the application and makes it easier to update
or refactor the module without breaking dependent code. It contributes to
the overall maintainability and stability of the application by providing a clear,
controlled, and stable interface for other modules to interact with.

StevenCodeCraft.com
Page 255
StevenCodeCraft.com

95) What role does the require function play in managing dependencies and
modularity within a Node.js application?
The require function in Node.js is crucial for managing dependencies and
modularity by allowing you to load and use code from other files or modules
within your application. It enables you to break your application into smaller,
reusable components, making your code more organized, maintainable, and
scalable. By importing only the needed functionality, require helps keep your
application’s structure clear and modular.

96) How can following best practices when using the require function
contribute to the maintainability and reliability of a Node.js codebase?
Following best practices when using the require function in Node.js, such as
using const for module imports and keeping modules small and focused,
helps maintain a clear and consistent code structure. This reduces the risk of
errors, makes your code easier to understand, and improves its reliability. It
also prevents accidental reassignment of modules and ensures that changes
in one part of the code don’t unintentionally affect other parts, contributing
to a more maintainable and robust codebase.

StevenCodeCraft.com
Page 256
StevenCodeCraft.com

97) How does Node.js’s module wrapping mechanism contribute to isolating


module scope and preventing global namespace pollution?
Node.js’s module wrapping mechanism isolates each module’s scope by
wrapping the module code in a function. This prevents variables and
functions defined in one module from leaking into the global scope, reducing
the risk of naming conflicts and ensuring that each module operates
independently. This mechanism helps maintain a clean and organized
codebase, where global namespace pollution is minimized, making the
application more secure and easier to manage.

98) What benefits do Node.js’s built-in modules provide for handling system-
level tasks in application development?
Node.js’s built-in modules provide powerful tools for handling system-level
tasks, such as file manipulation, network communication, and managing
paths. These modules save developers time by offering ready-to-use,
optimized functionality that integrates seamlessly with the Node.js runtime.
By using these built-in modules, developers can efficiently manage system
resources, build robust applications, and avoid the need to reinvent common
functionalities, leading to more secure, maintainable, and performant
applications.

StevenCodeCraft.com
Page 257
StevenCodeCraft.com

99) How can the Node.js ‘os’ module be utilized to gather essential system
information for applications that require insights into the operating system’s
resources?
The Node.js ‘os’ module can be utilized to gather essential system
information by providing methods to access details like total and free
memory, CPU architecture, network interfaces, and more. This information is
crucial for applications that need to monitor or optimize system resources,
especially in server environments where understanding the underlying
operating system can help in managing performance and resource allocation
effectively. The ‘os’ module allows developers to write scripts that interact
directly with the operating system, offering insights that go beyond typical
client-side capabilities.

100) What advantages does Node.js offer for building resource-sensitive


back-end applications that need to interact with the operating system
directly?
Node.js offers several advantages for building resource-sensitive back-end
applications that need to interact with the operating system directly. It
provides built-in modules, like the ‘os’ module, which allows access to system-
level information such as memory usage, CPU details, and network interfaces.
This direct interaction with the OS enables better monitoring, management,
and optimization of system resources. Additionally, Node.js’s non-blocking
I/O model makes it well-suited for handling multiple tasks efficiently, which is
critical in resource-sensitive environments.

StevenCodeCraft.com
Page 258
StevenCodeCraft.com

101) What are the benefits and drawbacks of using synchronous versus
asynchronous methods when performing file operations in Node.js?
Synchronous methods in Node.js are easy to implement and use, but they
block the execution of further code until the operation completes, which can
lead to performance issues, especially under heavy load. Asynchronous
methods perform operations in the background without blocking the main
thread, allowing the server to handle other tasks simultaneously. This makes
them more suitable for production environments, ensuring better
performance and responsiveness. The drawback is that they require handling
callbacks or promises, which can add complexity to the code.

102) Why is it important to prioritize asynchronous methods for file system


operations in production environments when using Node.js?
It is important to prioritize asynchronous methods for file system operations
in Node.js production environments because they allow the server to remain
responsive by freeing up the main thread to handle other tasks.
Asynchronous operations prevent the application from being blocked by
long-running tasks, which is crucial for maintaining high performance and
efficiently serving multiple client requests simultaneously. This helps ensure
the application can scale and handle high loads without performance
bottlenecks.

StevenCodeCraft.com
Page 259
StevenCodeCraft.com

103) How does Node.js’s event-driven architecture enhance the efficiency of


handling I/O operations?
Node.js’s event-driven architecture enhances the efficiency of handling I/O
operations by using an event loop to manage multiple tasks without blocking
the main thread. When an I/O operation is initiated, Node.js can continue
processing other tasks while waiting for the operation to complete. This non-
blocking approach allows Node.js to handle many concurrent operations
efficiently, making it ideal for applications that require high performance and
scalability in I/O-intensive environments.

104) What role does the EventEmitter class play in managing events and
listeners within Node.js applications?
The EventEmitter class in Node.js plays a central role in managing events and
listeners. It allows you to create and handle custom events within your
application. By using EventEmitter, you can define events, register listeners
that respond to those events, and emit the events when certain conditions
are met. This helps organize your application around an event-driven
architecture, enabling more modular, maintainable, and responsive code,
especially in applications with asynchronous operations

StevenCodeCraft.com
Page 260
StevenCodeCraft.com

105) How does passing data through events improve communication and
modularity within a Node.js application?
Passing data through events in a Node.js application improves
communication and modularity by allowing different parts of the application
to interact efficiently without direct dependencies. It enables event listeners
to receive relevant data when an event occurs, promoting a clear separation
of concerns. This modular approach makes the code more organized,
flexible, and easier to maintain, as components can respond to events and
handle data independently.

106) What are the benefits of encapsulating event data in an object when
using the Node.js EventEmitter class?
Encapsulating event data in an object when using the Node.js EventEmitter
class provides several benefits. It allows you to pass multiple related pieces of
data as a single argument, making the event handling code more organized
and easier to manage. This approach also enhances readability and
scalability, as the object structure can be easily extended to include
additional data without altering the function signatures or logic. It simplifies
the communication between different parts of the application, ensuring a
cleaner and more modular design.

StevenCodeCraft.com
Page 261
StevenCodeCraft.com

107) What are the advantages of integrating the EventEmitter class into
custom classes when building modular components in Node.js?
Integrating the EventEmitter class into custom classes in Node.js allows you
to build modular components that can handle specific tasks and emit events
related to those tasks. This approach encapsulates functionality and event
management within a single class, making the code more organized,
reusable, and easier to maintain. It ensures that event handling is tightly
coupled with the relevant functionality, improving the overall architecture
and communication between different parts of the application.

108) How does extending EventEmitter in custom classes enhance the


reusability and maintainability of Node.js applications?
Extending EventEmitter in custom classes enhances reusability and
maintainability by encapsulating both functionality and event management
within a single self-contained module. This allows the class to be easily reused
across different parts of an application or in other projects without
duplicating code. It also simplifies maintenance because the logic for emitting
and handling events is centralized, making it easier to update or modify
without affecting the rest of the application.

StevenCodeCraft.com
Page 262
StevenCodeCraft.com

109) What are the benefits of using the Node.js http module to create a basic
web server and when might you consider using a framework like Express
instead?
Using the Node.js ‘http’ module to create a basic web server is beneficial for
learning the fundamentals of network communication and handling HTTP
requests directly. It offers full control over the server’s behavior with minimal
overhead. However, for more complex applications, a framework like Express
is preferable because it simplifies routing, supports middleware, and adds
powerful features, making the development process faster, more organized,
and easier to maintain as the application grows.

110) How does the event-driven nature of Node.js influence the design and
functionality of a web server created with the http module?
The event-driven nature of Node.js influences the design and functionality of
a web server by allowing it to handle multiple requests concurrently without
blocking the main thread. This non-blocking I/O model ensures that the
server can efficiently manage incoming connections and process requests as
events, responding to them asynchronously. This design is particularly well-
suited for high-performance applications, where the server needs to remain
responsive and scalable even under heavy loads.

StevenCodeCraft.com
Page 263
08

Node Package
Manager

StevenCodeCraft.com
Page 264
StevenCodeCraft.com

Intro the Node Package Manager


Using Package Managers in Node.js Development

In Node.js development, managing third-party libraries and modules is


facilitated by package managers. The two primary package managers used in
the Node.js ecosystem are npm (Node Package Manager) and Yarn.

Understanding npm
Registry
Visit npmjs.com to explore a wide range of packages available for various
functionalities. These packages are open-source and free to use.

Installation
npm comes bundled with Node.js, so when you install Node.js, you
automatically get npm installed on your system.

Checking npm Version


$ npm -v

StevenCodeCraft.com
Page 265
StevenCodeCraft.com

Updating npm
To ensure you have the latest features and security updates, you can update
npm to a specific version globally using:

$ sudo npm install -g npm@latest

2) Using npm
Installing Packages
To add a package to your project, use

$ npm install packageName

Global vs. Local Installation


By default, npm installs packages locally within your project. However, some
tools need to be available globally to be run from anywhere on your system:

$ npm install -g packageName

StevenCodeCraft.com
Page 266
StevenCodeCraft.com

Introduction to Yarn
Yarn is another popular package manager that can be used as an alternative
to npm. It was created by Facebook and is known for its speed, reliability, and
improved network performance.

1) Installing Yarn
Unlike npm, Yarn is not bundled with Node.js and must be installed
separately. You can install Yarn globally using npm

$ npm install -g yarn

2) Using Yarn

Adding a Package
Similar to npm, you can add packages to your project using Yarn

$ yarn add packageName

Why Use Yarn?


Yarn caches every package it downloads, so it never needs to download the
same package again. It also parallelizes operations to maximize resource
utilization and thus install packages faster.

StevenCodeCraft.com
Page 267
StevenCodeCraft.com

Publishing Your Own Package


If you've developed a functionality that could benefit others, you can publish
your own packages to npm, making it available to the global Node.js
community.

1) Prepare Your Package


Ensure your package is well-documented, has a clear README, and includes
all necessary metadata in package.json.

2) Publishing
$ npm login

$ npm publish

Conclusion
npm and Yarn are essential tools for any Node.js developer. They simplify the
process of integrating third-party libraries into your projects, manage
dependencies effectively, and help you contribute back to the community by
publishing your own packages. Whether you choose npm or Yarn depends on
your specific needs and preferences, as both provide robust features to
streamline development workflows.

StevenCodeCraft.com
Page 268
StevenCodeCraft.com

package.json
Setting Up a Node.js Project with npm

When starting a new Node.js project, one of the first steps is to set up a
package.json file. This file contains metadata about your project and
manages the project's dependencies.

Creating a New Project Directory


1) Create and Enter the Project Directory
Start by creating a new directory for your project and navigate into it:

$ mkdir npm-demo
$ cd npm-demo

2) Initialize package.json
Before adding any Node packages to your project, you need to create a
package.json file. This file will track your project's dependencies and store
other important project information.

$ npm init

StevenCodeCraft.com
Page 269
StevenCodeCraft.com

This command will prompt you to enter several pieces of information such as
the project's name, version, description, entry point (like index.js), test
command, repository, keywords, author, and license. These details help
define and document your project.

3) Automating package.json Creation


If you prefer to skip the manual input and use default values for your
package.json, you can use the --yes flag:

$ npm init --yes

This command automatically fills in default values for all the fields in the
package.json file, speeding up the setup process.

Importance of package.json
Project Metadata
The package.json file holds key information about your project, which can be
useful for package management and during deployment.

Dependency Management
It lists all the packages your project depends on, allowing npm to
automatically install and manage these packages for you.

StevenCodeCraft.com
Page 270
StevenCodeCraft.com

Script Shortcuts
You can define scripts in package.json that you can run with npm. This is
useful for tasks like starting the server, running tests, or custom build
processes.

Best Practices
Regular Updates
Keep your package.json updated as you add or remove dependencies,
update scripts, or change project metadata

Version Control
Include package.json in your version control system (such as git) to ensure
that team members and deployment environments use the correct project
settings and dependencies.

Conclusion
The package.json file is a fundamental component of any Node.js project,
serving as the blueprint for managing the project's settings and
dependencies. By starting your project with the creation of this file, you
ensure that all dependencies are correctly tracked and that your project
metadata is well-documented from the beginning. This initial setup step is
crucial for maintaining a healthy, manageable codebase as your project
grows.

StevenCodeCraft.com
Page 271
StevenCodeCraft.com

Installing a Node Package


Adding a Third-Party Library to Your Node.js Application

Integrating third-party libraries into your Node.js project can enhance


functionality without the need to write additional code. Libraries such as
underscore provide a range of utilities for common programming tasks.

Step 1: Choosing a Library


Begin by choosing a library that suits your needs. For instance, underscore is
a popular JavaScript library that offers helpful functional programming
utilities. You can search for underscore or other libraries on npmjs.com,
which also provides installation instructions and documentation for each
package.

Step 2: Installing the Library


To add a library like underscore to your project, use the npm install
command. This command downloads the library from the npm registry and
adds it to your project.

$ npm install underscore

Alternatively, you can use the shortcut:


$ npm i underscore

StevenCodeCraft.com
Page 272
StevenCodeCraft.com

When you install a package using npm, two important things happen:

Update to package.json
npm adds the library to the "dependencies" section of your package.json file.
This entry ensures that anyone else working with your project repository can
install the same dependencies.

Library Storage
The library files are downloaded and stored in the node_modules/ directory
within your project.

Previously, to save a dependency explicitly to your package.json, you needed


to add the --save flag. However, recent versions of npm automatically save
installed packages to package.json, making the --save flag unnecessary.

Step 3: Using the Library in Your Application


After installation, you can require and use the library in your application files.
For example, to use underscore in your Node.js application, you would first
require it at the top of your file:

StevenCodeCraft.com
Page 273
StevenCodeCraft.com

Conclusion
Adding third-party libraries like underscore to your Node.js projects
streamlines development by allowing you to utilize pre-built functionalities.
This practice not only saves time but also enhances the capabilities of your
applications. Always ensure to use well-maintained and trusted libraries, and
manage your dependencies through package.json to keep your projects
organized and maintainable.

Using a Package
Using third-party libraries like underscore can significantly streamline
complex operations in Node.js applications. Here’s how you can include and
use underscore in your project.

Setting Up Your Project File


Start by creating the main file where you will write your code:

$ touch index.js

Importing the Underscore Library


In your index.js file, require the underscore library. It is common to use the
underscore (_) symbol as the variable name for this library, reflecting its
actual name

StevenCodeCraft.com
Page 274
StevenCodeCraft.com

const _ = require('underscore');

The require function follows these steps to locate the underscore module:

It first checks if underscore is a core Node.js module.


Failing that, it looks for underscore as a relative or absolute path in the
project.
Lastly, it searches within the node_modules/ directory.

Using Underscore in Your Application


Underscore provides a wide range of utility functions that are highly useful
for working with collections, arrays, and objects. For example, to check if an
array contains a specific item, you can use the contains function. This
function returns true if the array contains the specified value, demonstrating
how underscore can simplify array operations.

StevenCodeCraft.com
Page 275
StevenCodeCraft.com

Running Your Application


Run your application using Node.js to see the output of the underscore
function.

$ node index.js

This will execute the script in index.js, and you should see true output to the
console if the array contains the number 2.

Documentation and Further Exploration


To better understand all available functions and their uses in the underscore
library, visit the documentation on npmjs.com/package/underscore. This can
provide insights into additional methods and their potential applications.

Conclusion
Using libraries like underscore in Node.js projects helps to reduce the
amount of code you need to write while increasing functionality and
readability. It is essential for developers to familiarize themselves with
importing modules and understanding the path resolution mechanism of
Node.js to effectively manage and utilize various libraries. This practice
ensures your applications are both efficient and scalable.

StevenCodeCraft.com
Page 276
StevenCodeCraft.com

Package Dependencies
Lesson: Installing a Node Package: Axios

When developing applications with Node.js, managing external libraries and


dependencies is commonly handled using npm (Node Package Manager).
Here’s how you can install a package like axios, which is widely used for
making HTTP requests to APIs.

Step 1: Install Axios


To add axios to your project, use the npm install command. This command
fetches the package from the npm registry and installs it into your project.

$ npm i axios

Observations Post-Installation
Node_modules Directory
After installation, check your project's node_modules/ directory. You'll notice
that it contains not only axios but also several other directories. These are
dependencies that axios requires to function properly.

StevenCodeCraft.com
Page 277
StevenCodeCraft.com

Dependency Management

Flat Structure
In the past, npm used a nested structure where each package would have its
own node_modules directory containing its dependencies. This often led to
duplication and a deeply nested directory structure, which could cause path
length issues, especially on Windows.

Current Approach
Now, npm installs all dependencies in a flat structure in the root
node_modules directory of your project. This change helps avoid redundancy
and the complications of deeply nested dependencies.

Handling Version Conflicts


If different packages require different versions of the same dependency, npm
will nest the conflicting version locally within the requiring package's
directory to avoid version clashes at the root level.

StevenCodeCraft.com
Page 278
StevenCodeCraft.com

Benefits of the Current Approach


Simplification
The flat structure simplifies dependency management by reducing
redundancy and the potential for version conflicts across your project.

Efficiency
It minimizes disk space usage and improves installation speed since npm no
longer needs to install multiple instances of the same package across
different locations.

Compatibility
Reduces issues related to file path limits on certain operating systems, which
is particularly beneficial for Windows users.

Practical Example with Axios


After installing axios, you can start using it in your Node.js applications to
make HTTP requests. Here’s a simple example to include axios in your
project:

StevenCodeCraft.com
Page 279
StevenCodeCraft.com

Conclusion
Installing and managing Node packages with npm is a straightforward
process that enhances the functionality of your applications. Understanding
how npm handles dependencies allows you to better organize and optimize
your projects. By using tools like axios, developers can easily make HTTP
requests in their applications, making the development process more
efficient.

NPM Packages and Source Control


Managing node_modules in Node.js Projects

When working with Node.js, the node_modules directory can become quite
large because it contains all the packages you've installed using npm, along
with their dependencies. Here’s how to efficiently handle this directory,
especially in collaborative environments.

The Role of node_modules


Storage of Dependencies

node_modules holds all the packages that your application needs to run,
which are installed based on the list of dependencies found in your
package.json file.

StevenCodeCraft.com
Page 280
StevenCodeCraft.com

Why Exclude node_modules from Version Control


Size Considerations
This folder can quickly grow to hundreds of megabytes or more due to the nested
dependencies typical in Node.js projects. Including it in version control would
significantly increase the size of your repository and slow down operations like
cloning and pulling changes.

Reproducibility
Every dependency and its exact version is already specified in package.json, which
means node_modules can be recreated on any machine by running npm install.
Thus, there is no need to include it in version control.

Steps to Exclude node_modules


Here's how you can exclude node_modules from Git, which is essential for
keeping your repository clean and lightweight:

StevenCodeCraft.com
Page 281
StevenCodeCraft.com

Initialize Git in Your Project


If you haven't already initialized Git in your project directory, you can do so
from the command line with:

$ git init

Check Git Status


Running git status will show you all the untracked files and changes. Initially, it
will include node_modules.

$ git status

Create a .gitignore File


The .gitignore file tells Git which files or directories to ignore in your project.

$ touch .gitignore

Configure .gitignore
Open the .gitignore file in a text editor and add the following line to specify
that the node_modules directory should not be tracked by Git:

node_modules/

Save the file and exit the editor.

StevenCodeCraft.com
Page 282
StevenCodeCraft.com

Verify the Setup


After updating .gitignore, run git status again. You should see that
node_modules is no longer listed as untracked.

$ git status

Add and Commit Your Changes


Add your changes to the staging area and make your initial commit. This will
track your project files, including .gitignore, but exclude node_modules.

$ git add .
$ git commit -m "Initial commit"

Restoring node_modules
If you clone the project or need to restore dependencies, simply run:

$ npm install

This command looks at package.json and installs all the necessary packages
from the npm registry.

StevenCodeCraft.com
Page 283
StevenCodeCraft.com

Conclusion
Excluding node_modules from version control is a best practice in Node.js
development. It keeps your project repository manageable, speeds up
operations like cloning, and ensures that all developers are working with the
same dependencies as defined in package.json. This approach fosters a
cleaner, more efficient development environment.

Semantic Versioning
Understanding Semantic Versioning in Node.js

Semantic Versioning, or SemVer, is a standard for versioning software, which


is widely adopted in the development community, including Node.js
packages. It helps developers understand the potential impact of updating a
package.

Components of Semantic Versioning


A typical version number in SemVer format includes three components:

Major Version
Indicates significant changes that make API changes which are not backwards
compatible. Upgrading to a different major version could break existing
functionalities.

StevenCodeCraft.com
Page 284
StevenCodeCraft.com

Minor Version
Adds new features in a backwards-compatible manner. It does not break or
change existing functionality but adds to it.

Patch Version
Includes bug fixes and minor changes that do not affect the software's
functionality or API (also backwards-compatible).

For example, in the version ^4.13.6:


4 is the major version
13 is the minor version
6 is the patch version

The Caret (^) and Tilde (~) in Versioning


Caret (^)
When you see a caret (^) in front of a version number in package.json, it
means npm can install updates that do not modify the left-most non-zero
digit in the SemVer string. For ^4.13.6, npm is allowed to install any 4.x.x
version as long as the major version 4 does not change, even if the minor or
patch version is higher than specified.

StevenCodeCraft.com
Page 285
StevenCodeCraft.com

Tilde (~)
A tilde (~) allows updates that only change the most right-hand digit that is
not zero in the SemVer string, assuming that most right-hand digit is the
patch. For ~1.13.6, npm can update to 1.13.x, where x is any patch number
greater than 6. This means you get bug fixes and minor changes that are
unlikely to break your project.

Installing Packages with npm


When someone clones a repository and runs $ npm install, npm installs the
dependencies based on the rules set in the package.json using the caret (^)
or tilde (~) operators. This allows for a controlled upgrade path that balances
stability with getting timely patches and features.

Specifying Exact Versions


In some cases, especially when ensuring absolute consistency across
environments or dealing with very sensitive dependencies, you might want to
pin dependencies to an exact version. To do this, you can specify the version
without any prefix:

"underscore": "1.13.6"

This configuration guarantees that no version other than 1.13.6 will be


installed, avoiding any unforeseen issues due to minor updates or patches.

StevenCodeCraft.com
Page 286
StevenCodeCraft.com

Conclusion
Understanding how semantic versioning works with npm helps manage
dependencies more effectively, ensuring that applications remain stable
while still receiving necessary updates and bug fixes. It allows developers to
control the risk associated with automatically updating packages and ensures
that all team members and production environments run the same versions
of each package.

Listing the Installed Packages


Checking Installed Versions of Node.js Packages

When managing a Node.js project, it's essential to keep track of the versions
of packages installed to ensure compatibility and stability. Here’s how to
determine the versions of the packages you have installed.

Viewing Installed Versions


Manual Checking
Sometimes, you may want to manually check the version of a specific package
installed in your project. This can be done by looking at the package.json file
within each package’s directory in node_modules.

StevenCodeCraft.com
Page 287
StevenCodeCraft.com

For example, to check the version of Axios

Go to node_modules/axios/package.json

At the end of the file, you will find a version key that tells you the exact version
installed:

"version": "5.11.15"

This method can be cumbersome if you need to check multiple packages.

Using npm Commands


A more efficient way to view all installed packages and their versions is to use
the npm list command, which displays the tree of packages installed in your
project. Running this command in your project directory will show you a tree
structure of all the packages, including their dependent packages:

$ npm list

This output can be extensive because it includes all nested dependencies.

Simplifying the Output


If you are only interested in the top-level packages (those directly specified in
your project’s package.json), you can simplify the output using the --depth=0
option:

StevenCodeCraft.com
Page 288
StevenCodeCraft.com

$ npm list --depth=0

This command restricts the output to the first level of the dependency tree,
showing only the packages that you have directly installed in your project.

Why It Matters
Compatibility
Knowing the exact versions of the packages you have installed helps manage
compatibility between different parts of your application and its
dependencies.

Debugging
When troubleshooting issues in your application, knowing the exact versions
can help determine if a specific version of a package might be causing the
problem.

Updates and Upgrades


Regularly checking installed package versions can help you decide when to
upgrade to newer versions and take advantage of new features or important
bug fixes.

StevenCodeCraft.com
Page 289
StevenCodeCraft.com

Conclusion
Effectively managing package versions in a Node.js project is crucial for
maintaining the stability and reliability of your applications. Using tools like
npm list helps streamline this process by providing a clear overview of what is
installed, thus enabling better version control and dependency management.
By routinely checking and updating your dependencies, you can ensure your
application remains secure, efficient, and up-to-date.

Viewing Registry Info for a Package


Learning About npm Packages

When working with npm packages, it is often necessary to understand their


dependencies, versions, and other metadata to ensure they fit well within
your project.

StevenCodeCraft.com
Page 290
StevenCodeCraft.com

Viewing Package Metadata on npmjs.com


Using the npm Website

Go to npmjs.com.
Search for the package you're interested in, such as axios.
On the package page, you will see comprehensive details including the
latest version, licensing, repository link, weekly downloads, and the
package's dependencies.
This page is useful for getting a quick overview of the package and its
documentation.

Using npm Commands to View Package


Information
For a more direct and detailed exploration of package metadata, you can use
npm commands:

Viewing General Metadata


To see a summary of metadata for a package directly in your command line,
use the npm view command:

$ npm view axios

StevenCodeCraft.com
Page 291
StevenCodeCraft.com

This command outputs information such as the latest version, description,


main entry point, repository, keywords, author, license, and bugs link.

Viewing Dependencies
If you’re specifically interested in what dependencies a package has, you can
directly view that information:

$ npm view axios dependencies

This will list the dependencies required by axios, showing you what packages
it relies on to function.

Checking Available Versions


To view all versions of a package that have been published to npm, use:

$ npm view axios versions

This is particularly useful if you need to upgrade or downgrade to a specific


version. It lists every version available, helping you make informed decisions
based on the features or fixes included in each.

StevenCodeCraft.com
Page 292
StevenCodeCraft.com

Practical Usage of Package Information


Upgrade Decisions
Knowing the versions and dependencies can help you decide whether to
upgrade a package. You can assess the changes between versions and
determine if the upgrade addresses any issues you face or offers new
features you need.

Compatibility Checks
Viewing dependencies ensures that the package is compatible with other
components of your application, especially if there are specific versions of
dependencies that your application requires.

Debugging and Issue Resolution


Detailed metadata can assist in debugging issues related to package
configurations or interactions between multiple packages.

Conclusion
Understanding how to retrieve and utilize metadata about npm packages is
essential for effective package management in Node.js projects. Whether it's
through browsing npmjs.com for a high-level overview or diving deep with
npm view commands for specific details, these tools provide critical insights
that help maintain the health and functionality of your applications.

StevenCodeCraft.com
Page 293
StevenCodeCraft.com

Installing a Specific Version of a


package
When developing applications, you might occasionally need to install a
version of a package that isn't the latest due to compatibility issues, specific
features, or other dependencies in your project. This can be accomplished
easily using npm commands.

Specifying Package Versions


Installing a Specific Version
To install a specific version of a package, append the version number to the
package name using an @ symbol.

For example, to install version 8.4.1 of mongoose, you would use:

$ npm install [email protected]

This command tells npm to fetch and install exactly version 8.4.1 of
mongoose from the npm registry.

StevenCodeCraft.com
Page 294
StevenCodeCraft.com

Example with Another Package


Similarly, if you need to install version 1.13.6 of underscore, you would run

$ npm install [email protected]

This ensures that you get the specific features or API compatibility that
version 1.13.6 offers.

Verifying Installation
After installing the packages, it’s a good practice to verify that the correct
versions have been installed:

Checking Installed Versions


Use the npm list --depth=0 command to check the versions of the top-level
packages installed in your project:

$ npm list --depth=0

This command provides a list of all packages directly installed in your project
(ignoring their dependencies) along with the version numbers. It allows you
to quickly verify that the correct versions are installed.

StevenCodeCraft.com
Page 295
StevenCodeCraft.com

Why Specify Versions?


Compatibility
Certain projects or dependencies might require specific versions to maintain
compatibility. Breaking changes in newer versions could disrupt the
functionality of your application.

Bug Fixes
Some versions might include bug fixes not present in newer versions, or new
versions might introduce bugs that were not present in the older versions.

Features
Older versions might have features that have been deprecated in the latest
release but are still necessary for your project.

Conclusion
Understanding how to specify and install particular versions of packages with
npm is crucial for precise dependency management in software
development. This practice helps ensure that your application remains stable
and behaves as expected, regardless of changes and updates in the package
ecosystem. By specifying package versions, you maintain control over your
development environment and reduce the risk of unexpected issues.

StevenCodeCraft.com
Page 296
StevenCodeCraft.com

Updating Local Packages


Managing Outdated npm Packages

Maintaining the dependencies of your application ensures you benefit from


the latest bug fixes, performance improvements, and security patches. Here’s
how to identify and update outdated packages in your Node.js project.

Checking for Outdated Packages


List Outdated Packages

To check which packages are outdated in your project, use the npm outdated
command. This will display all dependencies with newer versions available,
showing the current version you're using, the latest version available, and the
desired version (which is the highest version permitted by your versioning
rules in your package.json file).

$ npm outdated

StevenCodeCraft.com
Page 297
StevenCodeCraft.com

Updating Packages
To update all packages to the newest versions that do not include breaking
changes (typically minor updates and patches), you can run:

$ npm update

This command updates the packages within the semantic versioning


constraints specified in your package.json. It will not update packages to a
new major version that could contain breaking changes.

Handling Major Updates


Updating to Latest Major Versions

Sometimes, you may need or want to update packages to their latest major
versions, which might include breaking changes. To handle this, you can use a
tool called npm-check-updates.

StevenCodeCraft.com
Page 298
StevenCodeCraft.com

First, install npm-check-updates globally


$ sudo npm install -g npm-check-updates

Run npm-check-updates to see which packages have new


major versions available
$ npm-check-updates

You can then use the tool to update your package.json to


the latest major versions
$ npm-check-updates -u

or the shortcut:
$ ncu -u

Reinstall Dependencies
After updating package.json with npm-check-updates, the packages
themselves are not automatically updated in your node_modules directory.
You need to reinstall them to sync your directory with the updated
package.json:

$ npm install

StevenCodeCraft.com
Page 299
StevenCodeCraft.com

Best Practices
Testing
Always thoroughly test your application after updating dependencies,
especially when major versions are involved, to ensure no breaking changes
disrupt your application.

Version Control
Commit changes to your package.json and package-lock.json after updates.
This ensures that your team or deployment environments use the same
versions.

Conclusion
Regularly updating the npm packages in your Node.js projects is crucial for
maintaining the security, efficiency, and reliability of your applications. Tools
like npm-check-updates help manage the lifecycle of your dependencies,
allowing you to take advantage of the latest improvements while carefully
managing the risk of breaking changes.

StevenCodeCraft.com
Page 300
StevenCodeCraft.com

DevDependencies
Managing Dependencies in Node.js Projects

In Node.js development, it's important to differentiate between


dependencies required for the application to function in production and
those needed only during the development process.

Understanding Dependencies
Production Dependencies
These are the packages your application needs to function correctly in the
production environment, such as frameworks (e.g., Express), database
libraries (e.g., Mongoose), or any other libraries necessary for the runtime
execution of your app.

Development Dependencies
Development dependencies are tools and libraries used only during the
development process, such as compilers (e.g., Babel),' testing frameworks
(e.g., Mocha), or static analysis tools (e.g., JSHint). These are not needed in
production and should not be bundled with your production build.

StevenCodeCraft.com
Page 301
StevenCodeCraft.com

Installing Development Dependencies


Specifying Development-Only Packages
To install a package as a development dependency, use the --save-dev flag.
This tells npm to save the package under devDependencies in your
package.json. This distinction is crucial for keeping production environments
lightweight by excluding unnecessary packages. For example, to install JSHint
as a development dependency:

$ npm install jshint --save-dev

This command installs JSHint and adds it to the devDependencies section of


your package.json:

StevenCodeCraft.com
Page 302
StevenCodeCraft.com

Benefits of Correctly Categorizing Dependencies


Optimized Production Builds
By segregating development dependencies, you can ensure that your
production environment only installs what is necessary, reducing
deployment size and potentially improving application performance.

Clear Project Organization


Keeping a clear distinction between dependencies and devDependencies in
your package.json helps maintain organization and clarity, making it easier
for other developers to understand the project setup.

Managing node_modules
Storage
Despite the categorization in package.json, both production and
development packages are stored in the node_modules/ directory when
installed. The distinction in package.json helps npm understand which
packages to install in different environments (e.g., when setting
NODE_ENV=production).

StevenCodeCraft.com
Page 303
StevenCodeCraft.com

Deploying to Production
When deploying your application, you can ensure that only production
dependencies are installed by setting the environment variable NODE_ENV to
production and running npm install. npm will skip devDependencies in this
case.

$ NODE_ENV=production npm install

Conclusion
Properly managing production and development dependencies is vital for
efficient development workflows and optimized production deployments. By
categorizing your packages appropriately in package.json, you can maintain a
lean and efficient application setup that ensures only necessary packages are
included in production environments. This practice not only optimizes
performance but also enhances security by minimizing the attack surface of
your application.

StevenCodeCraft.com
Page 304
StevenCodeCraft.com

Uninstalling a Package
Removing Unused Node.js Packages
Over time, you may find that certain npm packages are no longer needed in
your application. Removing these packages helps keep your project lean and
prevents unnecessary bloat in your node_modules directory.

Removing a Package
Uninstalling a Package
To remove an installed package, you can use the npm uninstall command
followed by the package name. This command removes the package from
your node_modules directory and updates the dependencies list in your
package.json file.

$ npm uninstall packageName

Alternatively, you can use the shorthand un instead of uninstall:

$ npm un mongoose

StevenCodeCraft.com
Page 305
StevenCodeCraft.com

Effects of Uninstalling
When you uninstall a package, npm automatically updates your package.json
file, removing the package from the list of dependencies or devDependencies,
depending on where it was listed.

Updating node_modules/
The corresponding package directory and its contents are removed from the
node_modules/ folder. This cleanup helps reduce the overall project size and
declutters your development environment.

Best Practices
Verify Dependencies
Before uninstalling a package, make sure it is not required by any other part
of your application. You can check where and how a package is used in your
project to avoid removing a package that is still in use.

Commit Changes
After uninstalling a package and verifying that your application still functions
as expected, commit the changes to your version control system. This keeps
your repository up-to-date and allows other developers to be aware of the
changes in dependencies.

StevenCodeCraft.com
Page 306
StevenCodeCraft.com

Regular Maintenance
Periodically review your package.json and your project dependencies to
identify and remove packages that are no longer necessary. This practice
keeps your project clean and minimizes potential security risks associated
with outdated or unused packages.

Conclusion
Regularly updating and cleaning up your project’s dependencies are crucial
steps in maintaining a healthy codebase. Uninstalling unused packages
reduces the complexity of your project, decreases load times, and lessens the
risk of conflicts or security vulnerabilities. By keeping your package.json and
node_modules directory streamlined, you ensure that your project remains
efficient and manageable.

Working with Global Packages


Understanding Global npm Packages

In Node.js development, some packages are not tied to a specific project but
are rather tools or utilities used across multiple projects. These are typically
installed globally on your system.

StevenCodeCraft.com
Page 307
StevenCodeCraft.com

Installing Global Packages


Purpose of Global Installation
Global packages are typically command-line tools and utilities that you want
to run from anywhere on your system, not just within a specific project. For
instance, package managers (like npm itself) or project scaffolding tools (like
Angular CLI) are commonly installed globally.

Using the -g Flag


To install a package globally, use the -g or --global flag with the npm install
command. This tells npm to place the package in a special system-wide
location that Node.js can access from any directory.

For example:
$ npm install -g @angular/cli

This installs the Angular CLI globally, which you can then use to create new
Angular projects from anywhere on your system.

StevenCodeCraft.com
Page 308
StevenCodeCraft.com

Updating npm Itself


npm can also be updated globally using the same flag. This is useful to ensure
that you have the latest features and security updates.

$ sudo npm install -g npm

Note on Permissions
On macOS and Linux, you might need to use sudo to install global packages,
depending on your system’s permissions settings. However, setting up npm
to run without sudo can avoid permission issues and is recommended for
security reasons.

Managing Global Packages


Checking for Outdated Packages
To manage and update your global packages, you can check which ones are
outdated by running:

$ npm outdated -g

his command lists all globally installed packages that have newer versions
available.

StevenCodeCraft.com
Page 309
StevenCodeCraft.com

Best Practices for Global Packages


Minimize Global Installations
Install packages globally only when necessary. Overuse of global installations
can lead to version conflicts between projects and complicate dependency
management.

Regular Updates
Keep your global packages updated to leverage new features and security
enhancements.

Document Globally Installed Tools


For team projects, document the tools required globally, so all developers set
up their environments consistently

Conclusion
Global npm packages are essential tools for development, offering
functionalities that extend across multiple projects. Properly managing these
packages ensures that your development environment is both effective and
secure. By regularly updating and maintaining the global packages, you can
avoid potential conflicts and keep your system optimized for all your
development needs.

StevenCodeCraft.com
Page 310
StevenCodeCraft.com

Publishing a package
Publishing Your Own npm Package

Creating and publishing your own npm package can be a rewarding process,
allowing you to share your work with the wider Node.js community. Here’s
how to go about it from start to finish.

Step 1: Set Up Your Package


Create a New Directory
Begin by creating a directory for your new package.

$ mkdir new-lib
$ cd new-lib

Initialize the Package


Use npm init to create a package.json file with default values. The --yes flag
auto-accepts default options.

$ npm init --yes

StevenCodeCraft.com
Page 311
StevenCodeCraft.com

Create the Main File


Create an index.js file where you will write the code for your package.

$ touch index.js

Open your text editor to add functionality to the package. For example, a
simple function to add numbers:

module.exports.add = function(a, b) { return a + b; }

Step 2: Create or Use an npm Account


Account Setup
If you don’t already have an npm account, you will need to create one. This
can be done from the npm website or directly from the command line.

$ npm adduser

If you already have an account, simply log in:


$ npm login

You will be prompted to enter your username, password, and email address.

StevenCodeCraft.com
Page 312
StevenCodeCraft.com

Step 3: Publish Your Package


Prepare for Publishing
Ensure your package.json file includes a unique name for your package. npm
requires that package names be unique to the registry.

Publish the Package


Once you are ready and have verified that all information is correct, publish
your package to npm.
$ npm publish

This command uploads your package to the npm registry, making it available
for others to install.

Step 4: Verify Installation


Testing Installation

To ensure that your package can be installed from another project, create a
new directory elsewhere on your system and install the package.

$ mkdir test-new-lib
$ cd test-new-lib
$ npm install new-lib

StevenCodeCraft.com
Page 313
StevenCodeCraft.com

Test the functionality in this new project environment to confirm everything


works as expected.

After Publishing
Update and Maintenance
After publishing, you might need to update your package with improvements
or bug fixes. Make changes in your local project, increment the version
number in your package.json (following Semantic Versioning), and run npm
publish again.

Metadata and Visibility


npm automatically adds metadata to your published package. This includes
information like the publication date, version history, and dependencies. This
metadata is crucial for users of your package to understand its history and
stability.

Conclusion
Publishing a package on npm is a straightforward process but requires
careful setup and attention to detail to ensure that the package is functional
and useful to others. By following these steps, you can contribute your own
modules to the npm ecosystem and potentially help thousands of developers
worldwide.

StevenCodeCraft.com
Page 314
StevenCodeCraft.com

Updating a published package


When you've made changes to your npm package, such as adding new
features or fixing bugs, you must update the package's version before
republishing it. This ensures that users can keep track of changes and
upgrade their installations appropriately.

Step 1: Make Changes to Your Package


In your package directory, make the necessary changes to your code.For
example, if you want to add a multiplication function to your package:

// In index.js
module.exports.multiply = function(a, b) { return a * b; }

Step 2: Update the Package Version


Versioning Your Package

Before you can republish your package, you need to update its version in the
package.json file. npm uses semantic versioning, which includes major,
minor, and patch updates.

StevenCodeCraft.com
Page 315
StevenCodeCraft.com

Major Version (major): Makes incompatible API changes.


Minor Version (minor): Adds functionality in a backward-compatible
manner
Patch Version (patch): Makes backward-compatible bug fixes

$ npm version major // for breaking changes


$ npm version minor // for new features that are backward-compatible
$ npm version patch // for backward-compatible bug fixes

This command updates the version number in your package.json and creates
a new commit (if your package directory is a git repository).

Step 3: Publish the Updated Package


Republish Your Package

After updating the version, you are now ready to publish the updated
package.
$ npm publish

If you try to publish without updating the version, npm will return an error
because the package version must be unique. Following the version update,
the publish command should succeed.

StevenCodeCraft.com
Page 316
StevenCodeCraft.com

Additional Considerations
Testing
Before publishing an update, thoroughly test your package to ensure that
new changes do not introduce bugs or break existing functionality.

Documentation
Update your README file or any other documentation to reflect changes
made in the new version, especially if you've added new features or made
significant modifications.

Conclusion
Updating a published npm package requires careful attention to version
management and compatibility. By following semantic versioning rules, you
can communicate the nature of changes in your package updates to users
effectively. Always ensure that your updates are well-tested and documented,
which helps maintain and enhance the package's utility and credibility in the
community.

StevenCodeCraft.com
Page 317
StevenCodeCraft.com

Active Recall Study Questions


111) What are the key differences between npm and Yarn in terms of speed,
reliability, and package management features?
112) How do package managers like npm and Yarn contribute to efficient
dependency management in Node.js projects?
113) Why is the package.json file crucial for managing dependencies and
project metadata in a Node.js project?
114) How does initializing a Node.js project with npm init contribute to better
organization and maintainability of the project’s codebase?
115) What are the key considerations when selecting and integrating third-
party libraries into a Node.js project?
116) How does managing dependencies through npm enhance the
organization and maintainability of a Node.js application?
117) What are the benefits of using third-party libraries like Underscore in
Node.js projects, and how can they enhance the efficiency of your code?
118) How does understanding Node.js’s module resolution process help in
effectively managing and integrating external libraries into a project?
119) What are the advantages of npm’s flat dependency structure in modern
Node.js projects, particularly in terms of efficiency and compatibility?
120) How does understanding npm’s dependency management process
benefit developers when integrating external packages like Axios into a
Node.js application?

StevenCodeCraft.com
Page 318
StevenCodeCraft.com

121) Why is it important to exclude the node_modules directory from version


control, and how does it benefit the management of a Node.js project?
122) What are the best practices for managing dependencies and restoring
them in a Node.js project, particularly in collaborative environments?
123) How does semantic versioning help developers manage dependencies
and ensure stability in Node.js projects?
124) What are the implications of using caret (^) and tilde (~) operators in
versioning when managing package updates in Node.js?
125) What is it important to regularly check the versions of installed Node.js
packages in a project?
126) How can using npm commands help streamline the process of
managing and verifying package versions in a Node.js project?
127) Why is it important to regularly check the dependencies and versions of
npm packages in a Node.js project, and how can this practice impact the
stability and compatibility of the application?
128) Why might a developer need to install a specific version of an npm
package, and how does this practice contribute to project stability?
129) What are the benefits of verifying installed npm package versions after
installation, and how does it ensure consistency in a Node.js project?
130) What are the best practices for safely updating npm packages,
particularly when handling major version changes?

StevenCodeCraft.com
Page 319
StevenCodeCraft.com

131) Why is it important to differentiate between production and


development dependencies in a Node.js project?
132) How does proper management of dependencies impact the efficiency
and security of a Node.js application in production?
133) Why is it important to regularly remove unused npm packages from a
Node.js project, and how does this practice contribute to project
maintenance?
134) What steps should developers take before and after uninstalling npm
packages to ensure their Node.js project remains functional and up-to-date?
135) What are the benefits and potential risks of installing npm packages
globally instead of locally in a Node.js development environment?
136) How can developers effectively manage and maintain global npm
packages to ensure a consistent and secure development environment?
137) What are the key steps involved in creating, publishing, and maintaining
an npm package for the Node.js community?
138) Why is it important to verify and test your npm package after publishing,
and how can this impact its usability by other developers?
139) Why is semantic versioning important when updating an npm package,
and how does it help communicate changes to users?
140) What best practices should developers follow when updating and
republishing an npm package to ensure its reliability and usability?

StevenCodeCraft.com
Page 320
StevenCodeCraft.com

Active Recall Answers


111) What are the key differences between npm and Yarn in terms of speed,
reliability, and package management features?
The key differences between npm and Yarn are: Speed: Yarn generally installs
packages faster than npm because it parallelism operations and caches
packages locally, reducing the need for repeated downloads. Reliability: Yarn
ensures consistency by using a lockfile (yarn.lock) to keep track of exact
package versions, which helps avoid version mismatches. Package
Management: Yarn offers better offline support and more efficient
dependency resolution, while npm is more integrated with the Node.js
ecosystem and its package registry.

112) How do package managers like npm and Yarn contribute to efficient
dependency management in Node.js projects?
Package managers like npm and Yarn contribute to efficient dependency
management in Node.js projects by automating the process of installing,
updating, and organizing third-party libraries. They manage versioning to
ensure compatibility, handle dependency trees to prevent conflicts, and
streamline workflows through commands for adding, removing, and
updating packages. Both tools also maintain lockfiles to ensure consistent
environments across different development setups, making it easier to
collaborate and maintain project stability.

StevenCodeCraft.com
Page 321
StevenCodeCraft.com

113) Why is the package.json file crucial for managing dependencies and
project metadata in a Node.js project?
The package.json file is crucial in a Node.js project because it serves as the
central hub for managing project dependencies, scripts, and metadata. It
tracks all the libraries your project relies on, ensuring consistent installations
across different environments. Additionally, it stores important project
information such as the version, author, and licensing details, and provides
scripts for common tasks, making the project easier to manage, share, and
deploy. This file is essential for maintaining an organized and efficient
codebase.

114) How does initializing a Node.js project with npm init contribute to better
organization and maintainability of the project’s codebase?
Initializing a Node.js project with ‘npm init’ contributes to a better
organization and maintainability by creating a package.json file that
centralizes project metadata and dependency management. This file keeps
track of all libraries and modules the project depends on, ensuring consistent
setups across different environments. It also allows you to define scripts for
common tasks, making you workflow more efficient and the project easier to
manage as it grows. This structured approach promotes a well-organized and
maintainable codebase from the start.

StevenCodeCraft.com
Page 322
StevenCodeCraft.com

115) What are the key considerations when selecting and integrating third-
party libraries into a Node.js project?
When selecting and integrating third-party libraries into a Node.js project, key
considerations include ensuring the library is well-maintained and widely
used, which indicates reliability and community support. Check for
compatibility with your project’s Node.js version and evaluate the library’s
documentation and features to ensure it meets your specific needs. Also,
consider the potential impact on your project’s performance and security.
Properly manage these dependencies in your package.json to maintain an
organized and maintainable codebase.

116) How does managing dependencies through npm enhance the


organization and maintainability of a Node.js application?
Managing dependencies through npm enhances the organization and
maintainability of a Node.js application by automatically tracking all required
libraries in the package.json file. This ensures that all dependencies are
documented and can be easily installed or updated by other developers or in
different environments. npm also handles versioning, preventing conflicts
and ensuring consistent environments across different setups. This
structured approach helps keep the project organized and simplifies
dependency management, making the application easier to maintain over
time.

StevenCodeCraft.com
Page 323
StevenCodeCraft.com

117) What are the benefits of using third-party libraries like Underscore in
Node.js projects, and how can they enhance the efficiency of your code?
Using third-party libraries like Underscore in Node.js projects provides ready-
made utility functions that simplify complex tasks, reducing the amount of
code you need to write. This enhances code efficiency, readability, and
maintainability. By leveraging well-tested functions from libraries, you can
focus more on building unique features rather than reinventing common
functionalities, leading to faster development and more robust applications.

118) How does understanding Node.js’s module resolution process help in


effectively managing and integrating external libraries into a project?
Understanding Node.js’s module resolution process helps in effectively
managing and integrating external libraries by ensuring that you know how
Node.js locates and loads modules. This knowledge allows you to correctly
structure your project, avoid conflicts, and troubleshoot issues related to
module paths. It also ensures that the right versions of dependencies are
used, which is crucial for maintaining a stable and predictable development
environment. This leads to smoother integration of external libraries and
more reliable application behavior.

StevenCodeCraft.com
Page 324
StevenCodeCraft.com

119) What are the advantages of npm’s flat dependency structure in modern
Node.js projects, particularly in terms of efficiency and compatibility?
npm’s flat dependency structure improves efficiency by reducing
redundancy, as it avoids multiple copies of the same package being installed
in different locations. This structure also minimizes disk space usage and
speeds up installation times. In terms of compatibility, it reduces the
likelihood of path length issues, especially on Windows, and simplifies
dependency management by making the directory structure less complex
and easier to navigate. This approach leads to a more streamlined and
manageable project setup.

120) How does understanding npm’s dependency management process


benefit developers when integrating external packages like Axios into a
Node.js application?
Understanding npm’s dependency management benefits developers by
ensuring that they can effectively handle and resolve potential conflicts
between different versions of packages required by their Node.js application.
It helps in maintaining a clean and organized project structure, preventing
issues like redundant installations or version mismatches. This knowledge is
crucial when integrating external packages like Axios, as it ensures smooth
installation, compatibility, and optimal performance of the application,
leading to more reliable and maintainable code.

StevenCodeCraft.com
Page 325
StevenCodeCraft.com

121) Why is it important to exclude the node_modules directory from version


It is important to exclude the node_modules directory from version control
because it significantly reduces the size of the repository, making operations
like cloning and pulling changes faster. The node_modules directory can be
recreated at any time by running npm install, which installs all necessary
dependencies based on the package.json file.

122) What are the best practices for managing dependencies and restoring
them in a Node.js project, particularly in collaborative environments?
Best practices for managing dependencies in a Node.js project include a
package.json file to track all dependencies and their versions. This ensures
consistency across different environments. In collaborative environments, it’s
crucial to exclude the node_modules directory from version control to keep
the repository lightweight and avoid unnecessary duplication. To restore
dependencies, use the npm install command, which installs everything listed
in package.json, ensuring all team members work with the same setup,
reducing the risk of conflicts or errors.

StevenCodeCraft.com
Page 326
StevenCodeCraft.com

123) How does semantic versioning help developers manage dependencies


and ensure stability in Node.js projects?
Semantic versioning helps developers manage dependencies by clearly
indicating the impact of updates through version numbers. It uses three
components-major, minor, and patch- to signal the type of changes made.
Major versions indicate breaking changes, minor versions add new features
without breaking existing functionality, and patch versions include backward-
compatible bug fixes. This system ensures that developers can update
packages safely, maintaining stability while still receiving important updates
and fixes, thereby reducing the risk of unexpected issues in their projects.

124) What are the implications of using caret (^) and tilde (~) operators in
versioning when managing package updates in Node.js?
Using the caret (^) in versioning allows updates to minor and patch versions,
while keeping the major version stable, ensuring backward compatibility. The
tilde (~) operator restricts updates to only patch versions, keeping both
major and minor versions fixed. These operators help manage package
updates by allowing controlled updates-caret offers more flexibility by
permitting minor updates, while tilde provides stricter control, reducing the
risk of breaking changes. This helps maintain stability while still applying
necessary updates.

StevenCodeCraft.com
Page 327
StevenCodeCraft.com

125) What is it important to regularly check the versions of installed Node.js


packages in a project?
Regularly checking the versions of installed Node.js package is important to
ensure compatibility and stability in your project. It helps you identify
potential issues caused by outdated or conflicting dependencies and allows
you to take advantage of security patches, bug fixes, and new features.
Keeping track of package versions also aids in debugging, as knowing the
exact versions can help pinpoint problems related to specific updates or
changes in the package ecosystem. This practice contributes to maintaining a
reliable and secure application.

126) How can using npm commands help streamline the process of
managing and verifying package versions in a Node.js project?
Using npm commands streamlines managing and verifying package versions
by providing quick and detailed insights into the installed dependencies.
Commands like “npm list” display all installed packages and their versions,
while options like “--depth=0” focus on top-level dependencies, making it
easier to track and manage them. This approach helps ensure your project
remains stable and up-to-date by allowing you to easily monitor and update
packages as needed, all from the command line.

StevenCodeCraft.com
Page 328
StevenCodeCraft.com

127) Why is it important to regularly check the dependencies and versions of


npm packages in a Node.js project, and how can this practice impact the
stability and compatibility of the application?
Regularly checking the dependencies and versions of npm packages in a
Node.js project is crucial for maintaining stability and compatibility. It ensures
that all packages work well together, preventing conflicts that could arise
from incompatible versions. This practice also helps identify when updates or
security patches are needed, reducing the risk of bugs or vulnerabilities. By
staying on top of package versions, developers can keep the application
reliable and ensure it runs smoothly across different environments.

128) Why might a developer need to install a specific version of an npm


package, and how does this practice contribute to project stability?
A developer might need to install a specific version of an npm package to
ensure compatibility with other dependencies, maintain access to certain
features, or avoid bugs introduced in newer versions. By specifying and
controlling the exact version, the developer can stabilize the project,
ensuring that updates or changes in the package ecosystem don’t
unexpectedly break the application or alter its behavior. This practice helps
maintain a consistent and reliable development environment across different
setups.

StevenCodeCraft.com
Page 329
StevenCodeCraft.com

129) What are the benefits of verifying installed npm package versions after
installation, and how does it ensure consistency in a Node.js project?
Verifying installed npm package versions after installation ensures that the
correct versions are in place, which is crucial for maintaining consistency
across different development environments. This practice helps prevent
unexpected issues caused by version mismatches, ensures compatibility with
other dependencies, and supports stable project behavior. By confirming
that the specified versions are installed, you can confidently manage
dependencies and avoid problems that might arise from automatic updates
or version conflicts.

130) What are the best practices for safely updating npm packages,
particularly when handling major version changes?
Best practices for safely updating npm packages include: Review Updates:
Use tools like ‘npm updated’ or ‘npm-check-update’ to identify and review
available updates. Test Thoroughly: After updating, especially when updating
to major versions, thoroughly test your application to catch any breaking
changes. Use Version Control: Always commit changes to ‘package.json’ and
‘package-lock.json’ to ensure consistency across environments. Update
Incrementally: Where possible, update packages incrementally rather than all
at once to isolate issues more easily. These steps help maintain stability and
minimize the risk of disruptions.

StevenCodeCraft.com
Page 330
StevenCodeCraft.com

131) Why is it important to differentiate between production and


development dependencies in a Node.js project?
Differentiating between production and development dependencies in a
Node.js project is crucial because it ensures that only the essential packages
needed for the application to run are included in the production
environment. This reduces the deployment size, improves performance, and
minimizes security risks by excluding unnecessary tools used only during
development, such as testing frameworks or build tools. Proper
categorization also helps maintain a clean, organized project structure,
making it easier to manage and understand.

132) How does proper management of dependencies impact the efficiency


and security of a Node.js application in production?
Proper management of dependencies in a Node.js application impacts
efficiency by ensuring that only necessary packages are included in the
production environment, reducing the application’s size and improving
performance. It also enhances security by minimizing the attack surface, as
fewer packages mean fewer potential vulnerabilities. By installing only
production dependencies, you avoid including unnecessary tools that could
introduce risks, leading to a leaner, more secure, and optimized application
in production.

StevenCodeCraft.com
Page 331
StevenCodeCraft.com

133) Why is it important to regularly remove unused npm packages from a


Node.js project, and how does this practice contribute to project
maintenance?
Regularly removing unused npm packages from a Node.js project is
important because it helps keep the project lean and efficient. It reduces the
size of the node_modules directory, improving load times and minimizing
potential security risks from outdated or unnecessary packages. This practice
also simplifies project maintenance by reducing clutter, preventing
dependency conflicts, and ensuring that only essential packages are included
in the application. Keeping the codebase clean and manageable enhances the
overall health and stability of the project.

134) What steps should developers take before and after uninstalling npm
packages to ensure their Node.js project remains functional and up-to-date?
Before uninstalling npm packages, developers should verify that the package
is not in use by checking where and how it is utilized in the project. After
uninstalling, they should thoroughly test the application to ensure it still
functions correctly without the removed package. It’s also important to
update the version control system by committing the changes to
‘package.json’ and the ‘node_modules’ directory. This process ensures that
the project remains functional, clean, and up-to-date.

StevenCodeCraft.com
Page 332
StevenCodeCraft.com

135) What are the benefits and potential risks of installing npm packages
globally instead of locally in a Node.js development environment?
Benefits of Global Installation: Global npm packages are accessible from any
directory on your system, making them ideal for command-line tools or
utilities used across multiple projects, such as project scaffolding tools or
linters. Potential Risks: Overusing global installations can lead to version
conflicts between projects, as different projects may require different
versions of the same tool. It can also complicate dependency management
and make it harder to maintain consistent environments across different
machines or for different team members.

136) How can developers effectively manage and maintain global npm
packages to ensure a consistent and secure development environment?
Developers can effectively manage and maintain global npm packages by:
Minimizing Global Installations: Only install packages globally when necessary
to avoid version conflicts and simplify dependency management. Regular
Updates: Frequently check for outdated global packages using ‘npm outdated
-g’ and update them to ensure security and access to the latest features.
Documenting Requirements: Clearly document any global tools needed for a
project, ensuring that all team members can set up consistent and
compatible environments. These practices help maintain a stable and secure
development environment.

StevenCodeCraft.com
Page 333
StevenCodeCraft.com

137) What are the key steps involved in creating, publishing, and maintaining
an npm package for the Node.js community?
The key steps to create, publish, and maintain an npm package involve:
Setting Up: Create a directory, initialize it with ‘npm init’ and write your
package’s code. Publishing: Create or log in to an npm account, ensure your
package name is unique and publish it using ‘npm publish’ Verifying: Test the
package by installing it in a different project to ensure it works as expected
Maintaining: Update your package as needed, increment the version number,
and republish to keep it current and useful for others.

138) Why is it important to verify and test your npm package after publishing,
and how can this impact its usability by other developers?
Verifying and testing your npm package after publishing is crucial to ensure it
works as intended in different environments. This step helps identify and fix
any issues that might arise, ensuring that the package is reliable and user-
friendly for other developers. If a package is not properly tested, it can lead to
errors and frustration for users, reducing its adoption and usability.
Thorough testing enhances the package’s credibility and usefulness within
the developer community.

StevenCodeCraft.com
Page 334
StevenCodeCraft.com

139) Why is semantic versioning important when updating an npm package,


and how does it help communicate changes to users?
Semantic versioning is important when updating an npm package because it
clearly communicates the type of changes made, helping users understand
the impact on their projects. By using version numbers to indicate major
(breaking changes), minor (new features), or patch (bug fixes) updates,
developers can manage expectations and allow users to update
dependencies safely. This practice ensures that users can confidently
upgrade to newer versions without unexpectedly breaking their applications.

140) What best practices should developers follow when updating and
republishing an npm package to ensure its reliability and usability?
When updating and republishing an npm package, developers should follow
these best practices: Use Semantic Versioning: Clearly communicate the type
of changes (major, minor, or patch) to users. Thorough Testing: Ensure all
new changes are well-tested to prevent introducing bugs or breaking existing
functionality. Update Documentation: Revise README files and other
documentation to reflect the latest changes and new features. Commit and
Tag: If using version control, commit the changes and tag the new version
before publishing. These practices help maintain the package’s reliability and
usability.

StevenCodeCraft.com
Page 335
09

Asynchronous
JavaScript

StevenCodeCraft.com
Page 336
StevenCodeCraft.com

Synchronous vs Asynchronous Code


Setting Up the Project

Create a new directory for the project


$ mkdir async-demo
$ cd async-demo

Initialize a new Node.js project


$ npm init --yes

Open the project in your editor


$ code .

Create the main JavaScript file


$ touch index.js

Writing Synchronous Code


In your index.js file, add the following console statements. (view code on next
page) This code is an example of synchronous (or blocking) programming.
Here, the program waits for the first console.log to complete before moving
on to the second.

StevenCodeCraft.com
Page 337
StevenCodeCraft.com

Introducing Asynchronous Programming


In contrast, asynchronous programming allows the program to move on to
other tasks before the previous ones have completed. Modify your index.js
file as follows:

Running the Program


Execute the program with:
$ node index.js

You will see the following output:

before
after
Reading a grocery item from a database...

StevenCodeCraft.com
Page 338
StevenCodeCraft.com

Notice that before and after are logged immediately, while the message from
setTimeout appears after a 2-second delay. This demonstrates that
setTimeout schedules the task to be performed later, without blocking the
execution of subsequent code.

Understanding Asynchronous Programming


Asynchronous programming is not the same as concurrent or
multithreaded programming. In Node.js, asynchronous code runs on a
single thread.
It’s crucial because operations involving disk or network access in
Node.js are handled asynchronously, allowing the program to remain
responsive.

Asynchronous programming helps ensure that your application can handle


multiple operations efficiently, without getting stuck waiting for one task to
complete before starting another.

Patterns for Dealing with Asynchronous Code


Understanding Asynchronous Programming in JavaScript

StevenCodeCraft.com
Page 339
StevenCodeCraft.com

Example of Incorrect Synchronous Code

When you run this code with node index.js, you'll see:

before
undefined
after
Reading a grocery item from a database...

The function getGroceryItem is intended to simulate fetching an item from a


database. However, due to the setTimeout function, it does not return the
grocery item immediately. Instead, it schedules the operation to occur after 2
seconds. The console.log(groceryItem) statement executes before the
grocery item data is available, resulting in undefined.

StevenCodeCraft.com
Page 340
StevenCodeCraft.com

Explanation
The key reason this does not work as intended is because setTimeout is
asynchronous. The function does not wait for 2 seconds to return the
grocery item data; it returns immediately, and the code continues executing.

Correcting the Approach


When dealing with asynchronous operations like database calls, the result is
not immediately available. Here are three common patterns to handle
asynchronous code in JavaScript:

1. Callbacks
2. Promises
3. Async/Await

Using Callbacks

StevenCodeCraft.com
Page 341
StevenCodeCraft.com

Using Promises

Using Async/Await

StevenCodeCraft.com
Page 342
StevenCodeCraft.com

Summary
Callbacks
Functions passed as arguments to be executed once an asynchronous
operation is complete.

Promises
Objects representing the eventual completion or failure of an asynchronous
operation.

Async/Await
Syntactic sugar over promises, making asynchronous code look
synchronous.

Understanding these patterns is crucial for handling asynchronous


operations, such as database calls, in JavaScript.

StevenCodeCraft.com
Page 343
StevenCodeCraft.com

Callbacks
Understanding Asynchronous Programming in JavaScript

Example of Incorrect Synchronous Code

This code attempts to get a grocery list from a database, but it doesn't work
as intended because the function getGroceryList uses setTimeout, making it
asynchronous. The console.log(groceryList) statement executes before the
grocery list data is available, resulting in undefined.

StevenCodeCraft.com
Page 344
StevenCodeCraft.com

Explanation
A callback is a function that is passed as an argument to another function, to
be executed once an asynchronous operation is complete.

Using Callbacks Correctly


Here is an updated version of the code using a callback to handle the
asynchronous operation:

StevenCodeCraft.com
Page 345
StevenCodeCraft.com

Accessing Nested Asynchronous Data


To handle multiple asynchronous operations, such as fetching user
repositories after fetching the user data, you can nest callbacks:

StevenCodeCraft.com
Page 346
StevenCodeCraft.com

Running this code with node index.js will show


the following sequence of operations:
before
after
Fetching grocery list from the database...
Grocery List: { id: 1, items: [ 'apples', 'bananas', 'bread' ] }
Fetching items from the grocery list...
Items: [ 'apples', 'bananas', 'bread' ]

Summary
Callbacks
Functions passed as arguments to be executed once an asynchronous
operation is complete. Properly handling asynchronous code is crucial when
dealing with operations like database access, which may take some time to
complete.

StevenCodeCraft.com
Page 347
StevenCodeCraft.com

Callback Hell
Understanding Asynchronous Programming in JavaScript

In the previous example, we used nested callbacks to handle asynchronous


operations:

In this scenario, each callback depends on the completion of the previous


one, resulting in a nested structure. This pattern is often referred to as
"callback hell" or the "Christmas tree problem" because of its shape and
complexity.

StevenCodeCraft.com
Page 348
StevenCodeCraft.com

Comparing Synchronous Code


If all functions were synchronous, the code would be simpler and more linear:

console.log('Before');
const list = getGroceryList(1);
const items = getGroceryItems(list.id);
const availability = checkItemAvailability(items[0]);
console.log('After');

However, synchronous code blocks the execution of other operations until


the current one finishes, which is not ideal for I/O-bound tasks like database
queries.

Callback Hell
Using callbacks for asynchronous code can become difficult to manage and
read:

StevenCodeCraft.com
Page 349
StevenCodeCraft.com

This pattern, known as "callback hell," makes code harder to understand and
maintain.

A Simple Solution
To avoid callback hell, we can use Promises and async/await syntax.

Using Promises
Promises provide a cleaner way to handle asynchronous operations. (view
example on the next page)

StevenCodeCraft.com
Page 350
StevenCodeCraft.com

StevenCodeCraft.com
Page 351
StevenCodeCraft.com

Using Async/Await
The async/await syntax, built on top of Promises, further simplifies the code:

StevenCodeCraft.com
Page 352
StevenCodeCraft.com

Summary
Callbacks can lead to nested structures that are difficult to manage.
Promises and async/await provide cleaner, more readable ways to
handle asynchronous code.

Named Functions
Improving Callback Structure in Asynchronous Programming

Using Named Functions to Simplify Callbacks


When dealing with nested callbacks, you can replace anonymous functions
with named functions to flatten and simplify the structure of your code. This
approach makes your code more readable and easier to manage.

Example of Named Functions


Here's how you can refactor nested callbacks using named functions (view on
next page)

StevenCodeCraft.com
Page 353
StevenCodeCraft.com

StevenCodeCraft.com
Page 354
StevenCodeCraft.com

Explanation
Named Functions
By defining named functions (handleRepositories, handleCommits,
displayCommits), you avoid deep nesting and improve the readability of your
code.

Passing References
Note that we are passing references to these functions (handleRepositories,
handleCommits, displayCommits) instead of calling them directly.

Advantages
Readability: The code is easier to read and understand.
Reusability: Named functions can be reused elsewhere in the code if
needed.

Limitations
While using named functions helps, it’s not the most efficient way to handle
asynchronous code. For a better approach, consider using Promises.

StevenCodeCraft.com
Page 355
StevenCodeCraft.com

Using Promises
Promises provide a cleaner, more manageable way to handle asynchronous
operations compared to nested callbacks. Here’s how you can convert the
above example to use promises:

StevenCodeCraft.com
Page 356
StevenCodeCraft.com

Using promises makes the code more straightforward and easier to handle,
especially as the complexity of asynchronous operations increases.

Promise
Understanding Promises in JavaScript

Introduction to Promises

JavaScript promises are a powerful tool for managing asynchronous


operations. A promise is an object that represents the eventual result of an
asynchronous operation. It can be in one of three states:

1) Pending
The initial state, when the promise is still waiting for the asynchronous
operation to complete.

2) Fulfilled
The operation completed successfully, and the promise has a value.

3) Rejected
The operation failed, and the promise has an error.

StevenCodeCraft.com
Page 357
StevenCodeCraft.com

Creating a Promise
To create a promise, you can use the Promise constructor, which takes a
function with two parameters: resolve and reject.

These parameters are functions used to indicate the completion of the


asynchronous operation, either successfully (resolve) or unsuccessfully
(reject).

Consuming a Promise
Once you have a promise, you can use the then and catch methods to handle
the result or error:

StevenCodeCraft.com
Page 358
StevenCodeCraft.com

Example: Handling Errors


Here’s an example that simulates an asynchronous operation that fails:

Summary
A promise starts in the pending state.
It transitions to fulfilled if the asynchronous operation completes
successfully.
It transitions to rejected if the operation fails.
Use .then to handle the successful result.
Use .catch to handle errors.

Best Practice
Whenever you have an asynchronous function that takes a callback, consider
modifying it to return a promise for better readability and maintainability.
This approach helps avoid "callback hell" and makes the code easier to follow.

StevenCodeCraft.com
Page 359
StevenCodeCraft.com

Replacing Callbacks with Promises


Resolving Callback Hell by Using Promises
Example of Nested Callbacks
Nested callbacks, also known as "callback hell," can make your code difficult
to read and maintain. Here's an example:

StevenCodeCraft.com
Page 360
StevenCodeCraft.com

This code demonstrates a series of asynchronous operations that depend on


each other. Each nested callback increases the indentation level, making the
code harder to follow.

Converting Callbacks to Promises


To improve the readability and maintainability of the code, you can modify
the asynchronous functions to return promises. Here’s how to do it:

StevenCodeCraft.com
Page 361
StevenCodeCraft.com

Using Promises
With the functions returning promises, you can chain them using then and
catch methods:

This approach makes the code more linear and easier to read, eliminating the
deep nesting associated with callback hell.

Summary
Promises: Objects representing the eventual completion (or failure) of
an asynchronous operation.
States: Promises can be in one of three states: pending, fulfilled, or
rejected
Methods: Use .then to handle resolved values and .catch to handle
errors.
Converting callback-based functions to return promises can significantly
improve code readability and maintainability.

StevenCodeCraft.com
Page 362
StevenCodeCraft.com

Consuming Promises
Improving Callback Structure with Promises
Example of Nested Callbacks
Using nested callbacks can make code difficult to manage and read. Here's an
example:

StevenCodeCraft.com
Page 363
StevenCodeCraft.com

Using Promises
First, ensure each function works correctly:

Next, chain the promises:

StevenCodeCraft.com
Page 364
StevenCodeCraft.com

Explanation
Promises: Represent the eventual result of an asynchronous operation.
Methods: Use .then for handling resolved values and .catch for errors.
Error Handling: Always include a .catch method to handle any errors
that might occur.

Using promises simplifies the code, making it more readable and easier to
maintain. This method eliminates the deep nesting associated with callback
hell.

Running Promises in Parallel


Sometimes, you need to run several asynchronous operations
simultaneously and perform an action once all of them have completed. For
example, you might call different APIs like Facebook and Twitter, and once
both calls finish, you return the results to the client.

Running Multiple Asynchronous Operations


Here’s how you can simulate calling the Facebook and Twitter (X) APIs using
promises:

StevenCodeCraft.com
Page 365
StevenCodeCraft.com

This code kicks off two asynchronous operations and uses Promise.all() to
wait until both promises are resolved. The then method is called with the
results when both operations complete.

StevenCodeCraft.com
Page 366
StevenCodeCraft.com

Handling Promise Rejections


If one of the promises fails, Promise.all() will reject, and the catch method will
handle the error:

In this example, if p1 fails, the entire Promise.all will reject, and the error will
be logged.

StevenCodeCraft.com
Page 367
StevenCodeCraft.com

Using Promise.race()
If you need to proceed as soon as one of the promises is fulfilled or rejected,
you can use Promise.race():

With Promise.race(), the first promise to resolve or reject will determine the
outcome. If it resolves first, the then method will be called with the result of
the first fulfilled promise. If it rejects first, the catch method will handle the
error.

Summary
Promise.all()
Waits for all promises to resolve. If any promise rejects, the entire promise is
rejected.

Promise.race()
Resolves or rejects as soon as one promise resolves or rejects.

These methods help manage multiple asynchronous operations effectively,


ensuring that your code is cleaner and more maintainable.

StevenCodeCraft.com
Page 368
StevenCodeCraft.com

Async and Await


Simplifying Asynchronous Code with Async and
Await
Introduction to Async and Await
JavaScript's async and await features provide a more readable and
straightforward way to write asynchronous code, resembling synchronous
code. Here's an example of using promises:

StevenCodeCraft.com
Page 369
StevenCodeCraft.com

Rewriting with Async and Await


Using async and await, you can rewrite the above code to make it cleaner and
easier to understand:

Explanation
await
This keyword allows you to wait for a promise to resolve and get its
result. You can use it only inside functions marked with async.

async
This keyword is used to declare that a function is asynchronous. It ensures
that the function returns a promise.

StevenCodeCraft.com
Page 370
StevenCodeCraft.com

When using await, the JavaScript engine pauses the execution of the function
until the promise settles. This makes the asynchronous code look like
synchronous code, which is easier to read and understand.

Handling Errors with Async and Await


In the promise-based approach, .catch is used to handle errors. With async
and await, you use a try and catch block:

Summary
Async and Await
Provides a cleaner way to write asynchronous code that looks like
synchronous code.

Error Handling
Use try and catch blocks to handle errors when using async and await

StevenCodeCraft.com
Page 371
StevenCodeCraft.com

Using async and await can make your asynchronous JavaScript code much
more readable and easier to maintain. This syntactic sugar over promises
simplifies the code structure significantly.

Summary
Handling Asynchronous JavaScript
Synchronous vs Asynchronous Code
Understanding the difference between synchronous and asynchronous code
is fundamental in JavaScript. Synchronous code executes sequentially,
blocking further execution until the current task is completed. In contrast,
asynchronous code allows other operations to continue while waiting for an
asynchronous task to complete, improving performance and responsiveness.

Patterns for Dealing with Asynchronous Code


JavaScript offers several patterns to manage asynchronous operations:

Callbacks
Functions passed as arguments to other functions to be invoked once an
asynchronous operation is complete.

StevenCodeCraft.com
Page 372
StevenCodeCraft.com

Promises
Objects representing the eventual completion or failure of an asynchronous
operation, providing a cleaner way to handle asynchronous logic.

Async and Await


Syntax that makes asynchronous code appear synchronous, enhancing
readability.

Callbacks
Callbacks were the original method for handling asynchronous code in
JavaScript. They involve passing a function to another function to be executed
after an operation completes.

Callback Hell
Nested callbacks can lead to "callback hell," a situation where code becomes
deeply nested and difficult to manage. This pattern complicates both reading
and maintaining the code.

Named Functions
To mitigate callback hell, named functions can be used to flatten the
structure. Instead of nesting anonymous functions, you define separate
named functions and pass them as callbacks.

StevenCodeCraft.com
Page 373
StevenCodeCraft.com

Promises
Promises provide a more manageable alternative to callbacks. A promise
represents an operation that hasn't completed yet but is expected in the
future. Promises have three states: pending, fulfilled, and rejected.

Replacing Callbacks with Promises


Promises help avoid the complexity of nested callbacks. By returning
promises from functions, you can chain asynchronous operations more
straightforwardly using .then() for resolved promises and .catch() for errors.

Consuming Promises
Consuming promises involves using .then() to handle resolved values and
.catch() to handle errors. This approach results in more readable and
maintainable code.

Creating Settled Promises


JavaScript provides Promise.resolve() and Promise.reject() methods to create
promises that are already settled, either fulfilled or rejected. This is
particularly useful in testing scenarios.

StevenCodeCraft.com
Page 374
StevenCodeCraft.com

Running Promises in Parallel


Using Promise.all(), multiple promises can be executed in parallel, and you
can perform an action when all of them are resolved. If any promise is
rejected, the entire Promise.all() is rejected.

Async and Await


Async and await keywords provide a way to write asynchronous code that
looks synchronous. Functions declared with async return a promise, and
await pauses the execution of the function until the promise is resolved or
rejected, making the code easier to read and maintain. Error handling is done
using try and catch blocks. By understanding and applying these patterns,
you can effectively manage asynchronous operations in JavaScript, leading to
more efficient and maintainable code.

StevenCodeCraft.com
Page 375
StevenCodeCraft.com

Active Recall Study Questions


141) How does asynchronous programming improve the efficiency and
responsiveness of a Node.js application, especially in handling I/O
operations?
142) What are the key differences between synchronous and asynchronous
programming in the context of Node.js, and why is it important for
developers to understand these differences?
143) What are the main methods for handling asynchronous operations in
JavaScript, and why is it important to understand them?
144) How does the asynchronous nature of JavaScript impact the execution
order of code, and what are the strategies to manage this effectively?
145) Why is it important to handle asynchronous operations properly in
JavaScript, particularly when dealing with tasks like database access?
146) What challenges can arise when using callbacks to manage multiple
asynchronous operations, and how can these be effectively addressed?
147) What are the benefits of using Promises and async/await over callbacks
in managing asynchronous operations in JavaScript?
148) How does the use of Promises and async/await help prevent issues like
‘callback hell’ in JavaScript programming?
149) How can refactoring nested callbacks into named functions improve the
readability and maintainability of asynchronous code in JavaScript?
150) What are the limitations of using named functions for handling
asynchronous operations and how do Promises provide a more efficient
solution?

StevenCodeCraft.com
Page 376
StevenCodeCraft.com

151) How do promises improve the management of asynchronous


operations in JavaScript, particularly compared to traditional callback
methods?
152) Why is it beneficial to modify asynchronous functions that use callbacks
to return promises instead, and how does this practice enhance code
readability and maintainability?
153) How do promises help in resolving the challenges of “callback hell” in
asynchronous JavaScript programming?
154) What are the key benefits of converting callback-based functions to
return promises, particularly in terms of code readability and maintainability?
155) How can creating resolved and rejected promises with methods like
Promise.resolve() and Promise.reject() be useful in testing and simulating
asynchronous operations?
156) What are the advantages of using Promise.all() and Promise.race() for
handling multiple asynchronous operations in JavaScript?
157) How can managing multiple asynchronous operations with promises
enhance the efficiency and reliability of JavaScript applications?
158) How do the async and await keywords simplify the process of writing and
understanding asynchronous code in JavaScript?
159) What are the benefits of using async and await for error handling in
asynchronous JavaScript code compared to traditional promise-based
methods?

StevenCodeCraft.com
Page 377
StevenCodeCraft.com

Active Recall Answers


141) How does asynchronous programming improve the efficiency and
responsiveness of a Node.js application, especially in handling I/O
operations?
Asynchronous programming improves the efficiency and responsiveness of a
Node.js application by allowing the program to continue executing other
tasks while waiting for I/O operations, such as database calls or file access, to
complete. This non-blocking approach ensures that the application remains
responsive and can handle multiple operations simultaneously, rather than
being held up by slow operations. As a result, the application can serve more
users and manage more tasks without delays or bottlenecks, leading to
better performance overall.

142) What are the key differences between synchronous and asynchronous
programming in the context of Node.js, and why is it important for
developers to understand these differences?
Synchronous programming in Node.js executes tasks one after the other,
blocking the execution of subsequent code until the current task is finished.
Asynchronous programming, on the other hand, allows the program to
initialize tasks (like I/O operations) and move on to other tasks without
waiting for the previous ones to complete. Understanding these differences
is crucial because asynchronous programming helps prevent bottlenecks,
making applications more efficient and responsive by not allowing a single
slow operation to halt the entire program.

StevenCodeCraft.com
Page 378
StevenCodeCraft.com

143) What are the main methods for handling asynchronous operations in
JavaScript, and why is it important to understand them?
The main methods for handling asynchronous operations in JavaScript are
callbacks, promises, and async/await. Callbacks involve passing a function to
be executed after an asynchronous operation completes. Promises
represent the eventual completion or failure of an asynchronous task and
provide methods like .then() and .catch() for handling results. Async/Await is
built on promises, allowing asynchronous code to be written in a more
readable, synchronous-like style. Understanding these methods are crucial
for managing tasks like database calls, ensuring efficient and correct code
execution.

144) How does the asynchronous nature of JavaScript impact the execution
order of code, and what are the strategies to manage this effectively?
The asynchronous nature of JavaScript means that code doesn’t always
execute in the order it’s written; some task (like I/O operations) run in the
background while the rest of the code continues. This can lead to unexpected
results if not managed properly. To handle this, developers use strategies like
callbacks, promises, and async/await to control the flow of asynchronous
operations, ensuring that tasks complete in the desired order and that
results are handled correctly.

StevenCodeCraft.com
Page 379
StevenCodeCraft.com

145) Why is it important to handle asynchronous operations properly in


JavaScript, particularly when dealing with tasks like database access?
Handling asynchronous operations properly in JavaScript is crucial because it
ensures that tasks like database access, which takes time to complete, do not
block the execution of other code. This allows your application to remain
responsive, efficiently processing multiple tasks without waiting for each to
finish sequentially. Proper management of asynchronous operations, such as
using callbacks, promises, or async/await prevents issues like incomplete
data retrieval or unexpected behavior ensuring that the application functions
correctly and smoothly.

146) What challenges can arise when using callbacks to manage multiple
asynchronous operations, and how can these be effectively addressed?
When using callbacks to manage multiple asynchronous operations,
challenges like “callback hell” can arise, where nested callbacks make the code
difficult to read and maintain. This complexity increases the risk or errors
and makes debugging harder. To address these issues, developers can use
techniques such as modularizing callback functions, or better yet, use
promises or async/await syntax, which allow for more readable and
maintainable code by flattening the structure and handling errors more
effectively.

StevenCodeCraft.com
Page 380
StevenCodeCraft.com

147) What are the benefits of using Promises and async/await over callbacks
in managing asynchronous operations in JavaScript?
Using Promises and async/await in JavaScript offers several benefits over
callbacks: Readability: Promises and async/await result in cleaner, more linear
code that is easier to read and maintain, avoiding the nested structure of
callbacks (‘callback hell’). Error Handling: Promises provide better error
handling through .catch() and try/catch blocks with async/await, making it
easier to manage asynchronous errors. Control Flow: Promises and
async/await allows for more intuitive control flow, helping developers
manage complex asynchronous operations more effectively.

148) How does the use of Promises and async/await help prevent issues like
‘callback hell’ in JavaScript programming?
The use of Promises and async/await in JavaScript helps prevent ‘callback hell’
by making asynchronous code more linear and easier to follow. Promises
allow chaining of asynchronous operations with .then() instead of deeply
nesting callbacks, while async/await further simplifies the syntax making the
code look and behave more like synchronous code. This reduces complexity,
improves readability, and makes error handling more straightforward,
resulting in cleaner and more maintainable code.

StevenCodeCraft.com
Page 381
StevenCodeCraft.com

149) How can refactoring nested callbacks into named functions improve the
readability and maintainability of asynchronous code in JavaScript?
Refactoring nested callbacks into named functions improves the readability
and maintainability of asynchronous code by reducing complexity and
avoiding deeply nested structures, known as ‘callback hell’. Named functions
make the code more organized, easier to follow, and allow for better reuse of
code. This approach simplifies debugging and understanding the flow of
asynchronous operations, making it easier to manage and maintain the
codebase as the application grows in complexity.

150) What are the limitations of using named functions for handling
asynchronous operations and how do Promises provide a more efficient
solution?
The limitations of using named functions for handling asynchronous
operations include increased complexity when dealing with multiple
asynchronous tasks and a tendency to still create deeply nested structures.
While named functions improve readability, they don’t fully solve the issue of
‘callback hell’. Promises provide a more efficient solution by allowing for a
more linear and manageable flow of asynchronous tasks through chaining,
which improves readability and simplifies error handling, leading to cleaner
and more maintainable code.

StevenCodeCraft.com
Page 382
StevenCodeCraft.com

151) How do promises improve the management of asynchronous


operations in JavaScript, particularly compared to traditional callback
methods?
Promises improve the management of asynchronous operations in JavaScript
by providing a more structured and readable way to handle tasks compared
to traditional callbacks. They allow you to chain operations like .then() for
success and .catch for errors, avoiding “callback hell” and making the code
easier to follow. Promises also offer better error handling and simplify the
process of managing multiple asynchronous operations, leading to cleaner
and more maintainable code.

152) Why is it beneficial to modify asynchronous functions that use callbacks


to return promises instead, and how does this practice enhance code
readability and maintainability?
Modifying asynchronous functions to return promises instead of using
callbacks is beneficial because it simplifies the code structure, making it more
readable and easier to maintain. Promises allow for changing operations and
handling errors more cleanly with .then() and .catch() methods, reducing the
complexity associated with nested callbacks known as “callback hell”. This
approach leads to more organized and modular code, which is easier to
debug and scale as your application grows.

StevenCodeCraft.com
Page 383
StevenCodeCraft.com

153) How do promises help in resolving the challenges of “callback hell” in


asynchronous JavaScript programming?
Promises help resolve the challenges of “callback hell” in asynchronous
JavaScript programming by allowing you to chain asynchronous operations in
a more linear and readable manner. Instead of deeply nested callbacks,
promises use .then() and .catch() methods to handle success and errors,
respectively. This flattens the code structure, making it easier to understand,
maintain, and debug. By converting callback-based functions to return
promises, developers can write cleaner, more manageable code while
avoiding the complexity and pitfalls of nested callbacks.

154) What are the key benefits of converting callback-based functions to


return promises, particularly in terms of code readability and maintainability?
Converting callback-based functions to return promises offers key benefits in
terms of code readability and maintainability. Promises allow for chaining
operations using .then() and .catch() which results in a flatter, more linear
code structure, making it easier to read and understand. This approach
eliminates the deeply nested, hard-to-manage code typical of callbacks,
known as “callback hell.” Additionally, promises provide a consistent way to
handle errors, further enhancing the maintainability of the codebase.

StevenCodeCraft.com
Page 384
StevenCodeCraft.com

155) How can creating resolved and rejected promises with methods like
Promise.resolve() and Promise.reject() be useful in testing and simulating
asynchronous operations?
Creating resolved and rejected promises with methods like ‘Promise.resolve()’
and ‘Promise.reject()’ is useful in testing and simulating asynchronous
operations because they allow developers to immediately generate success
or failure outcomes without needing to perform actual asynchronous tasks.
This capability is particularly valuable in unit testing, where you may want to
test how your code handles resolved or rejected promises, such as
simulating successful API responses or error conditions, ensuring your code
behaves correctly in different scenarios.

156) What are the advantages of using Promise.all() and Promise.race() for
handling multiple asynchronous operations in JavaScript?
Promise.all() allows you to run multiple asynchronous operations in parallel
and waits for all of them to complete before proceeding. It’s useful when you
need all tasks to finish successfully before taking the next step. If any promise
fails, the entire operation fails. Promise.race() resolves or rejects as soon as
one of the promises completes, making it useful when you want to proceed
based on whichever task finishes first. This approach can help optimize
performance when only the quickest result is needed.

StevenCodeCraft.com
Page 385
StevenCodeCraft.com

157) How can managing multiple asynchronous operations with promises


enhance the efficiency and reliability of JavaScript applications?
Managing multiple asynchronous operations with promises enhances the
efficiency and reliability of JavaScript applications by allowing tsks to run
concurrently reducing wait times. Promise.all() ensures that all tasks
complete successfully before proceeding, making the application reliable
when multiple results are needed. Promise.race() allows the application to
respond faster by acting on the first completed task, improving performance
in scenarios where the quickest result is prioritized. These tools help
structure and control complex asynchronous workflows, leading to cleaner,
more maintainable code.

158) How do the async and await keywords simplify the process of writing and
understanding asynchronous code in JavaScript?
The async and await keywords simplify writing asynchronous code in
JavaScript by making it look and behave like synchronous code. async marks a
function as asynchronous, automatically returning a promise, while await
pauses the function’s execution until the promise resolves. This eliminates
the need for complex promise chains and nesting, resulting in more
readable, maintainable, and easier-to-understand code, especially when
dealing with multiple asynchronous operations.

StevenCodeCraft.com
Page 386
StevenCodeCraft.com

159) What are the benefits of using async and await for error handling in
asynchronous JavaScript code compared to traditional promise-based
methods?
Using async and await for error handling in JavaScript provides a more
straightforward and readable approach compared to traditional promise-
based methods. With async/await, you can handle errors using try and catch
blocks, which are familiar from synchronous code, making it easier to
understand and manage. This approach avoids the need for chaining .catch()
methods, resulting in cleaner code that is easier to debug and maintain,
especially in complex asynchronous workflows.

StevenCodeCraft.com
Page 387
10

JavaScript
Interview
Essentials

StevenCodeCraft.com
Page 388
StevenCodeCraft.com

Introduction
JavaScript is the primary programming language used for web development.
As a general-purpose language, it has several unique properties that are
essential for front-end engineers to understand. JavaScript on the frontend is
typically executed within a user's browser, which sets it apart from other
programming languages.

This execution environment provides a unique set of features, such as the


ability to manipulate the Document Object Model (DOM), but it also comes
with limitations that vary across different browsers.

One key characteristic of JavaScript is that it operates in a single-threaded


environment. To handle multiple tasks concurrently, the browser uses an
event loop, which simulates concurrency. Understanding how the event loop
works is crucial for grasping asynchronous programming in JavaScript and
effectively using browser APIs.

StevenCodeCraft.com
Page 389
StevenCodeCraft.com

This course covers the basics of JavaScript, including:

Closures: Used to manage the scope of variables and functions.


DOM Manipulation: Interacting with and modifying the document to
create dynamic and interactive web pages.
Asynchronous Programming: Understanding the event loop and how to
work with promises, async/await, and other asynchronous patterns.

A thorough knowledge of JavaScript is arguably one of the most important


skills for passing front-end interviews. You are likely to be asked to implement
functions or classes in JavaScript, and these questions will test your
understanding of core concepts. Many problems have simple and elegant
solutions that require a deep understanding of how JavaScript works.

Additionally, you may be asked to add functionality to a webpage through


DOM manipulation, which requires practical knowledge of JavaScript and how
to interact with HTML documents.

By mastering these topics, you will be well-prepared to handle the challenges


of front-end development and succeed in technical interviews.

StevenCodeCraft.com
Page 390
StevenCodeCraft.com

JavaScript Basics
In this lesson, we'll go over some of the major paradigms of JavaScript.

Event-driven
JavaScript allows functions to respond to events. For example, when a user
clicks on an element or scrolls down the page, you can have functions that
execute in response.

Functional
You can write functions in JavaScript as "pure functions." These functions
always return the same output for a given set of inputs and do not produce
side effects. JavaScript also supports first-class and higher-order functions.
This means functions can be treated like any other value, passed as
arguments to other functions, and returned from functions.

Object-Oriented
In JavaScript, you can create objects as custom data stores, and these objects
can inherit properties and methods from other objects.

Imperative
This is a text placeholder - click this text to edit.

StevenCodeCraft.com
Page 391
StevenCodeCraft.com

Declarative
In this paradigm, you write programs by describing the desired outcome
without explicitly detailing the control flow. This approach is often associated
with functional programming, like using the forEach method to loop over an
array instead of using a traditional for loop.

Now we'll cover some important aspects of JavaScript.

JavaScript Evolution
JavaScript is constantly evolving. The standardized version of JavaScript is
called ECMAScript, which browsers follow. New versions are released every
year. One of the most significant updates was ES6, which came out in 2015.
ES6 introduced many modern features, and today, "ES6" is often used to refer
to "modern JavaScript."

JavaScript as an Interpreted Language


JavaScript is generally considered an interpreted language, meaning that the
code is executed directly by a JavaScript engine, rather than being compiled
into machine code. However, modern browsers use a technique called Just-
in-Time (JIT) compilation. This approach converts JavaScript code into binary
code while it’s being executed, which can improve performance.

StevenCodeCraft.com
Page 392
StevenCodeCraft.com

JavaScript Engine and Event Loop


The JavaScript engine is the program that executes JavaScript code in the
browser. Most engines now use JIT compilation, though some may still
interpret the code. The Event Loop is another crucial component that allows
JavaScript to handle asynchronous tasks, like responding to user interactions.

Dynamic Typing
JavaScript is dynamically typed, which means variables can hold values of any
type without needing to be declared as a specific type. Common types in
JavaScript include:

Number
BigInt (example: 20n)
Boolean
String
Symbol (example: Symbol('description')
Null (explicitly set to no value)
Undefined (value has not been set)

StevenCodeCraft.com
Page 393
StevenCodeCraft.com

Automatic Semicolon Insertion


JavaScript automatically inserts semicolons where they appear to be missing,
but it’s still a good practice to include them explicitly.

Math Functions
JavaScript includes several useful math functions, like `Math.floor()` and
`Math.random()`, which returns a number between 0 and 1. These functions
can be helpful in algorithmic problems.

Working with Strings and Numbers


JavaScript offers various ways to manipulate strings and convert them to
numbers. For instance:

Number(strNum);
Converts a string to a number.

parseInt(strNum);
Extracts an integer from a string.

parseFloat(strNum);
Handles decimals.

StevenCodeCraft.com
Page 394
StevenCodeCraft.com

Math.pow(2, 3);
Calculates 2 to the power of 3.

Template literals (using backticks)


Allow for easy string interpolation.

Objects and Data Structures


JavaScript provides built-in classes like Map and Set:

Map is similar to an object but allows keys of any type and includes
methods for managing key-value pairs.
Set is a collection of unique values, ensuring no duplicates.

Functions as First-Class Citizens


In JavaScript, functions are first-class objects, meaning they can be passed as
arguments, returned from other functions, and assigned to variables. For
example:

StevenCodeCraft.com
Page 395
StevenCodeCraft.com

Error Handling
JavaScript provides mechanisms for error handling, such as using `try...catch`
blocks. Errors can be thrown manually using `throw new Error('Oh no');`.

Console Methods
The `console` object in JavaScript offers various methods for debugging:

console.log()
The most common method to logging to the console.

console.table()
Counts the number of times it’s been called.

StevenCodeCraft.com
Page 396
StevenCodeCraft.com

console.count()
Counts the number of times it’s been called.

console.time() and console.end()


To measure execution time.

Strict Mode
Adding `'use strict';` at the top of a JavaScript file enforces stricter parsing and
error handling, which can help catch potential issues early.

Active Recall Study Questions


160) What is JavaScript?
161) What is a programming paradigm?
162) Can you identify and explain the five primary programming paradigms
supported in JavaScript?
163) What are the 7 primitive data types in JavaScript?
164) What is the purpose of Just-in-Time (JIT) compilation in modern
JavaScript engines, and how does it differ from traditional interpretation?
165) How does the Map object in JavaScript differ from a standard object, and
what advantage does it offer when managing key-value pairs?

StevenCodeCraft.com
Page 397
StevenCodeCraft.com

160) What is JavaScript?


JavaScript is the primary programming language of the web, primarily used
for adding dynamic functionality to websites. JavaScript is a general-purpose,
multi-paradigm programming language with dynamic typing.

161) What is a programming paradigm?


A programming paradigm refers to a style of programming.

162) Can you identify and explain the five primary programming paradigms
supported in JavaScript?
Event-Driven: JavaScript allows functions to respond to events. For example,
when a user clicks on an element or scrolls down the page, you can have
functions that execute in response.
Functional: You can write functions in JavaScript as “pure functions.” These
functions always return the same output for a given set of inputs and do not
produce side effects. JavaScript supports first-class functions and high-order
functions. This means functions can be treated like any other value, passed
as arguments to other functions, and returned from functions.
Object-Oriented: In JavaScript, you can create objects as custom data stores,
and these objects can inherit properties and methods from other objects.
Imperative: You can write programs by explicitly describing the control flow,
using constructs like loops and conditionals to direct how the program
should execute.

StevenCodeCraft.com
Page 398
StevenCodeCraft.com

163) What are the 7 primitive data types in JavaScript?


Primitive types refer to the most basic data types of a language.
JavaScript has seven primitive data types.
Number: numeric values, including integers and decimal values
BigInt: integers too large to store as a number data type
Boolean: a binary value of true or false
String: a sequence of characters
Symbol: a dynamically generated unique value
null: a nonexistent value
undefined: a value that has not been set

164) What is the purpose of Just-in-Time (JIT) compilation in modern


JavaScript engines, and how does it differ from traditional interpretation?
Just-in-Time (JIT) compilation in modern JavaScript engines improves
performance by converting JavaScript code into machine code while the
program is running, instead of interpreting it line by line. Unlike traditional
interpretation, which directly executes the code as it reads, jIT compilation
allows the code to run faster because the browser optimizes it as it executes,
resulting in better overall performance.

StevenCodeCraft.com
Page 399
StevenCodeCraft.com

165) How does the Map object in JavaScript differ from a standard object, and
what advantage does it offer when managing key-value pairs?
The Map object in JavaScript differs from a standard object in that it allows
keys of any type (not just strings or symbols) and preserves the order of key-
value pairs. An advantage of using Map is that it provides built-in methods for
managing keys and values more efficiently, such as set, get, and has. This
makes Map more flexible and powerful when working with key-value pairs,
especially when you need keys that aren’t strings.

StevenCodeCraft.com
Page 400
StevenCodeCraft.com

Variables and Scoping


In this lesson, we'll discuss some key concepts related to variable declaration
in JavaScript:

let, var, and const, as well as the concepts of block scope, function scope, and
hoisting.

let
The let keyword is used to declare a block-scoped variable. This means that
the variable can only be accessed within the block where it was defined.
Additionally, you cannot access a let variable before it is initialized.

var
The var keyword declares a function-scoped variable. Unlike let, a var variable
is automatically initialized to undefined when it is hoisted. This means the
variable is available throughout the entire function, even before its
declaration line, but will hold the value undefined until it is assigned.

const
The const keyword is used to declare a constant value. const behaves
similarly to let, with block scope, but with the added restriction that the
variable cannot be reassigned after it’s been initialized.

StevenCodeCraft.com
Page 401
StevenCodeCraft.com

Block Scope
Block scope refers to the behavior where a variable is only accessible within
the block where it was defined. Typically, this block is the nearest pair of curly
braces {} around the declaration.

Function Scope
Function scope means that a variable is accessible anywhere within the
function where it was defined. This is the scope used by variables declared
with var.

Hoisting
Hoisting is the process by which JavaScript moves variable declarations to the
top of their scope. For var variables, they are hoisted and initialized to
undefined until their assigned value is encountered. For let and const
variables, they are hoisted but not initialized, meaning they cannot be
accessed before their line of declaration.

StevenCodeCraft.com
Page 402
StevenCodeCraft.com

In this example, varNum is hoisted and initialized to undefined, so it doesn’t


cause an error when accessed before its declaration. On the other hand,
letNum is hoisted but not initialized, leading to a reference error when
accessed before its declaration.

Active Recall Study Questions


166) How does the choice between let, var, and const impact the scope and
behavior of variables in JavaScript, and why is it important to understand
these differences?
167) What role does hoisting play in the execution of JavaScript code,
particularly in relation to variable declarations with var, let, and const?

StevenCodeCraft.com
Page 403
StevenCodeCraft.com

166) How does the choice between let, var, and const impact the scope and
behavior of variables in JavaScript, and why is it important to understand
these differences?
The choice between let, var, and const in JavaScript affects how and where
variables can be accessed in your code. var is function-scoped
Meaning it can be accessed anywhere within the function where it’s declared.
It is also hoisted and initialized to undefined, which can lead to unexpected
behavior if not carefully managed. let is block-scoped This limits its
accessibility to the block (usually within curly braces) where it is declared. It is
hoisted but not initialized, so accessing it before declaration causes an error.
const is also block-scoped like let It creates a constant value that cannot be
reassigned after its initial assignment. Understanding these differences is
crucial for avoiding bugs, ensuring variables are only accessible where they
should be, and maintaining predictable and secure code.

StevenCodeCraft.com
Page 404
StevenCodeCraft.com

167) What role does hoisting play in the execution of JavaScript code,
particularly in relation to variable declarations with var, let, and const?
Hoisting in JavaScript is the process where variable declarations are moved to
the top of their scope during the code’s execution phase. With var, the
variable is hoisted and initialized to undefined, so it can be accessed before
its declaration but will hold undefined until it’s assign a value. With let and
const, the variables are hoisted but not initialized, meaning they cannot be
accessed before their declaration, leading to a reference error if you try to
use them too early. Understanding hoisting helps prevent errors and
ensures that variables are used in the correct order in your code.

StevenCodeCraft.com
Page 405
StevenCodeCraft.com

Arrays
In this lesson, we'll cover the basics of working with arrays in JavaScript.

An array is a data structure used to store lists of information. In JavaScript,


arrays are mutable, meaning they can be changed, and they can contain data
of different types. Although arrays are essentially objects, they have a special
syntax for creation and manipulation.

Creating Arrays
You can create an array using bracket notation:

Or using the Array constructor, though this is less common

StevenCodeCraft.com
Page 406
StevenCodeCraft.com

Common Array Methods:

fill()
fill(): Fills an array with a specific value. This is useful in technical interviews.

const arr2 = new Array(5).fill(0);

includes()
Checks if an array contains a specific value, returning a boolean.

console.log(array.includes(2));

indexOf()
Finds the first occurrence of a value in the array.

console.log(array.indexOf(2));

lastIndexOf()
Finds the last occurrence of a value in the array.

console.log(array.lastIndexOf(2));

StevenCodeCraft.com
Page 407
StevenCodeCraft.com

push()
Adds elements from the end of the array.

console.log(array.push(3));

pop()
Removes elements from the beginning of the array.

console.log(array.pop());

unshift()
Adds elements from the beginning of the array.

console.log(array.unshift(1));

shift()
Removes elements from the beginning of the array.

console.log(array.shift());

StevenCodeCraft.com
Page 408
StevenCodeCraft.com

Array Identification
You can check if a variable is an array using Array.isArray() or the instanceof
operator.

Modifying Arrays
splice()
Modifies an array by removing, replacing, or adding elements.

console.log(array.shift(startIndex, endIndex, itemsToAdd));

slice()
Returns a new array with a portion of the original array, leaving the original
unchanged.

const newArr = array.slice(1, 3);

StevenCodeCraft.com
Page 409
StevenCodeCraft.com

concat()
Merges two arrays into one.

let combinedArray = arr.concat(['hello', 'world']);

reverse()
Reverses the order of elements in an array in place.

arr.reverse();

join()
Combines all elements of an array into a single string, using a specified
delimiter.

console.log(arr.join(', '));

Looping Through Arrays


You can loop through arrays using traditional for loops, but it's more
common to use for-of loops or array methods like forEach().

StevenCodeCraft.com
Page 410
StevenCodeCraft.com

Array Functions

map()
Creates a new array by applying a function to each element of the original
array.

const mappedArray = array.map(value => value + 1);

filter()
Creates a new array with elements that meet a specific condition.

const filteredArray = array.filter(value => value > 1);

find()
Finds the first element that meets a condition.

const foundValue = array.find(value => value > 1);

findIndex()
Finds the first index that meets a condition.

const foundValue = array.findIndex(value => value > 1);

StevenCodeCraft.com
Page 411
StevenCodeCraft.com

every()
Checks if every in the array meets a condition.

const allPass = array.every(value => value > 0);

some()
Checks if any element in the array meets a condition.

const anyPass = array.some(value => value > 0);

reduce()
Reduces the array to a single value by applying a function from left to right.

let sum = array.reduce((acc, value) => acc + value, 0);

reduceRight()
Reduces the array to a single value by applying a function from right to left.

let sum = array.reduceRight((acc, value) => acc + value, 0);

StevenCodeCraft.com
Page 412
StevenCodeCraft.com

Sorting Arrays
You can sort an array with the sort() method, either by default or with a
custom comparison function.

arrayToSort.sort((a, b) => a - b);

Active Recall Study Questions


168) What are the differences between the splice() and slice() methods in
JavaScript when modifying arrays?
169) How can you check if a variable is an array in JavaScript, and why might
you choose one method over another?
170) What is the purpose of the reduce() method in JavaScript, and how does
it differ from other array methods like map() or filter()?

StevenCodeCraft.com
Page 413
StevenCodeCraft.com

Active Recall Answers


168) What are the differences between the splice() and slice() methods in
JavaScript when modifying arrays?
The splice() and slice() methods in JavaScript both deal with parts of an array,
but they work differently. splice() modifies the original array by adding,
removing, or replacing elements. It changes the array in-place and returns
the removed elements. slice() does not modify the original array. Instead, it
returns a new array containing a portion of the original array based on the
specified start and end indices. In summary, splice() changes the original
array, while slice() creates a new array without altering the original.

StevenCodeCraft.com
Page 414
StevenCodeCraft.com

169) How can you check if a variable is an array in JavaScript, and why might
you choose one method over another?
You can check if a variable is an array in JavaScript using two main methods:
Array.isArray(variable)
This method is specifically designed to check if a variable is an array. It’s the
most reliable and preferred way because it directly checks for array types.
variable instanceof Array
This checks if the variable is an instance of the Array class. While it works, it
might be less reliable in certain scenarios, like when dealing with arrays
across different frames or windows. Why choose one over the other?
Array.isArray() is generally preferred because it is more reliable and clearly
indicates the intent to check for an array. It handles edge cases better, such
as arrays created in different execution contexts.

StevenCodeCraft.com
Page 415
StevenCodeCraft.com

170) What is the purpose of the reduce() method in JavaScript, and how does
it differ from other array methods like map() or filter()?
The reduce() method in JavaScript is used to iterate over an array and
accumulate its elements into a single value, such as sum, product, or a more
complex result. It applies a function to each element, passing the result of the
previous iteration as an accumulator, and returns the final accumulated
value. Now it differs from map() and filter() map() creates a new array by
applying a function to each element of the original array filter() creates a new
array containing only the elements that meet a specific condition.

Objects
In this lesson, we'll discuss objects in JavaScript, focusing on key concepts like
object creation, manipulation, and unique features like symbols.

Objects in JavaScript are the base non-primitive data structure used to store
key-value pairs.

Keys are usually strings but can also be symbols, while values can be of any
type. Typically, objects are declared using the object literal syntax:

StevenCodeCraft.com
Page 416
StevenCodeCraft.com

Accessing and Modifying Objects


You can access object properties using dot notation or bracket
notation.
Objects in JavaScript are mutable, meaning you can add, modify, or
delete properties even after the object has been created.

Example

Comparing Objects
Objects are compared by reference, not by value. Two different objects, even
with the same properties, are not equal.

Symbols
Symbols are a unique and immutable primitive value used as keys in
objects.
Symbols are created using Symbol(description) and ensure that each
symbol is unique, even if they have the same description.

StevenCodeCraft.com
Page 417
StevenCodeCraft.com

Example

Inheriting from Other Objects


You can iterate over an object's properties using methods like Object.keys(),
Object.values(), and Object.entries(). These methods return arrays of the
object's keys, values, or key-value pairs.

Object Methods
JavaScript allows defining methods directly within objects, which can be
invoked like any other function. These methods can also include getter and
setter functions for properties:this text to edit.

StevenCodeCraft.com
Page 418
StevenCodeCraft.com

Object Modification and Protection


You can copy objects using Object.assign(), freeze them with
Object.freeze(), or seal them with Object.seal().
Object.freeze() makes an object immutable, while Object.seal() allows
modifications to existing properties but prevents adding new ones.

StevenCodeCraft.com
Page 419
StevenCodeCraft.com

Special Object Methods


toString() and valueOf() are methods that convert an object to a string or a
primitive value. These methods can be overridden to customize how objects
behave when they're converted.

The Symbol.toPrimitive method can also be used to control how an object is


converted to a primitive value based on the context (number, string, or
default).

Active Recall Study Questions


171) How do symbols in JavaScript ensure uniqueness, and what is a practical
use case for using symbols as object keys?
172) What is the difference between Object.freeze() and Object.seal() when
modifying objects in JavaScript?

StevenCodeCraft.com
Page 420
StevenCodeCraft.com

Active Recall Answers


171) How do symbols in JavaScript ensure uniqueness, and what is a practical
use case for using symbols as object keys?
Symbols in JavaScript ensure uniqueness by generating a completely unique
identifier each time Symbol(description) is called, even if the description is
the same. This makes symbols ideal for use as object keys when you need to
avoid property name conflicts, especially when adding properties to objects
from third-party libraries or APIs. Since symbols are hidden from most
iteration methods, they prevent accidental property overrides and keep your
keys distinct from any existing keys on the object.

172) What is the difference between Object.freeze() and Object.seal() when


modifying objects in JavaScript?
The difference between Object.freeze() and Object.seal() in JavaScript lies in
how they restrict modifications to objects.
Object.freeze() makes an object completely immutable. You cannot add,
remove, or modify properties of the object.
Object.seal() allows you to modify existing properties but prevents adding
new properties or deleting existing ones.
In summary, Object.freeze() fully locks the object, while Object.seal() allows
changes to existing properties but prevents structural changes.

StevenCodeCraft.com
Page 421
StevenCodeCraft.com

Equality and Type Coersion


In this lesson, we'll cover the concepts of equality and type coercion in
JavaScript, focusing on the differences between loose equality (==) and strict
equality (===).

Loose Equality (==)


The loose equality operator compares values regardless of their types. It
follows specific steps to perform type conversion before making the
comparison:

1. If both values are null or undefined, it returns true.


2. Booleans are converted to numbers: true becomes 1, and false
becomes 0.
3. When comparing a number to a string, the string is converted to a
number.
4. When comparing an object to a string, the object is converted using its
toString() or valueOf() method.
5. If the values are of the same type, it compares them as strict equality
would.

StevenCodeCraft.com
Page 422
StevenCodeCraft.com

While loose equality can be useful in certain cases, such as checking if a value
is either null or undefined using value == null, strict equality is generally
preferred because it is more predictable.

Strict Equality (===)


The strict equality operator compares both the value and the type without
performing type conversion:

1. If either value is NaN, it returns false.


2. If the values have different types, it returns false.
3. If both values are null or both are undefined, it returns true.
4. If both values are objects, it returns true only if they reference the same
object, otherwise false.
5. If both values are of the same primitive type, it returns true if they have
the same value, otherwise false.

StevenCodeCraft.com
Page 423
StevenCodeCraft.com

Type Coercion
Loose equality involves implicit type coercion, where JavaScript automatically
converts one or both values to a common type before comparison. This often
results in values being converted to numbers.

You can also perform explicit type coercion manually:

Special Cases
NaN
The value NaN (Not-a-Number) is unique in that it is not equal to any other
value, including itself.

StevenCodeCraft.com
Page 424
StevenCodeCraft.com

null and undefined


These values are equal to each other with loose equality but not with strict
equality.

Objects
When comparing objects, strict equality checks if they reference the same
object, not if they have the same contents.

console.log({} === {}); // false

Conclusion
In general, it's best to use strict equality (===) to avoid unexpected behavior
caused by implicit type coercion. Loose equality (==) can be helpful in specific
cases, but it requires a good understanding of how JavaScript performs type
conversions.

StevenCodeCraft.com
Page 425
StevenCodeCraft.com

Active Recall Study Questions


173) What is the main difference between loose quality (==) and strict equality
(===) when comparing values in JavaScript?
174) Why is strict equality (===) generally preferred over loose quality (==) in
JavaScript, and in what situations might loose equality be useful?

StevenCodeCraft.com
Page 426
StevenCodeCraft.com

Active Recall Answers


173) What is the main difference between loose quality (==) and strict equality
(===) when comparing values in JavaScript?
The main difference between loose equality (==) and strict equality (===) in
JavaScript is that loose equality compares values after performing type
conversions, whereas strict equality compares both the value and the type
without any conversion. This means == may consider different types as equal
if they can be coerced to the same value, while === requires both the value
and type to be exactly the same.

174) Why is strict equality (===) generally preferred over loose quality (==) in
JavaScript, and in what situations might loose equality be useful?
Strict equality (===) is generally preferred in JavaScript because it avoids
unexpected behavior by ensuring that both the value and type must match
exactly, making the code more predictable and easier to debug. Loose
equality (==), which performs type conversion, can lead to confusing results if
you’re not careful. However, loose quality can be useful in specific situations,
such sa when you want to check if a value is either null or undefined in one
step, using value == null.

StevenCodeCraft.com
Page 427
StevenCodeCraft.com

Syntactic Sugar and Modern


JavaScript
In this lesson, we'll explore some modern JavaScript features often referred
to as "syntactic sugar." These features simplify the code we write, making it
more concise and easier to read.

Arrow Functions
Arrow functions provide a shorter syntax for writing functions, especially
anonymous ones. They are useful for short, one-line functions. Unlike
traditional functions, arrow functions do not have their own this binding and
cannot be used as constructors or generators.

Destructuring Assignment
Destructuring allows you to extract values from arrays or objects and assign
them to variables in a more concise way.

StevenCodeCraft.com
Page 428
StevenCodeCraft.com

You can also rename fields during destructuring:

Rest Operator
The rest operator ... lets you condense multiple elements into a single array.
It can also be used in function parameters to accept an unlimited number of
arguments.

Spread Operator
The spread operator ... allows you to expand an array or object into individual
elements. It’s useful for combining arrays or copying objects.

StevenCodeCraft.com
Page 429
StevenCodeCraft.com

Template Literals
Template literals make it easier to work with strings by allowing inline
expressions using backticks `.

Nullish Coalescing (??)


The nullish coalescing operator provides a default value if the left-hand side is
null or undefined.

Optional Chaining (?.)


A JavaScript operator using ?. for reading object properties without throwing
an error if the object is null

Example:

person?.company?.website

StevenCodeCraft.com
Page 430
StevenCodeCraft.com

The above code example will work the same as


person.company.website however if any values in the chain are null or
undefined, it will return undefined rather than throwing an error.

Short Circuit Evaluation


A method of utilizing the evaluation order of JavaScript to conditionally run
code. This usually uses the && operator, because for it to return true, both
the left and right expressions must be true.

Since the browser runs code from left to right, if it encounters false on the
left side, it does not even run the code on the right side. Thus this can be
used to conditionally run code.

true && myFunc(); // calls myFunc()


false && myFunc(); // doesn't call myFunc()

Less commonly, short circuit evaluation can also be used with the ||
operator

Since this operator only needs one expression to be true, if the left side is
true then the right side will not be evaluated. This is essentially the opposite
of the behavior with &&.

true || myFunc(); // doesn't call myFunc()


false || myFunc(); // calls myFunc()

StevenCodeCraft.com
Page 431
StevenCodeCraft.com

Active Recall Study Questions


175) What is the primary difference between arrow functions and traditional
functions in JavaScript, particularly regarding this binding?
176) How do the rest and spread operators differ in their usage, and can you
give an example of each?
177) What problem does the optional chaining operator (?.) solve in
JavaScript, and how does it improve code safety when accessing nested
properties?

StevenCodeCraft.com
Page 432
StevenCodeCraft.com

Active Recall Answers


175) What is the primary difference between arrow functions and traditional
functions in JavaScript, particularly regarding this binding?
The primary difference between arrow functions and traditional functions in
JavaScript is how they handle this binding. Arrow functions do not have their
own this context; instead, they inherit this from the surrounding (lexical)
scope. In contrast, traditional functions have their own this binding, which
can change depending on how the function is called (e.g., as a method, in a
constructor, or with call/apply). This makes arrow functions particularly
useful when you want to preserve the this value from the surrounding
context.

176) How do the rest and spread operators differ in their usage, and can you
give an example of each?
The rest and spread operators in JavaScript both use the ... syntax but serve
different purposes. Rest Operator: The rest operator condenses multiple
elements into a single array. It's typically used in function parameters to
handle an unlimited number of arguments or to capture the remaining
elements in an array or object.

const [first, ...rest] = [1, 2, 3, 4]; // rest is [2, 3, 4]

StevenCodeCraft.com
Page 433
StevenCodeCraft.com

Spread Operator: The spread operator expands an array or object into


individual elements. It's often used to combine arrays, copy objects, or pass
elements as separate arguments to a function.

const arr1 = [1, 2, 3];


const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // combined is [1, 2, 3, 4, 5, 6]

In summary, the rest operator gathers elements into an array, while the
spread operator spreads elements out into individual items.

177) What problem does the optional chaining operator (?.) solve in
JavaScript, and how does it improve code safety when accessing nested
properties?
The optional chaining operator (?.) in JavaScript solves the problem of safely
accessing nested object properties that might be null or undefined.

Without optional chaining, trying to access a deeply nested property on a null


or undefined object would throw an error, potentially crashing your
code. With optional chaining, if any part of the chain is null or undefined, the
expression simply returns undefined instead of throwing an error, making
your code safer and preventing runtime errors.

StevenCodeCraft.com
Page 434
StevenCodeCraft.com

Connecting JavaScript to HTML


In this lesson, we'll discuss how to connect JavaScript to HTML using the
<script> tag and the different ways to manage how scripts are loaded and
executed.

The <script> Tag:

The <script> tag is used to add JavaScript to an HTML document. Typically,


you’ll see this tag in the <head> section with the src attribute pointing to an
external JavaScript file.

By default, when a script is encountered, the browser pauses rendering the


rest of the page until the script is fully downloaded and executed. This can
delay the page load, especially for large scripts.

StevenCodeCraft.com
Page 435
StevenCodeCraft.com

Defer Attribute:
The defer attribute changes this behavior by downloading the script
asynchronously, without blocking the page, and then executing it only after
the DOM has fully loaded.

<script src="script.js" defer></script>

This is the recommended approach for most cases, as it ensures the script
runs after the HTML is fully parsed, without delaying the page load.

Async Attribute:
The async attribute also downloads the script asynchronously but executes it
as soon as it’s ready, even if the DOM hasn’t finished loading. This can be
useful for scripts that don’t depend on the DOM, such as analytics or ads.

Example
<script src="script.js" async></script>

Use async carefully, as it can lead to unpredictable behavior if the script


needs to interact with the DOM.

StevenCodeCraft.com
Page 436
StevenCodeCraft.com

Traditional Placement in <body>


Before these attributes were common, scripts were often placed at the end of
the <body> to ensure the DOM was fully loaded before the script ran.
However, this approach can be slower since the script isn’t downloaded until
the browser reaches the bottom of the page.

Waiting for DOMContentLoaded


If you’re not using defer or async, you can write JavaScript that waits for the
DOMContentLoaded event before executing, ensuring the DOM is ready.

StevenCodeCraft.com
Page 437
StevenCodeCraft.com

Summary
Use defer for scripts that need to interact with the DOM but don’t need
to block the page load.
Use async for scripts that can run independently of the DOM, like
analytics or ads.
Avoid placing scripts at the bottom of the <body> when using defer
provides a better solution.

Active Recall Study Questions


178) What is the primary difference between the defer and async attributes
when loading JavaScript with the <script> tag?
179) Why is it generally recommended to use the defer attribute instead of
placing scripts at the end of the <body> tag?

StevenCodeCraft.com
Page 438
StevenCodeCraft.com

Active Recall Answers


178) What is the primary difference between the defer and async attributes
when loading JavaScript with the <script> tag?
The primary difference between the defer and async attributes when loading
JavaScript is how and when the script is executed:

defer: The script is downloaded asynchronously and executed only after


the entire HTML document has been parsed, ensuring that the DOM is
fully loaded before the script runs.
async: The script is downloaded asynchronously and executed as soon
as it’s ready, even if the HTML document is still being parsed, which can
lead to unpredictable behavior if the script depends on the DOM.

179) Why is it generally recommended to use the defer attribute instead of


placing scripts at the end of the <body> tag?
It’s generally recommended to use the defer attribute instead of placing
scripts at the end of the <body> tag because defer allows the script to be
downloaded earlier, while still ensuring it only runs after the DOM is fully
loaded. This results in faster page loads and ensures that the script executes
at the right time, without delaying the rendering of the rest of the page.

StevenCodeCraft.com
Page 439
StevenCodeCraft.com

DOM Manipulation
In this lesson, we'll cover some essential concepts and techniques for DOM
manipulation in JavaScript. This includes selecting elements, setting
attributes, adding and removing elements, and working with sizes and
scrolling.

Selecting Elements
To interact with elements on the page, we need to select them using various
methods on the document object:

document.getElementById(id)
Selects a single element by its id.

const element = document.getElementById('element-id');

document.querySelector(selector)
Selects the first element that matches a CSS selector.

const element = document.querySelector('.class-name');

StevenCodeCraft.com
Page 440
StevenCodeCraft.com

document.querySelectorAll(selector)
Selects all elements that match a CSS selector and returns them as a
NodeList.

const elements = document.querySelectorAll('li');

Setting Attributes
Once you've selected an element, you can modify its attributes and styles:

element.style.property
Sets a CSS property as an inline style.

element.style.color = 'red';

element.textContent
Sets or gets the text content of an element.

element.textContent = 'Hello World';

element.classList
Manages the CSS classes of an element with methods like add(), remove(),
and toggle().

element.classList.add('active');

StevenCodeCraft.com
Page 441
StevenCodeCraft.com

Adding and Removing Elements


You can create and manipulate elements directly in the DOM:

document.createElement(tagName)
Creates a new HTML element.

const newElement = document.createElement('p');


newElement.textContent = 'New paragraph';
document.body.appendChild(newElement);

element.appendChild(childElement)
Appends a new child element to an existing element.

document.body.appendChild(newElement);

element.remove()
Removes an element from the DOM.

newElement.remove();

StevenCodeCraft.com
Page 442
StevenCodeCraft.com

Sizes and Scrolling


JavaScript provides several properties and methods to work with element
sizes and scrolling:

window.innerWidth
Gets the width of the browser window.

console.log(window.innerWidth);

element.scrollHeight
Gets the total height of the content inside an element, including content not
visible due to overflow.

console.log(element.scrollHeight);

element.scrollIntoView()
Scrolls the element into view.

element.scrollIntoView();

StevenCodeCraft.com
Page 443
StevenCodeCraft.com

element.scrollTo(options)
Scrolls the element to a specific position with options like smooth scrolling.

element.scrollTo({ top: 100, behavior: 'smooth' });

Conclusion
DOM manipulation is a fundamental part of working with JavaScript on the
web. By mastering how to select elements, set attributes, add and remove
elements, and handle sizes and scrolling, you can create dynamic and
interactive web pages.

Active Recall Study Questions


180) What method would you use to select an element by its id in JavaScript?
181) How can you add a CSS class to an element using JavaScript?
182) Which method allows you to create a new HTML element in JavaScript?
183) What property would you use to get the total height of the content
inside an element, including content not visible due to overflow?
184) How can you scroll an element into view using JavaScript?

StevenCodeCraft.com
Page 444
StevenCodeCraft.com

Active Recall Answers


180) What method would you use to select an element by its id in JavaScript?
You would use the document.getElementById(id) method to select an
element by its id in JavaScript. For example, document.getElementById('myId')
selects the element with the id of "myId".

181) How can you add a CSS class to an element using JavaScript?
You can add a CSS class to an element using JavaScript with the
element.classList.add('className') method. For example,
element.classList.add('active') adds the class active to the selected element.

182) Which method allows you to create a new HTML element in JavaScript?
You can create a new HTML element in JavaScript using the
document.createElement('tagName') method. For example,
document.createElement('p') creates a new <p> (paragraph) element.

183) What property would you use to get the total height of the content
inside an element, including content not visible due to overflow?
You would use the element.scrollHeight property to get the total height of
the content inside an element, including content not visible due to overflow.

StevenCodeCraft.com
Page 445
StevenCodeCraft.com

184) How can you scroll an element into view using JavaScript?
You can scroll an element into view using the element.scrollIntoView()
method in JavaScript. For example, element.scrollIntoView() will automatically
scroll the page so that the selected element is visible.

Event-Driven Programming
In this lesson, we'll explore the concept of Event-Driven Programming in
JavaScript, focusing on how to create and manage event listeners, as well as
understanding event propagation and delegation.

Event-Driven Programming is a paradigm where code runs in response to


events, often triggered by user interactions. In JavaScript, you can add event
listeners to DOM elements to handle these events.

Creating an Event Listener


To create an event listener, use the addEventListener method:

StevenCodeCraft.com
Page 446
StevenCodeCraft.com

This adds a click event listener to a button, which triggers the onClick function
when the button is clicked.

Event Propagation
Event propagation is the process by which an event travels through the DOM,
going through three phases:

1. Capturing Phase: The event starts from the root and travels down to the
target element.
2. Target Phase: The event reaches the target element and triggers the
event listener.
3. Bubbling Phase: The event bubbles back up from the target element to
the root.

By default, event listeners are triggered during the bubbling phase, but you
can change this by setting the third argument of addEventListener to true or
by using an options object with { capture: true } to trigger the event during
the capturing phase. er - click this text to edit.

StevenCodeCraft.com
Page 447
StevenCodeCraft.com

Managing Event Listenersg


You can customize how event listeners behave by using an options object:t to
edit.

capture: Specifies whether the event listener should be triggered during


the capturing phase.
once: Automatically removes the event listener after it has been
triggered once.
passive: Indicates that event.preventDefault() will not be called, allowing
the browser to optimize performance.
signal: An AbortSignal that allows you to remove the event listener by
calling abortController.abort().

StevenCodeCraft.com
Page 448
StevenCodeCraft.com

Event Delegation
Event delegation is a technique where you add a single event listener to a
parent element and delegate the handling of events to its child elements. This
can improve performance by reducing the number of event listeners in your
application.

Here, the event listener on the container element handles clicks on any of its
child elements, changing the text content of the clicked element.

Conclusion
Event-Driven Programming is a powerful way to handle user interactions in
JavaScript. By understanding how to create and manage event listeners,
control event propagation, and use event delegation, you can write more
efficient and maintainable code.

StevenCodeCraft.com
Page 449
StevenCodeCraft.com

Active Recall Study Questions


185) What is Event-Driven Programming, and how does it relate to user
interactions in JavaScript?
186) How can you specify that an event listener should be triggered during
the capturing phase instead of the default bubbling phase?
187) What does the once option do when used in an event listener's options
object?
188) What is event delegation, and how does it improve performance in
JavaScript applications?
189) How can you remove an event listener using an AbortController in
JavaScript?

StevenCodeCraft.com
Page 450
StevenCodeCraft.com

Active Recall Answers


185) What is Event-Driven Programming, and how does it relate to user
interactions in JavaScript?
Event-Driven Programming is a programming paradigm where code runs in
response to events, such as user actions like clicks or key presses. In
JavaScript, it relates to user interactions by allowing developers to add event
listeners to elements on a webpage. These listeners detect specific events
and trigger corresponding functions, enabling dynamic and interactive web
applications.

186) How can you specify that an event listener should be triggered during
the capturing phase instead of the default bubbling phase?
You can specify that an event listener should be triggered during the
capturing phase by passing true as the third argument to addEventListener,
or by using an options object with { capture: true }. This changes the event
listener to fire during the capturing phase instead of the default bubbling
phase.

187) What does the once option do when used in an event listener's options
object?
The once option in an event listener's options object ensures that the event
listener is automatically removed after it is triggered once. This means the
listener will only run the first time the event occurs and then it will be
removed from the element.

StevenCodeCraft.com
Page 451
StevenCodeCraft.com

188) What is event delegation, and how does it improve performance in


JavaScript applications?
Event delegation is a technique where a single event listener is added to a
parent element to manage events for all of its child elements. Instead of
adding individual listeners to each child, the parent handles events as they
bubble up from the children. This improves performance by reducing the
number of event listeners in the application, which is especially beneficial
when dealing with many elements.

189) How can you remove an event listener using an AbortController in


JavaScript?
You can remove an event listener using an AbortController in JavaScript by
passing the controller's signal to the event listener's options object. When
you call abortController.abort(), it removes the event listener automatically.
Here's a simple example:

StevenCodeCraft.com
Page 452
StevenCodeCraft.com

Promises
In this lesson, we'll explore Promises in JavaScript, a key concept for handling
asynchronous operations.

What is a Promise?
A Promise is an object that represents the eventual completion (or failure) of
an asynchronous operation. It has three possible states:

1. Pending: The operation has not completed yet.


2. Fulfilled: The operation completed successfully, and the promise has a
resulting value.
3. Rejected: The operation failed, and the promise has an error.

To remember these three possible states, think of the acronym of PFR


(pending, fulfilled, and rejected)

StevenCodeCraft.com
Page 453
StevenCodeCraft.com

Creating a Promise
You can create a Promise using the Promise() constructor, which takes a
function (often called the executor) with two parameters: resolve and reject.

In this example, after 1 second, the promise will be fulfilled with the value 2.

Handling Promises with then, catch, and finally


Promises have three main methods to handle their outcomes:

then(fulfilledFn, rejectedFn): Handles the fulfilled value or rejected error.


catch(rejectedFn): Handles only the rejected error.
finally(callback): Runs a callback when the promise is settled, regardless
of whether it was fulfilled or rejected.

StevenCodeCraft.com
Page 454
StevenCodeCraft.com

Chaining Promises
Promises can be chained to handle multiple asynchronous operations in
sequence. Each then returns a new promise, allowing you to chain more
.then() calls.

StevenCodeCraft.com
Page 455
StevenCodeCraft.com

Promise Utility Functions


JavaScript provides utility functions to work with multiple promises:

Promise.all([]): Waits for all promises in the array to settle and returns
their results in an array. If any promise is rejected, it returns the first
rejection.
Promise.race([]): Returns the result of the first promise to settle (fulfilled
or rejected).
Promise.any([]): Returns the first fulfilled promise, ignoring rejected
ones. If all are rejected, it throws an error.

Async/Await
JavaScript provides utility functions to work with multiple promises:

Promise.all([]): Waits for all promises in the array to settle and returns
their results in an array. If any promise is rejected, it returns the first
rejection.
Promise.race([]): Returns the result of the first promise to settle (fulfilled
or rejected).
Promise.any([]): Returns the first fulfilled promise, ignoring rejected
ones. If all are rejected, it throws an error.

StevenCodeCraft.com
Page 456
StevenCodeCraft.com

Async/Await
Async/Await is a more readable way to work with promises. An async function
returns a promise, and you can use await to pause execution until a promise
settles.

StevenCodeCraft.com
Page 457
StevenCodeCraft.com

Conclusion
Promises and async/await provide powerful ways to manage asynchronous
operations in JavaScript, making your code more readable and easier to
manage. Understanding how to create, handle, and chain promises is
essential for building modern JavaScript applications.

Active Recall Study Questions


190) What are the three possible states of a Promise in JavaScript?
191) How do you create a new Promise in JavaScript, and what are the roles of
resolve and reject?
192) What does the then method do when working with a Promise?
193) How can you handle errors that occur in a Promise chain?
194) What is the purpose of the finally method in a Promise?
195) How does chaining Promises help in managing multiple asynchronous
operations?
196) What is the difference between Promise.all, Promise.race, and
Promise.any?
197) How does async/await improve the readability of asynchronous code
compared to using then and catch?

StevenCodeCraft.com
Page 458
StevenCodeCraft.com

Active Recall Answers


190) What are the three possible states of a Promise in JavaScript?
The three possible states of a Promise in JavaScript are:

1. Pending: The initial state, where the operation is still in progress and
hasn't completed yet.
2. Fulfilled: The operation completed successfully, and the promise has a
resulting value.
3. Rejected: The operation failed, and the promise has an error.

191) How do you create a new Promise in JavaScript, and what are the roles of
resolve and reject?
You create a new Promise in JavaScript using the Promise constructor, which
takes a function (executor) with two parameters: resolve and reject.

resolve(value): Fulfills the promise and sets its resulting value.


reject(error): Rejects the promise and sets an error.

StevenCodeCraft.com
Page 459
StevenCodeCraft.com

In this example, resolve is called if the operation is successful, and reject is


called if it fails.dit.

192) What does the then method do when working with a Promise?
The then method in a Promise is used to handle the result of a fulfilled
promise. It takes two optional callback functions:

1. The first callback is executed if the promise is fulfilled, and it receives the
resolved value.
2. The second callback (optional) is executed if the promise is rejected, and
it receives the error.

The then method returns a new promise, allowing you to chain further then
calls.

This allows you to process the result of an asynchronous operation or handle


errors.

StevenCodeCraft.com
Page 460
StevenCodeCraft.com

193) How can you handle errors that occur in a Promise chain?
You can handle errors in a Promise chain using the catch method. The catch
method takes a callback function that is executed if any promise in the chain
is rejected. It allows you to manage errors and prevent them from
propagating further.

By placing catch at the end of the chain, you can catch any errors that occur
in any of the preceding then methods.

StevenCodeCraft.com
Page 461
StevenCodeCraft.com

194) What is the purpose of the finally method in


a Promise?
The finally method in a Promise is used to execute a callback function when
the promise is settled, regardless of whether it was fulfilled or rejected. It's
useful for running cleanup tasks or final actions that should occur no matter
what the outcome of the promise was.

In this example, the finally block runs after the promise has either resolved or
rejected.

StevenCodeCraft.com
Page 462
StevenCodeCraft.com

195) How does chaining Promises help in


managing multiple asynchronous operations?
Chaining Promises helps manage multiple asynchronous operations by
allowing you to execute them in sequence, with each operation depending on
the result of the previous one. Each then method in the chain returns a new
promise, passing the result to the next then in the chain. This makes it easier
to handle complex workflows, manage errors, and maintain cleaner, more
readable code.

Chaining keeps the code organized and ensures that each step waits for the
previous one to complete.

StevenCodeCraft.com
Page 463
StevenCodeCraft.com

196) What is the difference between Promise.all,


Promise.race, and Promise.any?
Here's a concise and easy-to-understand answer:

Promise.all([]): Waits for all promises in the array to settle (either fulfilled
or rejected). It returns a single promise that resolves with an array of
results if all promises are fulfilled, or rejects with the first error if any
promise is rejected.
Promise.race([]): Returns a single promise that settles as soon as any
one of the promises in the array settles (either fulfilled or rejected). The
result is the value or error of the first settled promise.
Promise.any([]): Waits for the first promise in the array to fulfill. It
returns a single promise that resolves with the first fulfilled value. If all
promises are rejected, it rejects with an error indicating that all
promises were rejected.

StevenCodeCraft.com
Page 464
StevenCodeCraft.com

197) How does async/await improve the readability of asynchronous code


compared to using then and catch?
async/await improves the readability of asynchronous code by allowing you
to write asynchronous operations in a more synchronous, linear style.
Instead of chaining multiple then and catch calls, await pauses the function
execution until the promise resolves, making the code easier to follow and
understand.

Example with async/await:

The async/await version reads more like standard, synchronous code, making
it more intuitive and easier to manage.

StevenCodeCraft.com
Page 465
StevenCodeCraft.com

Working with the Server


In this lesson, we’ll be covering how to work with a server using
JavaScript. The primary method we’ll use is the fetch() function, but we'll also
briefly touch on XMLHttpRequest, how to handle form submissions, and
making asynchronous requests using async and await.

Let’s start by understanding some of the key terms and concepts.

Key Terms
fetch
The fetch function allows you to make network requests in JavaScript. It
returns a Promise, which resolves when the network request completes. You
pass the URL to fetch as its first parameter. Optionally, you can include a
second parameter with options like the request method, headers, or body.

Common options include:

method: For example, 'GET' or 'POST'.


body: The content of the request, often passed as FormData.
headers: Additional metadata for the request. You can create a Headers
object, but a plain object works as well.
signal: Used to abort a request via an AbortController.

StevenCodeCraft.com
Page 466
StevenCodeCraft.com

StevenCodeCraft.com
Page 467
StevenCodeCraft.com

Response Handling
Once a request completes, the fetch Promise resolves to a Response object.
This object provides several useful methods:

response.text(): Returns a Promise that resolves with the text content of


the response.
response.json(): Resolves with a JSON object.
response.status: The numeric status code of the response (e.g., 200 for
success).
response.ok: A boolean indicating whether the request was successful.

Making a Request
Now let’s see an example of making a basic request using fetch. We’ll start
with a simple GET request:

StevenCodeCraft.com
Page 468
StevenCodeCraft.com

Notice that since fetch is asynchronous, the code continues to run even while
the network request is pending. This is why we see the console.log('After
fetch') output before the network request completes.

Working with Parameters


Sometimes, you’ll need to send parameters to the server. You can
concatenate them into the URL or use a URL object.

Here’s an example using string interpolation:

Alternatively, we can use the URL object for adding parameters:

StevenCodeCraft.com
Page 469
StevenCodeCraft.com

Using async and await


Instead of chaining .then() methods, you can simplify your code using async
and await. Here’s an example:

StevenCodeCraft.com
Page 470
StevenCodeCraft.com

Working with JSON APIs


Most APIs return data in JSON format. In such cases, we use response.json()
to parse the response:

POST Requests
When making POST requests, we send data to the server. Let’s take a look at
how to do that:

StevenCodeCraft.com
Page 471
StevenCodeCraft.com

Handling Forms
You can also use fetch to submit form data. Here’s how to handle form
submissions in HTML and JavaScript:

This prevents the form from refreshing the page and allows us to handle the
submission with JavaScript.

StevenCodeCraft.com
Page 472
StevenCodeCraft.com

Aborting Requests
Lastly, you can abort long-running requests using an AbortController. Here’s
an example:

In this case, if the request takes longer than 2 seconds, it will be aborted.

Conclusion
That’s a basic overview of working with servers in JavaScript using fetch,
handling asynchronous requests, working with forms, and even aborting
requests when needed.

StevenCodeCraft.com
Page 473
StevenCodeCraft.com

Active Recall Study Questions


198) What is the primary purpose of the fetch function in JavaScript, and how
does it handle network requests?
199) How can the response object be used to determine whether a network
request was successful?
200) What is the role of the Headers object in making network requests, and
how does it differ from using a plain object for headers?
201) Explain the purpose of an AbortController in JavaScript. How does it help
manage network requests?
202) Why is using async and await generally preferred over chaining .then()
for handling asynchronous operations in modern JavaScript?

StevenCodeCraft.com
Page 474
StevenCodeCraft.com

Active Recall Answers


198) What is the primary purpose of the fetch function in JavaScript, and how
does it handle network requests?
The primary purpose of the fetch function in JavaScript is to make network
requests, such as retrieving data from a server. It works asynchronously and
returns a Promise, allowing you to handle the response once the request is
complete. You provide a URL, and optionally, options like the request method
(GET, POST) and headers. When the request completes, the Promise resolves
to a Response object, which you can then use to access the response data,
status, and other information.

199) How can the response object be used to determine whether a network
request was successful?
You can determine if a network request was successful by checking two
properties of the response object:

response.ok:
This is a boolean that is true if the request was successful (status code in the
200-299 range).

response.status:
This is the numeric status code of the response. A code between 200 and 299
indicates success (e.g., 200 for "OK").

StevenCodeCraft.com
Page 475
StevenCodeCraft.com

If response.ok is true and the status is in the 200-299 range, the request was
successful.

200) What is the role of the Headers object in making network requests, and
how does it differ from using a plain object for headers?
The Headers object in JavaScript is used to manage HTTP request and
response headers in a structured way. It provides methods to easily set, get,
and delete headers when making network requests.
The key difference from using a plain object is that the Headers object
includes built-in methods like .append(), .get(), and .set() for managing
headers, while a plain object only stores key-value pairs without these
methods. Additionally, the Headers object handles header case sensitivity
and duplicates more efficiently.

201) Explain the purpose of an AbortController in JavaScript. How does it help


manage network requests?
The AbortController in JavaScript is used to cancel or abort network requests
that take too long or are no longer needed. It works by generating an
AbortSignal that can be passed to the fetch request. If the abort() method of
the controller is called, the associated network request is canceled. This helps
manage network requests by preventing unnecessary processing and saving
resources when a request is no longer required.

StevenCodeCraft.com
Page 476
StevenCodeCraft.com

202) Why is using async and await generally preferred over chaining .then()
for handling asynchronous operations in modern JavaScript?
Using async and await is generally preferred over chaining .then() because it
makes asynchronous code easier to read and write. With async/await, the
code looks more like regular, synchronous code, making it clearer and less
nested. It also handles errors more cleanly with try/catch, rather than
needing separate .catch() blocks. Overall, it improves code readability and
reduces complexity.

StevenCodeCraft.com
Page 477
StevenCodeCraft.com

Timers and Intervals


In this lesson, we’ll cover timers, intervals, and dates in JavaScript. We’ll use
examples like intervals to repeat actions, timeouts to delay them, and
animation frames to work with browser repaints.

Intervals, Timeouts, and Animation Frames


1) Intervals
Intervals allow us to run a function repeatedly after a set amount of time. For
example, every second or half-second.

StevenCodeCraft.com
Page 478
StevenCodeCraft.com

2) Timeouts
Timeouts are used to delay the execution of a function by a specific amount
of time. It runs the function only once. - click this text to edit.

3) Animation Frames
Animation frames allow us to run a function just before the browser repaints
the screen. This is useful for tasks that need to be executed every frame,
typically for animations.

Starting and Stopping a Timer


Here’s how we’ll set up a simple timer that increments a count on the screen:

StevenCodeCraft.com
Page 479
StevenCodeCraft.com

Using setInterval()
Intervals can repeatedly run a function at a fixed interval. Here's how we start
the timer:

To stop the interval:

StevenCodeCraft.com
Page 480
StevenCodeCraft.com

Using setTimeout()
Unlike setInterval(), setTimeout() runs the function once after a delay. Here’s
an example:

clearTimeout(timeoutID); // cancels the timeout if needed

Both clearTimeout() and clearInterval() are interchangeable, but it’s best to


use the appropriate one for clarity.

Using requestAnimationFrame()
Animation frames are similar to intervals but tied to the screen’s refresh rate.
Typically, this runs about 60 times per second, depending on the user’s
system.

StevenCodeCraft.com
Page 481
StevenCodeCraft.com

Timing with performance.now()


performance.now() gives a high-resolution timestamp of the time since the
page was loaded.

StevenCodeCraft.com
Page 482
StevenCodeCraft.com

Working with Dates


You can use Date.now() to get the number of milliseconds since January 1,
1970:

You can also create a new Date object manually:

StevenCodeCraft.com
Page 483
StevenCodeCraft.com

Conclusion
That covers how to work with intervals, timeouts, animation frames, and
dates in JavaScript. These tools are essential for managing time and running
tasks in your code.

Active Recall Study Questions


198) What is the main difference between using intervals and timeouts for
scheduling functions in JavaScript?
199) How does requestAnimationFrame() optimize performance compared to
using setInterval() for animations?
200) In what situations would you use clearTimeout() or clearInterval(), and
why is it important to cancel timers?
201) Why is performance.now() preferred over Date.now() for measuring
precise time intervals in performance-critical applications?
202) What are some practical use cases for using the Date object in web
development, and how do developers typically work with dates and times?

StevenCodeCraft.com
Page 484
StevenCodeCraft.com

Active Recall Answers


198) What is the main difference between using intervals and timeouts for
scheduling functions in JavaScript?
The main difference between intervals and timeouts is how often they run a
function. An interval repeatedly runs a function at a set time interval (e.g.,
every second), while a timeout runs a function only once after a specified
delay. So, intervals keep going until you stop them, while timeouts happen
just once.

199) How does requestAnimationFrame() optimize performance compared to


using setInterval() for animations?
requestAnimationFrame() optimizes performance by synchronizing your
animations with the browser's refresh rate, typically around 60 times per
second. This ensures that your animation runs smoothly and only when the
browser is ready to repaint, reducing unnecessary work. In contrast,
setInterval() runs at fixed intervals, which can cause performance issues if it
runs more often than the screen refreshes, leading to skipped frames or
choppy animations.

StevenCodeCraft.com
Page 485
StevenCodeCraft.com

200) In what situations would you use clearTimeout() or clearInterval(), and


why is it important to cancel timers?
You use clearTimeout() or clearInterval() when you need to stop a scheduled
function from running. For example, if you've set a timeout or interval but the
action is no longer needed (like stopping a countdown or animation), you can
cancel it to prevent unnecessary code execution. It's important to cancel
timers to avoid performance issues, like unnecessary memory usage or
unexpected behavior, especially when the function isn't needed anymore
(e.g., when the user navigates away from a page).

201) Why is performance.now() preferred over Date.now() for measuring


precise time intervals in performance-critical applications?
performance.now() is preferred over Date.now() for measuring precise time
intervals because it provides a more accurate and precise timestamp, with
sub-millisecond precision. It measures time relative to when the page started
loading, while Date.now() gives the current time in milliseconds since 1970,
which is less precise and can be affected by system clock changes. This makes
performance.now() better for performance-critical tasks like tracking
animations or calculating time intervals in complex applications

StevenCodeCraft.com
Page 486
StevenCodeCraft.com

202) What are some practical use cases for using the Date object in web
development, and how do developers typically work with dates and times?
The Date object is used in web development for handling dates and times.
Some practical use cases include:

1. Displaying current dates and times: For example, showing the current
date on a webpage or adding timestamps to posts.
2. Scheduling tasks: Using dates to set reminders, countdowns, or
deadlines.
3. Calculating time differences: Like finding how many days are left until an
event or how long ago something happened.

Developers typically work with dates using methods like getDate(),


getMonth(), and getFullYear() to retrieve parts of a date, and setDate() or
setMonth() to change them. They also use Date.now() to get the current
timestamp in milliseconds.

StevenCodeCraft.com
Page 487
StevenCodeCraft.com

Closures
In this lesson, we’re going to cover lexical scoping and closures in JavaScript,
two important concepts that help explain how variables are accessed within
different functions.

Lexical Scoping

In the code above, we have lexical scoping. This means that functions can
access variables from their parent scope.

When we call console.log(globalNum + localNum), the logNum function has


access to both its local variable localNum and the globalNum variable from
the outer scope.

This ability for inner functions to access variables from the outer scope is
called a closure.

StevenCodeCraft.com
Page 488
StevenCodeCraft.com

Closures
A closure is created when a function is declared. It gives the function access
to its outer scope, even after that outer function has finished executing.
Formally, a closure is the function plus the environment in which it was
declared. This environment holds references to the variables of its parent
scope.xt to edit.

Here, logNum is defined inside the example function. It creates a closure at


the time of declaration, keeping access to example's scope. Even though we
pass 10 to logNum, it still has access to num from the outer scope.

StevenCodeCraft.com
Page 489
StevenCodeCraft.com

Returning Functions with Closures


Closures are also created when we return a function from another function:

Even though the example function has finished running, the returned
logNum function retains access to num due to the closure. In many
languages, the local variable num would be discarded after example finishes,
but JavaScript keeps it around because of the closure.

StevenCodeCraft.com
Page 490
StevenCodeCraft.com

Practical Use of Closures: Private Methods


Closures can be useful for creating private methods, where certain functions
or variables are not accessible outside of a given scope.

In this example, privateIncrement and privateNum are only accessible inside


the makeFunctions scope. The returned object gives access to the logNum
and increment methods, but not directly to privateIncrement or privateNum.

StevenCodeCraft.com
Page 491
StevenCodeCraft.com

Closures in Loops
Closures can also occur inside loops. Here's a common interview example:

This will output 0, 1, 2 because the let keyword is block scoped. Each time the
loop runs, a new closure is created with a different value of i.

However, if we use var instead of let:

StevenCodeCraft.com
Page 492
StevenCodeCraft.com

This will output 3, 3, 3. That's because var is function scoped, not block
scoped. All iterations of the loop share the same i variable, so when the
timeout runs, it references the final value of i, which is 3.

Conclusion
Closures and lexical scoping are core concepts in JavaScript that explain how
functions access variables. Understanding these concepts is key to writing
effective and efficient code, especially when dealing with nested functions,
loops, and callbacks.

Active Recall Study Questions


203) What is lexical scoping in JavaScript, and how does it affect how
functions access variables?
204) How is a closure created in JavaScript, and why is it useful for functions?
205) What does it mean when we say that a function retains access to its
outer scope, even after the outer function has finished executing?
206) How can closures be used to create private methods in JavaScript?
207) What is the difference between block-scoped variables (like let) and
function-scoped variables (like var), and how does this affect closures in
loops?

StevenCodeCraft.com
Page 493
StevenCodeCraft.com

Active Recall Answers


203) What is lexical scoping in JavaScript, and how does it affect how
functions access variables?
Lexical scoping in JavaScript means that a function can access variables from
its own scope and from the scopes of its parent functions. This is because
functions are able to "remember" the environment where they were created.
It affects how functions access variables by allowing them to use variables
defined in outer scopes, even if those variables aren't inside the function
itself.

204) How is a closure created in JavaScript, and why is it useful for functions?
A closure is created in JavaScript when a function is defined inside another
function and has access to the outer function's variables. This happens
because the inner function "remembers" the environment in which it was
created, even after the outer function has finished running. Closures are
useful because they allow functions to keep access to variables from their
outer scope, which can be used later. This is helpful for tasks like maintaining
private variables or creating functions that remember specific data.

StevenCodeCraft.com
Page 494
StevenCodeCraft.com

205) What does it mean when we say that a function retains access to its
outer scope, even after the outer function has finished executing?
When we say a function retains access to its outer scope even after the outer
function has finished executing, it means that the inner function
"remembers" the variables from the outer function. Even though the outer
function is no longer running, the inner function can still use its variables.
This is possible because of closures in JavaScript, where the inner function
keeps a reference to the environment where it was created.

206) How can closures be used to create private methods in JavaScript?


Closures can be used to create private methods in JavaScript by keeping
certain variables and functions hidden within a function's scope. You define
variables or helper functions inside a function, and only expose the parts you
want to be publicly accessible by returning an object with specific methods.
The variables stay private because they can only be accessed by the inner
functions, not from outside the closure. This helps create "private" data that
can't be directly modified from outside the function.

StevenCodeCraft.com
Page 495
StevenCodeCraft.com

207) What is the difference between block-scoped variables (like let) and
function-scoped variables (like var), and how does this affect closures in
loops?
The difference between block-scoped variables (like let) and function-scoped
variables (like var) is where they are accessible in your code.

Block-scoped (let): Variables declared with let are limited to the block (like
inside a loop or an if statement) where they are defined. This means each
iteration of a loop gets its own new variable.

Function-scoped (var): Variables declared with var are limited to the function
they are defined in, not individual blocks.

This means all iterations of a loop share the same variable. In loops, this
affects closures because with let, each iteration has its own separate variable,
so closures "remember" the correct value for each loop iteration. With var,
since the variable is shared across all iterations, closures will remember the
final value of the loop variable.

StevenCodeCraft.com
Page 496
StevenCodeCraft.com

The 'this' keyword


In this lesson, we’ll explain the behavior of the this keyword in JavaScript,
which refers to the context in which the current code is running. The value of
this is determined at runtime and can vary depending on how and where it’s
used.

General Rules for this in the Browser


1) Global Context
At the top level of a file, this refers to the global object. In the browser, this is
the window object.

2) In a Function (Non-strict mode)


In a standard function, this still refers to the global object (window).

3) In a Function (Strict mode)


If you’re using strict mode, this is undefined inside functions unless explicitly
set.

4) In an Object Method
When this is used inside an object’s method, it refers to that object.

StevenCodeCraft.com
Page 497
StevenCodeCraft.com

5) In a Constructor Function
When using a constructor function, this refers to the new object being
created.

6) In Event Listeners
Inside an event listener, this refers to the object that triggered the event, like
a button element.

Arrow Functions and this


Arrow functions are different from regular functions because they don’t have
their own this. Instead, they inherit this from the surrounding (enclosing)
context in which they were defined. This means they don't create a new this
binding.

Binding this with Functions


JavaScript provides three ways to explicitly bind this in functions:

1) bind(thisArg)
This returns a new function with the value of this bound to thisArg.

2) call(thisArg, arg1, arg2)


This immediately calls the function, with this set to thisArg and additional
arguments passed individually.

StevenCodeCraft.com
Page 498
StevenCodeCraft.com

3) apply(thisArg, [args])
Similar to call(), but the arguments are passed as an array.

Logging this in the Global Context


console.log(this); // logs the window object

At the global level, this refers to the window object in the browser.

In Node.js, it would refer to the global object.

Function Example (Strict Mode)

In strict mode, this is undefined inside a function unless bound explicitly. In


non-strict mode, it would refer to the global object.text to edit.

StevenCodeCraft.com
Page 499
StevenCodeCraft.com

this in Objects

When this is used inside an object’s method, it refers to the object, allowing
access to the object’s properties and methods.

StevenCodeCraft.com
Page 500
StevenCodeCraft.com

this in Event Listeners

In an event listener, this refers to the element that triggered the event, like a
button in this case.

Arrow Functions and this

Since arrow functions don’t have their own this, they use the this from the
outer scope where they were defined, which could be the global object
(window).

StevenCodeCraft.com
Page 501
StevenCodeCraft.com

Using bind()

bind() creates a new function with this set to a specific value (in this case, the
object { num: 7 }).

Using call() and apply()

call() and apply() allow you to invoke a function immediately and set this to a
specific object.

StevenCodeCraft.com
Page 502
StevenCodeCraft.com

this in Array Iteration Functions

Anonymous functions in array methods like forEach() default this to the


global object (window) in non-strict mode, and undefined in strict mode. You
can also pass a custom this value as the second argument to forEach().

this in Classes

StevenCodeCraft.com
Page 503
StevenCodeCraft.com

In a class, this refers to the specific instance of the class, allowing access to
properties and methods defined within the class.

Conclusion
The this keyword in JavaScript is dynamically bound based on where and how
a function is called. It’s important to understand its behavior in different
contexts—global, functions, objects, event listeners, and classes—as it helps
manage scope and access to variables or properties.

Active Recall Study Questions


208) How is the value of the this keyword determined at runtime in JavaScript,
and why does it vary depending on the context?
209) What are the main differences between how this behaves in regular
functions and arrow functions in JavaScript?
210) In what scenarios would you use bind(), call(), or apply() to explicitly set
the value of this in JavaScript?
211) How does the behavior of this change when using strict mode, and what
are the implications for function calls?
212) How does the this keyword function differently in event listeners, and
why is understanding this behavior important when working with DOM
events?

StevenCodeCraft.com
Page 504
StevenCodeCraft.com

Active Recall Answers


208) How is the value of the this keyword determined at runtime in JavaScript,
and why does it vary depending on the context?
The value of the this keyword in JavaScript is determined at runtime based on
how and where a function is called. It can vary depending on the context:

In the global context, this refers to the global object (the window in a
browser).
Inside a regular function, this also refers to the global object, unless the
function is in strict mode, where this is undefined.
Inside an object method, this refers to the object that owns the method.
In a constructor function or class, this refers to the new object being
created.
In an event listener, this refers to the element that triggered the event.

Because this is determined by the function's call context, it changes


depending on how the function is used.

StevenCodeCraft.com
Page 505
StevenCodeCraft.com

209) What are the main differences between how this behaves in regular
functions and arrow functions in JavaScript?
The main difference between how this behaves in regular functions and
arrow functions is that:

In regular functions, this is set based on how the function is called. It can
refer to the global object, an object method, or something else,
depending on the context.
In arrow functions, this doesn’t change based on how or where the
function is called. Instead, it inherits this from the surrounding (or
enclosing) scope where the arrow function was defined. Arrow functions
don’t have their own this context.

This makes arrow functions useful when you want to maintain the this value
from the outer scope.

StevenCodeCraft.com
Page 506
StevenCodeCraft.com

210) In what scenarios would you use bind(), call(), or apply() to explicitly set
the value of this in JavaScript?
You would use bind(), call(), or apply() to explicitly set the value of this in
JavaScript when you want to control the context in which a function is
executed.
Here’s when to use each:

bind(): Use bind() when you want to create a new function with this set
to a specific value, but you don’t want to call the function immediately. It
returns a new function that you can call later.
call(): Use call() when you want to invoke a function immediately and set
this to a specific value. You pass arguments to the function individually.
apply(): Similar to call(), but use apply() when you want to invoke a
function immediately and pass arguments as an array.

These methods are helpful when you want to use a function in different
contexts, but need to control what this refers to during the function's
execution.

StevenCodeCraft.com
Page 507
StevenCodeCraft.com

211) How does the behavior of this change when using strict mode, and what
are the implications for function calls?
In strict mode, the behavior of this changes in that it does not default to the
global object. Instead:

In regular functions, if this is not explicitly set, it will be undefined in


strict mode. In non-strict mode, it would default to the global object (like
window in browsers).
In methods and objects, strict mode doesn’t change how this works—it
will still refer to the object the method belongs to.

The implication is that in strict mode, you need to ensure this is properly set
when calling functions, or else it will be undefined, which can prevent
unintentional references to the global object. This makes your code safer and
less prone to errors.

StevenCodeCraft.com
Page 508
StevenCodeCraft.com

212) How does the this keyword function differently in event listeners, and
why is understanding this behavior important when working with DOM
events?
n event listeners, the this keyword refers to the element that the event is
attached to. For example, if you add a click event to a button, this inside the
event handler will refer to that button. Understanding this is important
because it lets you interact with the specific element that triggered the event.
For instance, you can change its style or properties based on user
actions. However, if you use an arrow function inside the event listener, this
will behave differently. It won’t refer to the element but instead inherit this
from the surrounding context, which could be the global object or something
else. This difference is crucial when working with DOM events, as it affects
how you interact with elements in response to user actions.

StevenCodeCraft.com
Page 509
StevenCodeCraft.com

Understanding Classes and


Prototypal Inheritance in JavaScript
In JavaScript, inheritance is based on a model known as Prototypal
Inheritance.

Unlike classical inheritance found in languages like C# or Java, JavaScript uses


objects to inherit properties and methods directly from other objects,
creating a prototype chain.

Prototypal Inheritance
In JavaScript, objects inherit from other objects, not from class blueprints.
This is a key difference from classical inheritance. When a property is not
found on an object, JavaScript will search for it on the object's prototype. If
it's not found there, it continues searching up the prototype chain until it
reaches null, which ends the chain.

Prototype Chain
The prototype chain is how JavaScript handles inheritance. If a property or
method is not found on an object, JavaScript looks at the object's prototype,
and continues up the chain of prototypes until it either finds the property or
reaches a null prototype.

StevenCodeCraft.com
Page 510
StevenCodeCraft.com

We can interact with prototypes in several ways:

Object.getPrototypeOf(obj): Returns the prototype of the object.


Object.setPrototypeOf(obj, proto): Sets the prototype of the object.
Object.create(proto): Creates a new object with a specified prototype

Function Constructors
Before ES6 classes, function constructors were used to create objects and set
their prototypes.

Here’s an example using a Vehicle constructor:

When you use the new keyword with a constructor function, a new object is
created, and its prototype is set to the constructor's prototype.

StevenCodeCraft.com
Page 511
StevenCodeCraft.com

Modern Class Syntax


JavaScript now provides a class syntax to simplify working with prototypes
and constructor functions. Under the hood, classes still rely on prototypes
but offer a cleaner syntax.

The describe() method is defined on the Vehicle prototype and can be used
by any instance of the class.

StevenCodeCraft.com
Page 512
StevenCodeCraft.com

Inheritance with Classes


lasses can extend other classes, creating a prototype chain similar to how
objects inherit from other objects.

In this example, ElectricVehicle inherits from Vehicle. The super() call in the
constructor allows ElectricVehicle to use Vehicle's constructor to initialize
type.

StevenCodeCraft.com
Page 513
StevenCodeCraft.com

Static Properties and Methods


Classes can have static properties and methods that belong to the class itself,
rather than its instances. Here's an example:

Static properties and methods are useful when the behavior is related to the
class itself and not individual instances.

StevenCodeCraft.com
Page 514
StevenCodeCraft.com

Private Fields
JavaScript classes support private fields using the # symbol. These fields
cannot be accessed or modified outside the class.

Private fields help encapsulate data, ensuring that sensitive information is


not accessible outside the class.

StevenCodeCraft.com
Page 515
StevenCodeCraft.com

Summary
Prototypal Inheritance: JavaScript objects inherit from other objects,
forming a prototype chain.
Constructor Functions: Before ES6, objects were created using
constructor functions and the new keyword.
Classes: Provide a simpler syntax for working with inheritance in
JavaScript, but are built on top of the prototype system.
Inheritance: Classes can extend other classes, allowing them to inherit
properties and methods.
Static Properties/Methods: Belong to the class itself, not the instances.
Private Fields: Ensure that certain data is encapsulated and inaccessible
from outside the class.

With these core concepts, you can create reusable, organized code using
JavaScript’s inheritance system.

StevenCodeCraft.com
Page 516
StevenCodeCraft.com

Active Recall Study Questions


213) What is the key difference between prototypal inheritance in JavaScript
and classical inheritance in languages like Java or C#?
214) How does the prototype chain work in JavaScript, and what happens
when a property is not found on an object?
215) What are some of the modern alternatives to the deprecated __proto__
property for getting and setting an object's prototype?
216) How does the new keyword function when creating an object with a
constructor function in JavaScript?
217) What is the role of the super keyword in class inheritance, and when
would you use it?
218) How do static properties and methods in JavaScript classes differ from
instance properties and methods?
219) What are private fields in JavaScript classes, and why are they useful for
encapsulating data?

StevenCodeCraft.com
Page 517
StevenCodeCraft.com

Active Recall Answers


213) What is the key difference between prototypal inheritance in JavaScript
and classical inheritance in languages like Java or C#?
The key difference is that prototypal inheritance in JavaScript allows objects
to inherit directly from other objects. In contrast, classical inheritance (found
in languages like Java or C#) uses blueprint-like classes that define how
objects are created. In JavaScript, you don't need classes for inheritance—
objects can inherit properties and methods from other existing objects
through prototypes. In classical inheritance, you create new objects based on
predefined class structures.

214) How does the prototype chain work in JavaScript, and what happens
when a property is not found on an object?
The prototype chain in JavaScript is a system where objects can inherit
properties and methods from other objects. When you try to access a
property on an object and it’s not found, JavaScript looks at the object’s
prototype. If the property isn’t found there, it keeps going up the chain of
prototypes until it either finds the property or reaches null, which ends the
chain. If the property is not found anywhere in the prototype chain, JavaScript
returns undefined.

StevenCodeCraft.com
Page 518
StevenCodeCraft.com

215) What are some of the modern alternatives to the deprecated __proto__
property for getting and setting an object's prototype?
Modern alternatives to the deprecated __proto__ property for getting and
setting an object's prototype in JavaScript are

1) Object.getPrototypeOf(obj): This is used to get the prototype of an object. It


safely returns the object's prototype without using the old __proto__ syntax.

2) Object.setPrototypeOf(obj, proto): This method is used to set the


prototype of an object to another object (proto). It allows you to change the
prototype in a modern, safer way.

These methods are preferred over __proto__ because they are more reliable
and future-proof.

StevenCodeCraft.com
Page 519
StevenCodeCraft.com

216) How does the new keyword function when creating an object with a
constructor function in JavaScript?
The new keyword in JavaScript is used to create a new object from a
constructor function. Here's how it works:

1. Creates a new object: A new empty object is automatically created.


2. Sets the prototype: The new object's prototype is set to the constructor
function's prototype property.
3. Binds this: Inside the constructor function, this refers to the new object,
allowing properties and methods to be added to it.
4. Returns the object: The new keyword automatically returns the new
object, unless the constructor explicitly returns something else.

In short, new helps create an object and sets it up with the correct prototype
and properties.

StevenCodeCraft.com
Page 520
StevenCodeCraft.com

217) What is the role of the super keyword in class inheritance, and when
would you use it?
The super keyword in JavaScript is used in class inheritance to call the
constructor or methods of a parent class.

In a constructor: You use super() to call the parent class's constructor


and pass any required arguments. This ensures the parent class is
properly initialized before adding new properties to the child class.
In methods: You use super.method() to call a method from the parent
class when the child class needs to extend or modify the behavior of
that method.

You would use super whenever you need to access or reuse functionality
from a parent class in a child class.

StevenCodeCraft.com
Page 521
StevenCodeCraft.com

218) How do static properties and methods in JavaScript classes differ from
instance properties and methods?
In JavaScript classes, static properties and methods belong to the class itself,
not to individual instances of the class. This means you can access them
directly from the class without creating an object (instance) of the class.

Static properties/methods: You access these directly on the class, like


ClassName.staticMethod().
Instance properties/methods: These are specific to each object created
from the class, and you access them through the individual instances
(objects), like instanceName.instanceMethod().

Static methods are used when functionality is related to the class as a whole,
while instance methods are for actions specific to an individual object.

StevenCodeCraft.com
Page 522
StevenCodeCraft.com

219) What are private fields in JavaScript classes, and why are they useful for
encapsulating data?
Private fields in JavaScript classes are variables that are only accessible inside
the class where they are defined. They are marked with a # symbol and
cannot be accessed or modified from outside the class.

Private fields are useful for encapsulating data, meaning you can keep certain
information hidden and control how it's accessed or changed. This helps
prevent accidental or unwanted changes to important data from outside the
class.

StevenCodeCraft.com
Page 523
StevenCodeCraft.com

Understanding Currying in JavaScript


Currying is the process of transforming a function so that it takes its
parameters one at a time, in a sequence of individual function calls.

For example, a function func(a, b, c) can be turned into func(a)(b)(c).ext to


edit.

This is achieved by creating functions that return other functions, using


closures to keep track of the parameters.

Curried Sum Function


A basic curried function can be written like this:

In this example, calling curriedSum(4) returns a new function that takes the
second argument and adds it to 4. This allows us to create partially applied
functions like:

StevenCodeCraft.com
Page 524
StevenCodeCraft.com

Currying a Multi-Parameter Function


Let's say we have a regular function like this:laceholder - click this text to edit.

To transform this function into a curried version where each parameter is


passed one at a time, we create nested functions:

StevenCodeCraft.com
Page 525
StevenCodeCraft.com

Now, we can call it as:

General Currying Function


We can make currying more flexible by writing a generic curry function that
works for any function with three parameters:

This curry function can now be used to create curried versions of other
functions as well, like a subtraction function:

StevenCodeCraft.com
Page 526
StevenCodeCraft.com

Simplifying the Curried Function


Since each function is a single line, we can simplify the curried function using
arrow functions with implicit returns:

StevenCodeCraft.com
Page 527
StevenCodeCraft.com

Why Use Currying?


Currying is not something you'll use all the time, but it can be useful in certain
scenarios. One use case is when you want to create a function that "saves"
part of its arguments for later use. For example, if you have a function that
adds two numbers to a fixed value, currying allows you to simplify it:his text
to edit.

With currying, you can break this down:

StevenCodeCraft.com
Page 528
StevenCodeCraft.com

Here, addFour is a partially applied version of the curriedSum function, where


the first argument is fixed as 4. Currying helps you create flexible, reusable
functions by allowing you to store some arguments for later use.

Conclusion
Currying transforms a function so that it can take its arguments one by one.
This approach can be useful for creating partial functions and breaking
complex function calls into smaller, more manageable pieces. Though not
always needed, it offers flexibility in how you use and structure your
functions.

Active Recall Study Questions


220) What is currying in JavaScript, and how does it transform the way
functions handle their parameters?
221) How does currying make use of closures in JavaScript to handle multiple
function calls?
222) In what scenarios might currying be useful when designing functions in
JavaScript?
223) What is the benefit of using a generic curry function, and how does it
improve function flexibility?

StevenCodeCraft.com
Page 529
StevenCodeCraft.com

Active Recall Answers


220) What is currying in JavaScript, and how does it transform the way
functions handle their parameters?
Currying in JavaScript is a technique that transforms a function so that it
takes its parameters one at a time, instead of all at once. For example, a
regular function takes three arguments like func(a, b, c).
After currying, this function becomes func(a)(b)(c), where each call returns a
new function that takes the next argument.
This transformation allows you to “save” some arguments and pass the rest
later, making functions more flexible and reusable.

221) How does currying make use of closures in JavaScript to handle multiple
function calls?
Currying uses closures to handle multiple function calls by keeping track of
the arguments passed in earlier calls. When you call a curried function, it
returns a new function for the next argument, and each of these returned
functions “remembers” the previous arguments through closures.
This means the function can “hold onto” values until all the arguments are
provided, and then it processes them together. Closures allow each function
in the chain to access the variables from its surrounding context, which is
how currying works in JavaScript.

StevenCodeCraft.com
Page 530
StevenCodeCraft.com

222) In what scenarios might currying be useful when designing functions in


JavaScript?
Currying can be useful in scenarios where you want to create more flexible
and reusable functions. Some examples include:

1. Partial application: You can “preset” some arguments in a curried


function and reuse it later with different values for the remaining
arguments. For example, you could create a function that always adds a
fixed number to another number.
2. Configuration: When you need to configure a function with some values
up front and apply the rest of the values later. This is helpful in settings
like event handling or API calls where certain parameters stay constant.
3. Function composition: Currying makes it easier to break complex
functions into smaller, simpler pieces that can be combined or reused
in different ways.

By splitting function calls, currying provides more control and flexibility over
hows and when arguments are applied.

StevenCodeCraft.com
Page 531
StevenCodeCraft.com

223) What is the benefit of using a generic curry function, and how does it
improve function flexibility?
A generic curry function allows you to take any function and transform it into
a curried version. This means the function can accept its arguments one at a
time, making it more flexible. The main benefit is that you can partially apply
the function. For example, you can pass some arguments now and save the
rest for later. This improves flexibility because you can reuse the curried
function in different contexts with different sets of arguments, without
rewriting the original function. It also helps break down complex function
calls into simpler steps, making your code more modular and easier to
maintain.

StevenCodeCraft.com
Page 532
StevenCodeCraft.com

Understanding Generators in
JavaScript
Generators are a special type of function in JavaScript that allow you to create
iterable objects. They are defined using the function* syntax and use the yield
keyword to pause and resume execution.

How Generators Work


A generator function allows you to yield values one by one. Each time you call
next() on the generator object, it resumes execution until the next yield
statement or the end of the function.

The generator object provides three methods:

1) next(value)
This method resumes the generator function and returns an object with two
properties:

value: the value yielded by the generator.

done: a boolean indicating whether the generator has finished.

StevenCodeCraft.com
Page 533
StevenCodeCraft.com

2) return(value)
This method stops the generator, returning the passed value and marking
done as true.

3) throw(error)
This method throws an error inside the generator, halting its execution
unless the error is caught within the generator function.

Basic Generator

Each next() call returns the next value and indicates whether the generator is
done.

StevenCodeCraft.com
Page 534
StevenCodeCraft.com

Passing Values into Generators


You can pass values back into a generator through the next() method. Here's
how that works:

In this example, the value 5 is passed back into the generator, replacing the
yield's previous value, and it’s used in the next yield statement.

Using return and throw in Generators


You can also use return() to end the generator early and provide a final value:

console.log(generator.return(5)); // { value: 5, done: true }

After calling return(), the generator is considered done, and further next()
calls will return { value: undefined, done: true }.

StevenCodeCraft.com
Page 535
StevenCodeCraft.com

The throw() method can be used to throw an error inside the generator:

console.log(generator.throw(new Error('Error!')));

This will stop the generator unless the error is caught within a try-catch block
inside the generator function.

Delegating Generators with yield*


Generators can delegate control to other generators using the yield* syntax.
This allows you to combine multiple generators into one:

StevenCodeCraft.com
Page 536
StevenCodeCraft.com

In this example, genNumbers delegates part of its execution to generator1


and generator2.

Iterating Over Generators


You can use a for...of loop to iterate over all the values yielded by a generator:

Conclusion
Generators are a useful feature in JavaScript for creating iterable objects.

Although not used often in everyday programming, they are valuable for
situations where you need to control the flow of execution or manage
complex iterations.

It's important to understand how generators work, especially since they may
come up in technical interviews. text to edit.

StevenCodeCraft.com
Page 537
StevenCodeCraft.com

Active Recall Study Questions


224) What is a generator in JavaScript, and how is it different from a regular
function?
225) How does the yield keyword work in a generator, and what role does it
play in pausing and resuming function execution?
226) What is the purpose of the next(), return(), and throw() methods in
generator objects, and how do they control the generator's behavior?
227) In what scenarios might you use the yield* syntax in a generator, and
how does it allow delegation to other generators?

StevenCodeCraft.com
Page 538
StevenCodeCraft.com

Active Recall Answers


224) What is a generator in JavaScript, and how is it different from a regular
function?
A generator in JavaScript is a special type of function that can pause its
execution and resume later. It's different from a regular function because,
instead of running from start to finish in one go, a generator can yield values
one at a time and pick up where it left off when you call its next() method.
Regular functions run completely once called, while generators can stop,
return intermediate results, and continue running later.

225) How does the yield keyword work in a generator, and what role does it
play in pausing and resuming function execution?
The yield keyword in a generator pauses the function's execution and returns
a value. When the generator is called again with next(), it resumes from where
it left off after the last yield. This allows the generator to pause and resume
multiple times, returning different values each time.

StevenCodeCraft.com
Page 539
StevenCodeCraft.com

226) What is the purpose of the next(), return(), and throw() methods in
generator objects, and how do they control the generator's behavior?
The methods next(), return(), and throw() in generator objects control the
generator's behavior:

next(): Resumes the generator's execution from where it last paused


and returns the next value. It also indicates if the generator is done.
return(): Ends the generator early and returns a specified value, marking
the generator as done.
throw(): Throws an error inside the generator, halting its execution
unless the error is caught within the generator.

These methods let you manage how the generator runs and when it stops.

227) In what scenarios might you use the yield* syntax in a generator, and
how does it allow delegation to other generators?
You use the yield* syntax in a generator when you want to delegate control
to another generator or iterable. This means the generator temporarily
hands over execution to another generator, yielding all of its values before
continuing with its own code. This is useful when you want to combine
multiple generators or iterables into one seamless sequence without
manually managing each one.

StevenCodeCraft.com
Page 540
StevenCodeCraft.com

Understanding Modules in
JavaScript
Modules in JavaScript allow you to organize code in separate files without
polluting the global namespace. They help keep variables and functions
scoped to their own files, preventing naming conflicts and other issues that
arise from globally accessible variables.

Traditionally, Immediately Invoked Function Expressions (IIFE) were used to


isolate code, but in modern JavaScript, we can use the type="module"
attribute in <script> tags to achieve this more easily.

Key Features of type="module"


When you use type="module" in a <script> tag, several important things
happen:

Scoped to the file: Variables and functions declared at the top level are
scoped to the file, not globally.
Strict mode by default: Modules run in strict mode automatically, so you
don’t need to add "use strict".
Top-level await: You can use the await keyword at the top level, without
wrapping it in an async function.
Deferred execution: Scripts with type="module" are automatically
deferred, meaning they wait for the HTML to be fully parsed before
running.

StevenCodeCraft.com
Page 541
StevenCodeCraft.com

Modules can access each other using the import and export keywords.

Example: Using import and export


Here’s an example of how you can export and import variables and functions
between module files.

This allows you to keep your code modular and organized, only importing the
parts you need from other files.

StevenCodeCraft.com
Page 542
StevenCodeCraft.com

Immediately Invoked Function Expressions (IIFE)


Before modules, Immediately Invoked Function Expressions (IIFE) were used
to isolate code and avoid polluting the global namespace.

An IIFE looks like this:

The function is defined and immediately called. This creates a local scope for
your code, preventing variables from being accessible globally. While useful in
older code, modules are now the preferred way to achieve this in modern
JavaScript.

StevenCodeCraft.com
Page 543
StevenCodeCraft.com

Importing and Exporting in Modules


With modules, you can explicitly decide which functions, variables, or classes
to export from one file and import into another.

Exporting
Named exports: You can export multiple items from a file, and they must be
imported with the same names.

Default exports: Each module can have one default export, which can be
imported with any name.

StevenCodeCraft.com
Page 544
StevenCodeCraft.com

Importing
You can import values from a module using their names:

import { maxLimit, calculate } from './helpers.js';

For default exports, you can use any name when importing:

import Calculator from './helpers.js';

Dynamic Imports
Sometimes, you might want to load a module only when it's needed. You can
use dynamic imports for this:

Dynamic imports are useful when you want to load code only when it's
necessary, helping optimize the performance of your web application.

StevenCodeCraft.com
Page 545
StevenCodeCraft.com

Modules and Compatibility


To ensure compatibility with older browsers that don’t support modules, you
can include a fallback using the nomodule attribute:

<script src="oldScript.js" nomodule></script>

Browsers that don’t support modules will load this script, while modern
browsers will ignore it.

Summary
Modules in JavaScript help organize code and prevent global namespace
pollution. By using import and export, you can share specific functions,
variables, or classes between files without making everything globally
accessible. Modules also bring modern features like automatic strict mode,
top-level await, and deferred execution, making JavaScript code more efficient
and maintainable.

StevenCodeCraft.com
Page 546
StevenCodeCraft.com

Active Recall Study Questions


228) What are the main benefits of using JavaScript modules compared to
traditional scripts in managing code? s text to edit.
229) How does using type="module" in a script tag help prevent global
namespace pollution in JavaScript?
230) What is the difference between named exports and default exports in
JavaScript modules, and when would you use each?
231) What are dynamic imports in JavaScript, and why might they be useful
for optimizing application performance?

StevenCodeCraft.com
Page 547
StevenCodeCraft.com

Active Recall Answers


228) What are the main benefits of using JavaScript modules compared to
traditional scripts in managing code?
The main benefits of using JavaScript modules are:

Prevents global namespace pollution: Modules keep variables and


functions scoped to their own file, avoiding conflicts with other code.
Better organization: Modules allow you to split code into smaller,
reusable pieces, making it easier to manage.
Explicit imports and exports: You can control what functions, variables,
or classes are shared between files, reducing accidental access to
unnecessary code.
Automatic strict mode: Modules automatically run in strict mode,
making your code safer by catching common mistakes.

229) How does using type="module" in a script tag help prevent global
namespace pollution in JavaScript?
Using type="module" in a script tag helps prevent global namespace pollution
by automatically scoping variables and functions to the file they are defined
in. This means they are not accessible globally, reducing the chances of
naming conflicts and keeping your code isolated and organized. Only the
functions or variables you explicitly export will be available for import in other
files.

StevenCodeCraft.com
Page 548
StevenCodeCraft.com

230) What is the difference between named exports and default exports in
JavaScript modules, and when would you use each?
The difference between named exports and default exports in JavaScript is:

Named exports allow you to export multiple items from a file, and they
must be imported using their exact names.
Default exports allow you to export a single item, which can be imported
with any name you choose.

You use named exports when you want to export multiple things from a file,
and default exports when you want to export a single, main item.

231) What are dynamic imports in JavaScript, and why might they be useful
for optimizing application performance?
Dynamic imports in JavaScript allow you to load modules only when they are
needed, rather than at the start of the program. This can help optimize
application performance by reducing the initial load time, as parts of the
code are loaded only when required, improving efficiency for large
applications. You can use dynamic imports conditionally or on-demand, such
as when a user interacts with a specific feature.

StevenCodeCraft.com
Page 549
StevenCodeCraft.com

The Event Loop


A key concept that explains how JavaScript handles asynchronous tasks and
manages concurrency.

JavaScript Engine
Each browser has a JavaScript engine, like Chrome's V8 engine, which is
responsible for executing JavaScript code. The engine has two primary
components:

Heap
This is where memory is allocated to store objects. It's an unstructured data
store used whenever your code needs to allocate memory, such as for arrays
or objects.

Call Stack
This is a stack data structure that tracks function calls. When a function is
called, a stack frame is pushed onto the stack. When the function finishes, it's
popped off the stack. The call stack is crucial for keeping track of which
function is currently being executed.

StevenCodeCraft.com
Page 550
StevenCodeCraft.com

JavaScript Runtime Environment


JavaScript doesn't run in isolation. It operates within the JavaScript Runtime
Environment, which provides access to Web APIs like setTimeout(), fetch(),
and DOM manipulation methods. These APIs allow JavaScript to interact with
the web and perform tasks like making HTTP requests or updating the user
interface.

Event Loop
The Event Loop is the mechanism that allows JavaScript to perform non-
blocking operations, even though it runs on a single thread. Here's how it
works:

1) Task Queue
This is a queue where asynchronous callbacks (like those from setTimeout())
are placed after being handled by Web APIs. It's also known as the Message
Queue or Callback Queue.

2) Microtask Queue
This queue holds microtasks, which are callbacks from promises (then(),
catch(), finally()) or manually added tasks using queueMicrotask(). Microtasks
have higher priority than tasks in the Task Queue.

StevenCodeCraft.com
Page 551
StevenCodeCraft.com

3) Event Loop Process


1. Dequeue one task from the Task Queue.
2. Execute the task until the call stack is empty.
3. Execute all microtasks from the Microtask Queue until it's empty.
4. Render any changes to the DOM.
5. Repeat from step 1.

Chunking
Chunking is a technique used to prevent long-running tasks from blocking the
Event Loop and making the page unresponsive. The idea is to break up large
tasks into smaller chunks using setTimeout(). This allows the Event Loop to
process other tasks and microtasks between these chunks, keeping the UI
responsive.

Why This Matters


Understanding the Event Loop is crucial because it helps you write more
efficient and responsive JavaScript code. Slow tasks can block the page from
updating, delay promise callbacks, and make timers less precise. By using
techniques like chunking, you can avoid these issues and ensure your web
applications run smoothly.

StevenCodeCraft.com
Page 552
StevenCodeCraft.com

Active Recall Study Questions


232) What are the two primary components of the JavaScript engine, and
what are their functions?
233) What is the event loop?
234) How does the event loop handle tasks and microtasks differently in
JavaScript?
235) What is chunking, and why is it important for maintaining a responsive
user interface in JavaScript?

StevenCodeCraft.com
Page 553
StevenCodeCraft.com

Active Recall Answers


232) What are the two primary components of the JavaScript engine, and
what are their functions?
The two primary components of the JavaScript engine are:
Heap: A memory allocation area used to store objects and data. It’s an
unstructured data store where memory is allocated as needed by your code.
Call Stack: A stack data structure that keeps track of function calls. Each time
a function is called, a stack frame is pushed onto the stack. When the function
finishes, the frame is popped off. The call stack helps the engine know which
function is currently being executed.

233) What is the event loop?


The Event Loop is a mechanism in JavaScript that manages the execution of
asynchronous tasks. It allows JavaScript, which runs on a single thread, to
perform non-blocking operations by continuously checking the task queue
and executing tasks when the call stack is empty. The Event Loop processes
tasks, handles microtasks, and ensures the browser updates the user
interface efficiently, enabling JavaScript to handle multiple operations
seemingly at the same time.

StevenCodeCraft.com
Page 554
StevenCodeCraft.com

234) How does the event loop handle tasks and microtasks differently in
JavaScript?
The Event Loop handles tasks and microtasks differently in JavaScript:
Tasks: These are placed in the Task Queue and include events like
setTimeout() callbacks or user interactions.
The Event Loop processes one task at a time, only after the call stack is
empty.
Microtasks: These are placed in the Microtask Queue and include promise
callbacks (then(), catch(), finally()) and queueMicrotask() functions.
Microtasks have higher priority than tasks; the Event Loop processes all
microtasks before moving on to the next task in the Task Queue.
This prioritization ensures that microtasks are handled immediately after the
current code execution, making them more responsive.

235) What is chunking, and why is it important for maintaining a responsive


user interface in JavaScript?
Chunking is a technique used in JavaScript to break up large, time-consuming
tasks into smaller pieces. By using setTimeout() or similar methods, each
chunk of the task is processed separately, allowing the Event Loop to handle
other tasks and update the user interface in between chunks. This prevents
the main thread from being blocked by long-running tasks, ensuring that the
UI remains responsive and users can interact with the page smoothly

StevenCodeCraft.com
Page 555
StevenCodeCraft.com

You might also like