Table of Contents
- Node
- Vars, Functions, Strings
- Control Flow
- Loops
- Arrays
- Objects
- Callbacks & Higher Order Functions
- Array Higher Order Methods
- Regex
- Two places to run JavaScript: Node and the Browser
- Run
.js
files using Node with the terminal commandnode <filename.js>
- A module is a file that exports code to be reused in other files.
- A
.js
file can export values by assigning them tomodule.exports
// math-utilities.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
const SIMPLE_PI = 3.14;
// export these values in an Object
module.exports = {
add, subtract, SIMPLE_PI
}
- Another
.js
file can import those values using therequire()
function and providing a relative path.
// index.js
// destructure as "named" imports
const { add, subtract, SIMPLE_PI } = require('./math-utilities');
// or import the entire object as a "default" import
const mathUtils = require('./math-utilities');
console.log(add(5, 3)) // 8
console.log(mathUtils.add(5, 3)) // 8
- Often, we will destructure a module's exports immediately. This is called a "named export/import".
Q: What are the benefits or putting our code into modules?
Answer
"Modularizing" our code enables us separate the concerns of our application for better organization and maintainability. It also allows us to easily re-use exported logic, minimizing repetitive code.
- A package is a group of one or more modules published on the internet for use by other developers.
- Node Package Manager makes it easy to install and manage third-party packages.
- Packages published on NPM all have a
package.json
file that lists the package's dependencies, scripts, and metadata.
{
"name": "1.0.0-assignment",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"preinstall": "cp hooks/pre-commit .git/hooks/pre-commit",
"play": "node playground.js",
"start": "node index.js",
"lint": "eslint .",
"test": "jest --coverage",
"test:w": "jest --watchAll"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"eslint": "^8.34.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.27.5",
"jest": "^29.4.3",
"score-tests": "^1.0.0"
}
}
- A dependency is a package relied upon by another package. They can be installed using the
npm i
command
npm init # creates a package.json file
npm i prompt-sync # installs prompt-sync as a dependency
npm i -D jest # installs Jest as a devDependency
npm i -g nodemon # installs nodemon as a global dependency
- When importing modules installed from NPM,
require()
only needs the package name (not a relative path)
// npm module import
const prompt = require('prompt');
// local module import
const mathUtils = require('./math-utilities');
- When just the name is given,
require()
knows to look for NPM modules instead of local modules.
Q: When installing a package via npm
, where does it go?
Answer
Into a folder called node_modules/
in your project's root directory (or wherever the package.json
file is).
- NPM Scripts are defined in a package's
package.json
file and help you quickly execute a terminal command
"scripts": {
"preinstall": "cp hooks/pre-commit .git/hooks/pre-commit",
"play": "node playground.js",
"start": "node index.js",
"lint": "eslint .",
"test": "jest --coverage",
"test:w": "jest --watchAll"
},
- To run a script, enter
npm run <script name>
- Example:
npm run play
- Example:
- Some designated scripts can omit the "run" argument, such as:
npm start
npm test
Q: The "lint"
script is not a designated script name. How would you run it? What command is executed by that script?
Answer
npm run lint
which executes the command eslint .
- Jest is a testing framework for JavaScript.
- All testing files end in
.spec.js
. - A test in Jest is made by invoking
it()
with a test name and callback. The callback hasexpect()
statements that all must be true for the test to pass.
it('exports a named function called `add` from math-utilities.js', () => {
const { add } = require('./math-utilities');
expect(typeof add).toEqual('function');
});
it('returns the sum of two input numbers', () => {
const { add } = require('./math-utilities');
expect(add(5, 3)).toEqual(8);
expect(add(1, 2)).toEqual(3);
expect(add(100, 200)).toEqual(300);
});
-
Every test should test one piece of functionality, though it may require multiple
expect()
statements to do so. -
A collection of tests is called a test suite
-
Run
npm test
to see the results of a test file. Below is a test suite with all tests passing
- A failing test will show the
expect()
statement that failed as well as a comparison of what it "expected" vs. what it "received":
Q: In the failing test above, what mistake did the programmer make?
Answer
It seems as though the programmer forgot to put a comma ,
after the provided name as well as a question mark ?
at the end of the string.
- A variable "binds" a piece of data in memory to an "identifier" (the variable's name)
- Use
let
orconst
to declare a new variable (don't usevar
)
let age = 18; // let declares a reassignable variable
const PI = 3.14; // const declares a non-reassignable variable
age = 19; // Allowed
PI = 3.14159; // TypeError: cannot reassign const variables
- Variables must be referenced AFTER they are declared. Otherwise, a
ReferenceError
will be thrown.
Q: You are writing a program that involves a variable called clickCount
that will keep track of how many times a user clicks on a button. Should clickCount
be declared with let
or const
?
Answer
clickCount
should be declared with the let
keyword because we can assume that it will be reassigned each time the user clicks on the button.
- Variables can store data of any kind data type: String, Number, Boolean, Undefined, Null, Function, or Object.
- Primitive Data Types are String, Number, Boolean,
undefined
andnull
.
// Strings are text wrapped in 'single quotes', "double quotes",or `backticks`
const phrase = "Hello World!";
const name = 'ben';
const message = `My name is ${name}. I say ${phrase}`; // backticks allow for "String Interpolation"
// Numbers can be positive, negative, or include a decimal point
const PI = 3.14159;
const age = 18;
const countdown = -3;
// Booleans are either `true` or `false`
const canDrive = true;
const canFly = false;
// Null is a non-value that is explicitly set by the programmer
let toBeDetermined = null;
// Undefined is a non-value that is automatically set for variables that are not assigned a value:
let emptyVariable;
Q: You are building a social media app where users have an email, a follower count, and an isAdmin
status. Which data type would you use to represent each of these values?
Answer
- email: String
- follower count: Number
isAdmin
: Boolean
Q: What are the similarities and differences between null
and undefined
?
Answer
null
and undefined
both represent non-values but null
must be explicitly assigned while undefined
is automatically assigned to variables/parameters without a value.
- A function is a block of code bound to a variable.
- An arrow function is written
(parameter) => { code block }
and is stored in a variable.
- An arrow function is written
- After a function is declared, it can be invoked by ptting
()
after the function's name. Doing so causes the function's code block to execute.
const add = (a, b) => { // arrow function definition
const sum = a + b;
return sum;
}
const sum1 = add(5,3); // function invocation
const sum2 = add(1,2);
- A parameter is a variable serving as a placeholder for data passed into a function.
- An argument is a value "passed" to a function during a function invocation.
- One argument must be provided for each parameter in the function definition.
- When a function is invoked, the invocation will evaluate to a value determined by the function's
return
statement (or it will returnundefined
if not specified).
Q: What will be printed to the console after executing this program? What value will result
hold?
const greet = (name) => {
const message = `Hello ${name}, how are you?`;
console.log(message);
}
const result = greet('ben');
Answer
result
will hold the value undefined
because greet
doesn't have a return
statement. As a result, the function will return undefined
by default when it is invoked.
As a side-effect, the invocation of greet('ben')
will print "Hello ben, how are you?"
to the console.
- A function can also be created using the function declaration syntax. It can be identified by the use of the
function
keyword.
greet(); // this weirdly works
function greet(name) {
console.log(`Hello ${name}, how are you?`);
}
- Functions created using this syntax are hoisted (like variables declared with
var
) and are thus invocable before their declaration appears in the file. - For consistency, this should be avoided
let
- reassignable, hoisted, block scoped
const
- not reassignable, not hoisted, block scoped
var
- reassignable, hoisted, function scoped
- Scope refers to where a variable can be referenced
let
andconst
variables are block scoped — they are reference-able only within the code block where the variable is declared (loops,if
/else
statements, functions)
let a = "A globally scoped variable"
const myScopedFunction = () => {
let b = "I can be seen ANYWHERE in this function";
if (true) {
let c = "I'm only visible inside this if statement";
console.log(a, b, c); // We can reference all 3 values here
}
console.log(a, b); // but we can't reference c here
}
console.log(a); // and we can only reference a here
var
variables are function scoped — they are reference-able anywhere within the function where the variable is declared (including nested blocks)var
variables are "hoisted to the top of the function scope" - you can reference the variable "before" it is declared (but it will holdundefined
until it is assigned)
const foo = () => {
console.log(a); // undefined, no error
if (true) {
var a = 'hi';
}
console.log(a); // hi
}
- Variables declared without any keyword are globally scoped, regardless of where they are declared. They are not hoisted. This should always be avoided.
Q: Why is scope important?
Answer
- Scope helps reduce memory usage by effectively "throwing away" a variable once we leave the scope of that variable.
- Scope can also reduce variable name conflicts. We will often use common variable names for repeated tasks, for example: declaring the variable
i
for afor
loop. Without scope, everyfor
loop would need to use a different variable name to avoid conflicts.
Q: Consider the code below. Where are x
, a
, b
, and c
available?
const myFunc = () => {
console.log(a, b, c, x); // 1
if (true) {
const a = '5';
let b = 10;
var c = true;
x = 'hi'
console.log(a, b, c, x); // 2
}
console.log(a, b, c, x); // 3
}
myFunc();
console.log(a, b, c, x); // 4
Answer
a
andb
are only available at position 2 (within theif
statement) because they are block-scoped.c
is function scoped and hoisted so it is available at position 1 (with the valueundefined
) and positions 2 and 3 with the valuetrue
.x
is a globally scoped variable and is available at positions 2, 3, and 4 (it is not hoisted to position 1)
- A string is a series of characters wrapped in quotations ('', "", or ``)
- Each character in a string has an index - a number indicating the character's position in the string, starting at
0
. - Bracket Notation can be used to read the character at the given index:
console.log("Hello"); // Hello
console.log("Hello"[0]); // H
console.log("Hello"[4]); // o
console.log("Hello"[5]); // undefined
- Every string has a
.length
property which returns the number of characters in the string:
console.log("Hello".length); // 5
const name = 'Ben';
const lastChar = name[name.length - 1];
- Every string has methods for manipulating the string. A method is a function that is invoked direclty "on" a data value:
console.log("Hello".indexOf("H")); // 0
console.log("Hello".indexOf("o")); // 4
console.log("Hello".includes("ell")); // true
console.log("Hello".includes("h")); // false
console.log("Hello".toUpperCase()); // HELLO
console.log("Hello".toLowerCase()); // hello
console.log("Hello".slice(1, 3)); // el
console.log("Hello".slice(1)); // ello
console.log("Hello".slice()); // Hello
- These are some of the most common String methods.
Q: Write a function called startsWith
that takes in a string and a character and returns true
if the string starts with that character.
Answer
const startsWith = (str, char) => {
return str[0] === char;
// or
// return str.startsWith(char);
}
Q: Write a function called capitalize
that takes in a string and returns a copy of that string with the first letter capitalized and the rest in lowercase.
Answer
const capitalize = (str) => {
return str[0].toUpperCase() + str.slice(1).toLowerCase();
}
- A conditional statement is a block of code that will or will not run depending on the outcome of a Boolean expression (an expression that resolves to
true
orfalse
) - Boolean Expressions can be created using comparison operators:
< <= > >= === !==
- A conditional statement begins with an
if
statement, can be followed by 0 or moreelse if
statements, and can conclude with anelse
statement.
const greet = (name, tone) => {
if (tone === 'happy') {
console.log(`Hey ${name}, it is great to see you!`);
} else if (tone === 'grumpy') {
console.log(`Oh, it's you ${name}...`);
} else if (tone === 'country') {
console.log(`Howdy ${name}!`);
} else {
console.log(`Hi ${name}.`);
}
}
greet('ben', 'happy'); // Hey ben, it is great to see you!
greet('ben', 'grumpy'); // Oh, it's you ben...
greet('ben', 'country'); // Howdy ben!
greet('ben'); // Hi ben.
- Only the first statement in a chain of
if
/else if
/else
statements whose condition evaluates totrue
will be executed. - An
else
statement is only executed if all the priorif
andelse
if statements all evaluate tofalse
. - Truthy and Falsey values are non-Boolean values that will evaluate to
true
/false
when placed in anif
statement. - Falsey values are "empty" or "blank" or "non values":
- An empty string
""
- The number
0
- The number
NaN
undefined
null
- An empty string
- All other values are truthy.
Q: Write the classic fizzBuzz
function! It should accept a number and log to the console "fizz"
if the number is divisible by 3
, "buzz"
if divisible by 5
, and "fizzbuzz"
if divisible by both. If not divisible by any of these, print the input number itself. Do not return anything.
Answer
const fizzBuzz = (num) => {
if (num % 3 === 0 && num % 5 === 0) {
console.log("fizzbuzz");
} else if (num % 3 === 0) {
console.log("fizz");
} else if (num % 5 === 0) {
console.log("buzz");
} else {
console.log(num);
}
}
We can also take advantage of the "falsey-ness" of the number 0
to rewrite the conditions in this function:
const fizzBuzz = (num) => {
if (!(num % 3) && !(num % 5)) {
console.log("fizzbuzz");
} else if (!(num % 3)) {
console.log("fizz");
} else if (!(num % 5)) {
console.log("buzz");
} else {
console.log(num);
}
}
If the remainder is 0
, then we flip its truthyness using !
.
- A guard clause is an
if
statement used at the start of a function that returns immediately. - Guard clauses are used to "fail fast" — if the current state (data) does not allow the rest of the function to operate properly, we exit early.
- Consider this example:
const add = (a, b) => {
if (typeof a !== 'number' || typeof b !== 'number') { // guard clause
return NaN;
} else { // do we need this else statement?
return a + b;
}
}
- In this example, we want to avoid adding non-number values. Strings, for example, will concatenate with the
+
operator, which we'd like to avoid. - We can refactor (rewrite) this function without the
else
statement:
const add = (a, b) => {
if (typeof a !== 'number' || typeof b !== 'number') {
return NaN;
}
return a + b;
}
- If the guard clause's
return
statement is executed, the function will exit. Therefore, we are not at risk of accidentally executingreturn a + b
.
Q: Write a function called getType
that takes in a value and returns the type of that value as a string, including the types "NaN"
, "Array"
, and "null"
Answer
const getType = (value) => {
if (Array.isArray(value)) {
return "array";
}
if (Number.isNaN(value)) {
return "NaN";
}
if (value === null) {
return "null";
}
return typeof value;
}
Rather than using a chain of else if
statements, this solution uses a series of guard clauses.
-
Iteration is the repetition of a process, getting closer to some result each time.
-
for
loops are best used to repeat a process a known number of times.- The initialization is where the loop begins
- The condition must be
true
for the loop to continue - The increment determines what to change after each loop
- The body is what gets executed with each iteration of the loop.
const string = "hello";
for (let i = 0; i < string.length; i++) {
console.log(string[i], i);
}
// h 0
// e 1
// l 2
// l 3
// o 4
- An infinite loop is one in which the condition is ALWAYS
true
Q: Write an infinite loop
Answer
There are many ways to make an infinite loop:
// decrementing when we should be incrementing
for (let i = 0; i < 10; i--) {
console.log("it's infinite!");
}
// a condition that is always true!
for (let i = 0; i >= 0; i++) {
console.log("it's infinite!");
}
// or the weirdest of them all. This condition is always true too!
for ( ; true; ) {
console.log("it's infinite!");
}
while
loops are best used to repeat a process an unknown number of timesbreak
prematurely breaks out of a loopcontinue
prematurely goes to the iteration step of the loop
const prompt = require("prompt-sync")(); // for later
while (true) {
const input = prompt("Enter a number or q to quit: ");
if (input === "q") {
console.log("Bye!");
break; // exit the loop immediately
}
if (Number.isNaN(Number(input))) {
console.log("please enter a number");
continue; // loop again immediately
}
console.log(`${input}? That's a great number!`);
}
Q: What is an example of a program that would use a while
loop? Hint: one example has to do with Node.
Answer
The Node Read-Eval-Print-Loop (REPL) uses a while
loop to continuously ask for user input. Run this program by entering the command node
without any arguments into your terminal.
- A nested loop is a loop written inside the body of another loop. For each iteration of the outer loop, the inner loop will complete ALL of its iterations.
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 5; j++) {
console.log(`${i} - ${j}`);
}
}
Q: What does the nested loop above print to the console?
Answer
0 - 0
0 - 1
0 - 2
0 - 3
0 - 4
1 - 0
1 - 1
1 - 2
1 - 3
1 - 4
- An Array is a type of object that stores data in an ordered list.
- An Array is created using square brackets with comma-separated values:
const family = ['Wendy', 'Jon', 'Daniel'];
const scores = [95, 87, 79, 88];
const marcyStaff = [
{ name: 'ben', isInstructor: true },
{ name: 'gonzalo', isInstructor: true },
{ name: 'travis', isInstructor: false },
];
- Arrays have indexes and
.length
like strings- The first index is
0
- The last index is
array.length - 1
- The first index is
- To access a single value, use bracket notation:
array[index]
- Unlike Strings, Arrays are mutable - their contents can be modified.
- We can use bracket notation to reassign individual indexes in an Array
- We can reassign the
.length
property to "shorten" the Array
const family = ['Wendy', 'Jon', 'Daniel'];
console.log(family[0]); // Wendy
console.log(family.length); // 3
console.log(family[family.length - 1]); // Daniel
family[0] = 'William';
family[1] = 'Michael';
console.log(family); // ['William', 'Michael', 'Daniel'];
family.length = 1;
console.log(family); // ['William']
family.length = 0;
console.log(family); // []
- We can iterate through the contents of an Array with a
for
loop:
const family = ['Wendy', 'Jon', 'Daniel'];
for (let i = 0; i < family.length; i++) {
const familyMember = family[i];
console.log(`I am related to ${familyMember}`);
}
// I am related to Wendy
// I am related to Jon
// I am related to Daniel
Q: Clearly explain why array.length-1
is always the index of the last element in an Array
Answer
Every value of an Array has an index, starting with 0
. The first value in an Array is at index 0
. The second value in an Array is at index 1
. So, the n
th value in an Array is at index n-1
.
If there are 5 values in an Array, then the last value would be at index 5-1
or 4
.
const letters = ['a', 'b', 'c', 'd', 'e']
console.log(letters[4]); // 'e'
console.log(letters.length); // 5
console.log(letters[letters.length-1]); // 'e'
If an Array has arr.length
values in it, then the index of the last value will be arr.length - 1
- Arrays have a number of methods that mutate the contents of the Array:
const nums = [1, 2, 3]
nums.push(4); // adds to the end (the "tail")
console.log(nums); // [1, 2, 3, 4]
nums.unshift(0); // adds to the beginning (the "head")
console.log(nums); // [0, 1, 2, 3, 4]
nums.pop(); // removes the last value
console.log(nums); // [0, 1, 2, 3]
nums.shift(); // removes the first value
console.log(nums); // [1, 2, 3]
nums.splice(2, 1); // starting at index 2, removes 1 value
console.log(nums); // [1, 2];
nums.splice(1, 0, 'hi'); // starting at index 1, removes 0 values and inserts 'hi'
console.log(nums); // [1, 'hi', 2]
- Arrays have many more methods, many of which are similar to String methods such as:
arr.includes()
arr.indexOf()
- When an Array is stored in a variable, a reference to the memory location of the Array is stored in the variable, not the values of the Array itself.
- When a variable holding an Array or Object is assigned to another variable or function parameter, the reference is passed along, not the values.
// My partner and I share our kitchen supplies
let myKitchenSupplies = ["pot", "pan", "rice cooker"];
let partnerKitchenSupplies = myKitchenSupplies; // Pass-by-reference
partnerKitchenSupplies.push("spatula"); // Adding a value to the shared Array reference
console.log(myKitchenSupplies); // ["pot", "pan", "rice cooker", "spatula"];
console.log(partnerKitchenSupplies); // ["pot", "pan", "rice cooker", "spatula"];
myKitchenSupplies = []; // reassignment breaks the association
console.log(myKitchenSupplies); // [];
console.log(partnerKitchenSupplies); // ["pot", "pan", "rice cooker", "spatula"];
- In this situation, there is only one Array reference that is held by two different variables.
- If you mutate the Array reference, both variables will "see" those changes
const addValueToEnd = (arr, newVal) => {
arr.push(newVal);
}
const letters = ['a', 'b', 'c'];
addValueToEnd(letters, 'd');
console.log(letters); // ['a', 'b', 'c', 'd']
- Keep this in mind as you create functions that take in and mutate Arrays.
Q: Why is it important to know that Arrays/Objects are passed by reference?
Answer
If you were to copy an Array from one variable to another, you might think that there are now two separate Arrays. You may then accidentally mutate the Array using one variable, thinking that the Array referenced by the other variable will not be affected.
This would likely cause bugs.
- Functions that mutate values outside of their scope are considered impure functions. This is called a side effect.
- A pure function is one that has no side-effects and produces the same outputs when called with the same inputs.
Q: Are pure functions "better" than impure functions? What benefits do pure functions provide?
Answer
Pure functions provide the benefit of being predictable. They are not necessarily "better" than impure functions though. Sometimes, functions need to have side effects.
- Making a copy of an Array is an effective way to avoid mutating the original Array when using mutating methods.
- There are two common ways to make a shallow copy of an Array
const nums = [1,2,3];
const copy1 = nums.slice();
const copy2 = [...nums]; // "spread" syntax
copy1.push(4);
copy2.pop();
console.log(copy1); // [1,2,3,4]
console.log(copy2); // [1,2]
console.log(nums); // [1,2,3]
Q: Refactor this function such that it does not mutate the input Array
const doubleValues = (arr) => {
for (let i = 0; i < arr.length; i++) {
arr[i] = arr[i] * 2;
}
return arr;
}
Answer
There are a few ways to do this but here is one way:
const doubleValues = (arr) => {
const copy = [...arr]
for (let i = 0; i < copy.length; i++) {
copy[i] = copy[i] * 2;
}
return copy;
}
- Objects are a data type that can store multiple pieces of data as key:value pairs called properties
- Objects are best used for collections of data where each value needs a name
// This object has two properties with the keys "name" and "maker"
const car = {
name: "Camry",
maker: "Toyota",
};
- Object values can be accessed and/or modified using dot notation or bracket notation
// add key/value pairs
car.color = "blue"; // dot notation
car["model-year"] = 2018; // bracket notation
// access values
console.log(car.color); // blue
console.log(car["model-year"]); // 2018
- Delete properties by using the
delete
keyword and dot/bracket notation
// delete values
delete car.color;
delete car["model-year"];
Q: How are Arrays and Objects similar. How are they different? When would you use one or the other?
Answer
- Arrays and Objects both can store multiple data values.
- While values in an Object have a key (a String), values in an Arary have an index (a Number).
- Arrays and Objects both use bracket notation to access values but only Objects use dot notation.
- Use an Array when storing similar data or when the data needs to be in a numbered order (a list of todos)
- Use an Object when storing related data or when the data needs to be referenced by name (a collection of user data)
When the key name of a property is not known until you run the program, we can add "dynamic" properties using bracket notation (we can't use dot notation)
const prompt = require("prompt-sync")();
const car = {
name: "Corolla",
maker: "Toyota",
};
const key = prompt("What do you want to know about your car?");
const value = car[key];
console.log(`The value for the key ${key} is ${value}`);
Q: Why won't car.key
work in the example above?
Answer
When using dot notation, we are effectively "hard coding" which property we are accessing. If we write car.key
, then we are saying we want to access the "key" property of the object car
, but there is no property with a key named "key".
When using bracket notation, the value held by the variable key
will be resolved first, and we'll access the property who key name matches the value held by the variable key
.
- The
Object.keys(obj)
static method returns an Array containing the keys of the provided Objectobj
. The keys will be Strings. - The
Object.values(obj)
static method returns an Array containing the values of the provided Objectobj
.
const user = {
name: 'ben',
age: 28,
canCode: true,
}
const keys = Object.keys(user);
const values = Object.values(user);
console.log(keys); // ['name', 'age', 'canCode'];
console.log(values); // ['ben', 28, true];
- Notice that this is not
user.keys()
oruser.values()
but instead is a method of theObject
object. - This is for flexibility as some objects may implement their own
.keys()
or.values()
method
Q: Write a function called getLongestKey
that takes in an Object and returns the key with the longest name.
Answer
const getLongestKey = (obj) => {
let longestKeySoFar = '';
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
if (keys[i].length > longestKeySoFar.length) {
longestKeySoFar = keys[i];
}
}
return longestKeySoFar;
}
- Destructuring is a method of declaring a list of variables and assigning them values from an Array or Object.
const coords = [30, 90];
const [lat, long] = coords;
console.log(lat, long); // 30 90
const family = ['Wendy', 'Jon', 'Daniel'];
const [mom, , brother] = family; // skips the value 'Jon'
console.log(mom, brother); // Wendy Daniel
When destructuring Arrays:
- Surround the list of variables with
[]
and assign to them the entire Array. - The names of the variables are up to you.
- The order of the variables matters: the first variable gets the first value in the Array; the second variables gets the second value in the Array; and so on...
- You can skip values by leaving a blank space
const user = {
name: 'ben',
age: 28,
canCode = true;
}
const { name, age, canCode } = user;
console.log(name, age, canCode); // ben 28 true
const car = {
make: "chevy",
model: "cobalt",
year: 2007
}
const { year, make } = car;
console.log(year, make); // 2007 chevy
When destructuring Objects:
- Surround the list of variables with
{}
and assign to them the entire Object. - The names of the variables must match the property names of the Object.
- The order of the variables does not matter
- You can omit any unneeded properties
Q: Consider the array below. How would you use destructuring to create three variables called apple
, banana
, and orange
?
const fruits = ['apple', 'banana', 'orange']
Answer
const [apple, banana, orange] = fruits;
Q: Consider the function below. How would you rewrite it such that it uses destructuring?
const introduceSelf = (person) => {
console.log(`Hello! My name is ${person.name} and I am ${person.age} years old.`);
};
Answer
There are couple ways of doing this. You could destructure the person
parameter in the function body:
const introduceSelf = (person) => {
const { name, age } = person;
console.log(`Hello! My name is ${name} and I am ${age} years old.`);
};
But the best way is to destructure the parameter:
const introduceSelf = ({ name, age }) => {
console.log(`Hello! My name is ${name} and I am ${age} years old.`);
};
const greet = (person, msg, volumeLevel) => {
let result = '';
if (volumeLevel === 'loud') {
result = `${person} said, "${msg.toUpperCase()}!"`
} else if (volumeLevel === 'quiet') {
result = `${person} said, "...${msg.toLowerCase()}..."`
}
console.log(result)
return result;
}
greet('Maya', 'Hello', 'loud')
greet('Zo', 'Bye', 'quiet')
Q: What are the limitations of this function?
Answer
- There is repetitive code (the
if
statements) - Each option must be hard-coded
- Has a little too much control over HOW the greeting is formatted
- A callback is a function that is given to another function as an argument
- A higher order function (HOF) is a function that takes in and/or returns a function
- HOFs invoke callbacks
- "We" do not invoke callbacks
// These are callbacks
const yell = (msg) => `${msg.toUpperCase()}!!`;
const whisper = (msg) => `...${msg.toLowerCase()}...`;
// This is a "Higher-Order" function
const greetBetter = (person, msg, voiceCallback) => {
const result = `${person} said, "${voiceCallback(msg)}"`
console.log(result);
}
greetBetter('Maya', 'Hello', yell); // Maya said, HELLO!!
greetBetter('Zo', 'Bye', whisper); // Zo said, ...bye...
/* we can use "inline arrow functions" */
greetBetter('Ben', 'Hello World', (msg) => `${msg}?`) // Ben said, Hello World?
- When we refactor this function to use callbacks we:
- aren't repeating ourself
- no longer need to specify HOW the person is greeting
- aren't limited by the options we hard-coded
- Inline arrow functions allow us to define single-use callbacks without storing them in a variable first.
- Imperative code provides explicit instructions for exactly HOW to complete a task.
- High control (you write every single line)
- High effort (you write every single line)
const doubleAllNums = (arr) => {
const newArr = [];
for (let i = 0; i < arr.length; i++) {
const result = arr[i] * 2
newArr.push(result)
}
return newArr;
}
const doubledNumsOld = doubleAllNums([1, 5, 10, 20]);
- Declarative code provides the desired solution without specifying HOW to get there.
- Low control
- Low effort
const doubledNums = [1, 5, 10, 20].map((num) => num * 2);
- All Arrays have built-in higher-order methods for quickly iterating through the contents of the Array.
- The important ones to know are:
Name | When To Use |
---|---|
.forEach(callback) |
To iterate but not create a new Array |
.map(modify) |
To make a copy of the Array with modified values |
.filter(test) |
To make a copy of the Array with filtered values |
.find(test) |
To get a single value from an Array |
.findIndex(test) |
To get the index of a single value from an Array |
.reduce(accumulator, startingValue) |
To derive a single value from an Arary |
.sort(compare) |
To sort the contents of an Array in place |
- Invoke these directly ON the Array
- These are Higher Order Methods because they are functions stored within an Array "Object", not as a stand-alone function.
const nums = [1,2,3,4,5];
const doubleNums = nums.map((num) => num * 2);
const evens = nums.filter((num) => num % 2 === 0);
const firstEven = nums.find((num) => num % 2 === 0);
- Each Higher Order Method takes in a callback function
- The first parameter of the callback should be named a singular version of the array name (
num
for an array ofnums
,letter
for an array ofletters
, etc...)
Examples:
const letters = ['a', 'b', 'c'];
const names = ['wendy', 'jon', 'daniel'];
const nums = [1, 3, 5, 7, 9];
// Generic Callback (callback doesn't return anything)
nums.forEach((num) => console.log(num));
nums.forEach((num, i, arr) => console.log(num, i, arr));
nums.forEach(console.log);
// Modify Callback (callback returns a new version of the value)
const doubled = nums.map((num) => num * 2);
const capsLetters = letters.map((letter) => letter.toUpperCase());
const firstLetter = names.map((name) => name[0]);
// Test Callback (callback tests the value, returns true/false)
const lessThanSeven = nums.filter((num) => num < 7); // get All values that pass the test
const seven = nums.find((num) => num < 7); // get the first value that passes the test
const indexOfSeven = nums.findIndex((num) => num === 7); // get the index of the first value that passes the test
const areAllOdd = nums.every((num) => num % 2 === 1); // do all the values pass the test?
const hasSomeMultiplesOfThree = nums.some((num) => num % 3 === 0); // does at least one value pass the test?
// Accumulator Callback (callback returns the next value of the accumulator)
const sumOfNums = nums.reduce((total, num) => total + num, 0);
Q: You are given an Array of letters
. You need to make a copy of the Array but with all the letters capitalized. What Array higher order method do you use? How would you use it?
Answer
letters.map((letter) => letter.toUpperCase())
Q: You are given an Array of numbers
. You need to make a copy of the Array but with only values less than 5
. What Array higher order method do you use? How would you use it?
Answer
numbers.filter((num) => num < 5);
Q: You are given an Array of numbers
. You need the sum of those numbers. What Array higher order method do you use? How would you use it?
Answer
numbers.reduce((total, num) => total + num, 0);
- Regex is short for Regular Expression
- A regular expression is a sequence of characters (or tokens) that specifies a match pattern in text.
Blue highlights show the match results of the regular expression pattern:
/h[aeiou]+/g
(the letter h followed by one or more vowels)
-
A regular expression is written between a pair of forward slashes
/ /
-
The letter
g
following the second/
is a flag which alters the behavior of the regular expression. Important flags include:g
- the global flag matches ALL matches, not just the first one.i
- the case-insensitive flag disregards upper/lowercase when searching for matches
-
Use https://regexr.com/ to test out your regular expressions!
- Character Sets (or Character Class) are a grouping of characters, any of which may be included in a matching pattern.
- They are written inside of square brackets
[]
Match all instances of b followed by a vowel followed by a t
- Characters sets can include ranges of letters/numbers too.
[0-9]
- match any digit between 0 and 9[2-7]
- match any digit between 2 and 7 (inclusive)[a-g]
- match any lowercase letter between a and g[a-zA-Z]
- match any lowercase or uppercase letter
Match all instances of the numbers 1-9 (not including 0)
- To match anything NOT in a character set, put
^
inside of the[]
at the start:
Match all instances of characters that are NOT a digit 1-9
- Common character sets (like all digits or all "word" characters) are represented with special characters/tokens
- They all begin with
\
and are followed by a single letter - There is often an inverse version of each
\d
- Match any digit (0-9)\D
- Match any NON digit\w
- Match any "word" character (alpha-numeric or _)\W
- Match any NON-word character\s
- Match any whitespace character (spaces, tabs, line breaks);\S
- Match any NON-whitespace character\b
- Match any word-boundary position between a word character and a non-word character\B
- Match any NON-word-boundary.
- Matches any character except line breaks\.
- Matches a "." character
Match all sequences of two word characters. Notice that punctuation and spaces are not included.
- Quantifiers allow us to specify the quantity of a particular character/character set
?
- 0 or 1 of the preceding token*
- 0 or more of the preceding token+
- 1 or more of the preceding token{3}
- 3 of the preceding token (can be any number){3,}
- 3 or more of the preceding token{3,5}
- 3-5 of the preceding token
Match all sequences of 1 or more non-vowels
- Anchors match the beginning and/or end of a string.
^
matches the beginning of the string, or the beginning of a line if the multiline flag (m
) is enabled. This matches a position, not a character.$
matches the end of the string
Matches the beginning of any line that starts with a vowel (case insensitive) and is followed by any number of word characters.
- A regular expression is a type of Object and can be stored in a variable:
- Create a regular expression using the
RegExp
constructor:
const regEx = new RegExp('ab', 'g');
const greet = new RegExp(`Hi ${name}`, 'g');
console.log(typeof regEx); // 'object'
- This is a bit clunky but you can make dynamic regular expressions.
- When using the
new RegExp
syntax, you must escape all\
:
const brokenRegEx = new RegExp(`\w\d`, 'g');
console.log(brokenRegEx);
// /wd/g - the \ are ignored
const goodRegEx = new RegExp(`\\w\\d`, 'g');
console.log(goodRegEx);
// /\w\d/g
- Literal syntax is written with forward slashes. It is quicker to write but can't be used to generate dynamic regular expressions.
const literal = /[^a-z]/g;
const wrong = /`${name}`/; // won't be dynamic
- Every Regex object has a
.test()
method which takes in a string and returnstrue
if there is a match between the Regex and the given string.
const regex = /hi/;
const hasPattern = regex.test("I think Regex is the best!");
console.log(hasPattern); // true
- Every string has a
.match()
method which takes in a Regex object and returns an Array of matches between the string and the regular expression.- If the
g
flag is used, all results matching the complete regular expression will be returned, but capturing groups are not included. - If the
g
flag is not used, only the first complete match and its related capturing groups are returned.
- If the
const str = 'this is my cat, "thing 1"';
const firstMatch = str.match(/hi/);
console.log(firstMatch);
// [
// 'hi',
// index: 1,
// input: 'this is my cat, "thing 1"',
// groups: undefined
// ]
const allMatches = str.match(/hi/g);
console.log(allMatches);
// [ 'hi', 'hi' ]