Week 7

Pass by

Value vs Reference

Pass by Value

Primitive data types

  • number
  • string
  • boolean
  • undefined
  • null
let lunch = "Leftovers";
let dinner = lunch;

console.log(lunch); // Leftovers
console.log(dinner); // Leftovers

lunch = "Sandwich";

console.log(lunch); // Sandwich
console.log(dinner); // Leftovers

Creates a copy

Computer Memory

Leftovers

Leftovers

lunch

dinner

Pass by Reference

Points to value in memory

Complex data types

  • objects
  • arrays
  • functions
let movie = { title: "Frozen" };
let newMovie = movie;

console.log(movie);
// {title: "Frozen"}
console.log(newMovie);
// {title: "Frozen"}

movie.title = "Tarzan";

console.log(movie);
// {title: "Tarzan"}
console.log(newMovie);
// {title: "Tarzan"}
let cats = ["Felix", "Kitty"];

let newCats = cats;

newCats.push("Mittens");

console.log(cats);
// ["Felix", "Kitty", "Mittens"]
console.log(newCats);
// ["Felix", "Kitty", "Mittens"]

Computer Memory

{ "title": "frozen" }

movie

newMovie

const cats = ["Felix", "Kitty"];

cats.push("Mittens");

console.log(cats);
// What will happen here?
const dog = { name: "Doug" };

dog.breed = "Labrador Retriever";

console.log(dog);
// What will happen here?

Since objects and arrays are passed by reference, even objects and array declared with const can be modified.

const cats = ["Felix", "Kitty"];

cats.push("Mittens");

console.log(cats);
// ["Felix", "Kitty", "Mittens"]
const dog = { name: "Doug" };

dog.breed = "Labrador Retriever";

console.log(dog);
// { name: "Doug", breed: "Labrador Retriever" }

Copying objects

Shallow vs Deep Copy

Copying objects with the

Spread Operator

const originalBooking = {
  airline: "United",
  departure: "Albany",
  destination: "Chicago"
};

const booking = { ...originalBooking, airline: "Delta", checkedLuggage: 0 };

console.log(booking);
/**
 * {
 *    airline: "Delta",
 *    departure: "Albany",
 *    destination: "Chicago",
 *    checkedLuggage: 0
 * }
 */

Review

const graduate = {
  // Level 1: Copy by value
  name: "Eddie Willard",
  graduationYear: "2019",
  skills: [/* Level 2: Copy by reference */ "JavaScript", "React", "CSS"],
  links: {
    // Level 2: Copy by reference
    github: "https://github.com/example/profile",
    linkedIn: "https://linkedin.com/profile"
  }
};

const newGraduate = {...graduate};

// This does NOT change the original
// because the top level is copied by value
newGraduate.graduationYear = "2021";

// This DOES change the original
// because the nested levels are copied by reference
newGraduate.skills.push("SASS");
newGraduate.links.github = "https://github.com/newhandle/profile";

console.log(graduate);
/**
 * {
 *    name: "Eddie Willard",
 *    graduationYear: "2019",
 *    skills: ["JavaScript", "React", "CSS", "SASS"],
 *    links: {
 *      github: "https://github.com/newhandle/profile",
 *      linkedIn: "https://linkedin.com/profile"
 *    }
 * }
 */

Most methods of copying objects, including the spread operator, will create a shallow copy.

↓ Scroll down

Deep Copy with

structuredClone

const graduate = {
  // Level 1: Copy by value
  name: "Eddie Willard",
  graduationYear: "2019",
  skills: [/* Level 2: Copy by reference */ "JavaScript", "React", "CSS"],
  links: {
    // Level 2: Copy by reference
    github: "https://github.com/example/profile",
    linkedIn: "https://linkedin.com/profile",
  },
};

const newGraduate = structuredClone(graduate);

// This DOES not change the original because it is a deep copy
newGraduate.skills.push("SASS");
newGraduate.links.github = "https://github.com/newhandle/profile";

console.log(graduate);
/**
 * {
 *    name: "Eddie Willard",
 *    graduationYear: "2019",
 *    skills: ["JavaScript", "React", "CSS"],
 *    links: {
 *      github: "https://github.com/example/profile",
 *      linkedIn: "https://linkedin.com/profile"
 *    }
 * }
 */

↓ Scroll down

structuredClone came out in 2022. If you are working in older code bases, you may see them use third party libraries like Immer.js, Immutable.js, Lodash's cloneDeep or jQuery's extend to deep clone objects and arrays.

Callbacks

For Loop (Review)

Runs a block of code a specified number of times

for (let i = 0; i < 10; i++) {
  // This says "Hello World" 10 times
  console.log("Hello World");
}

Step 1

Step 2

Step 3

Step 1 Create a counter

Step 2 Create a condition of when to stop

What is inside of the { } repeats

Step 3 Update the counter

A callback is a function that accepts another function as a parameter

Without Callbacks

const sendMessageConsole = (message) => {
  console.log(message); // print message to console
};

const sendMessageAlert = (message) => {
  alert(message); // an alert will open saying "print message using the built in alert"
};

sendMessageConsole('print message to console');
sendMessageAlert('print message using the built in alert');

With Callbacks

const displayMessage = (message, callback) => {
  callback(message);
};

displayMessage("win", console.log); // win
displayMessage("win", alert); // alert that says "win"

Callbacks can be a reference to a function or an anonymous function.

const greet = (name, formatter) => {
  return "Hello " + formatter(name);
};

const lowerCaseName = (name) => {
  return name.toLowerCase();
};

const greeting = greet("Tim", lowerCaseName);
console.log(greeting); // Hello tim
const greet = (name, formatter) => {
  return "Hello " + formatter(name);
};

const greeting = greet("Tim", (name) => {
  return name.toLowerCase();
});

console.log(greeting);

Function reference

Anonymous function

What are callbacks used for?

  1. Looping through arrays
  2. Browser events (e.g. click event) 
  3. Asynchronous code
const slides = [
  "Intro to JavaScript",
  "Collections",
  "A Closer Look at Functions"
];

for (let i=0; i < slides.length; i++) {
  const slide = slides[i];
  console.log(`${i+1}. ${slide}`);
}

// 1. Intro to JavaScript
// 2. Collections
// 3. A Closer Look at Functions

Given this, how would complete this using a high order function that takes in a callback?

const slides = [
  "Intro to JavaScript",
  "Collections",
  "A Closer Look at Functions"
];

const printSlides = (slide, i) => {
  console.log(`${i+1}. ${slide}`);
};

slides.forEach(printSlides);

// 1. Intro to JavaScript
// 2. Collections
// 3. A Closer Look at Functions

You can use a forEach function.

Array.forEach()

Loops through and performs a callback on each item in an array

const myCallback = (item, index, array) => {
  // You write the code to perform an action on each item here
  console.log(item); // Single item in the array
  // 1st time around, prints "myString"
  // 2nd time around, prints 500
  
  console.log(index); // Position (where) in array
  // 1st time around, prints 0
  // 2nd time around, prints 1
  // 3rd time around, prints 2
  
  console.log(array); // Entire array
  // Everytime around, prints ["myString", 5]
}

const myArray = ["myString", 500, myVariable];
myArray.forEach(myCallback);

Alternatively, you can write forEach function like this, with an anonymous function:

["myString", 500, myVariable].forEach( (item, index, array) => {
  // You write the code to perform an action on each item here
  console.log(item);
});
["Intro to JavaScript", "Collections", "A Closer Look at Functions"].forEach(
  (slide, i) => {
    console.log(`${i + 1}. ${slide}`);
  }
);

// 1. Intro to JavaScript
// 2. Collections
// 3. A Closer Look at Functions
const oldArray = ["1", "2", "3"];

Array.map()

copies an array and transforms each item in the original array into something new

const newArray = [1, 2, 3];
const oldArray = ["myString", 500];

const newArray = oldArray.map( (item, index, array) => {
  console.log(item); // 1st time around, prints "myString"
  console.log(index); // 1st time around, prints 0
  console.log(array); // Everytime around, prints ["myString", 5]

  // ...

});

// ...

map is similar to forEach,

const oldArray = ["myString", 500];

const newArray = oldArray.map( (item, index, array) => {




  const newItem = /* Write code to change each item here */
  return newItem; // Return the new version of the item
});

console.log(newArray); // Prints new array with changed items

map is similar to forEach, but map differs from forEach because it creates a new array from whatever you return.

const oldArray = ["1", "2", "3"];

const newArray = oldArray.map( (item, index, array) => {
  const num = parseInt(item);
  return num;
});

console.log(newArray); // [1, 2, 3]
const slides = ["Intro to JavaScript", "Collections", "A Closer Look at Functions"].map(
  (slide, i) => {
    return `${i + 1}. ${slide}`;
  }
);

console.log(slides);
// [ "1. Intro to JavaScript", "2. Collections", "3. A Closer Look at Functions" ];

Do I keep?

Or do I toss?

Array.filter()

creates a new array by removes the values that you don't want from the original array

const oldArray = ["myString", 500];

const newArray = oldArray.filter( (item, index, array) => {
  if (/* ... */) {
    return true; // Keep this 
  } else {
    return false; // Toss this
  }
});

console.log(newArray); // Prints new array with some items removed
forEach() map() filter()
Loops through each item in an array Loops through each item in an array Loops through each item in an array
Returns undefined Returns a new array  Returns a new array
Use to access and do something with each item in an array Use to copy and change each item in an existing array Use to copy and remove unwanted items from an existing array

More on arrays and callbacks

There are more methods that you can use. You can find them in MDN:

Function Chaining

Function Chaining

a pattern in JavaScript where multiple functions are called upon a value consecutively

value.function1().function2().function3() ...
// Without chaining
// (Open me in browser console)
const timeNumbers = [1, 6, 8];

const timeStrings = timeNumbers.map((time) => {
  return `- ${time}:00 PM`;
});

const list = timeStrings.join("\n"); // The "\n" creates a new line

console.log(list);
// - 1:00 PM
// - 6:00 PM
// - 8:00 PM
// With chaining
// (Open me in browser console)
const movieTimes = [1, 6, 8]
  .map((time) => `- ${time}:00 PM`)
  .join("\n"); // The "\n" creates a new line

console.log(movieTimes);
// - 1:00 PM
// - 6:00 PM
// - 8:00 PM

Without chaining

Function chaining

Each function in a chain must return a value in order to work

let movieTimes = [];

[1, 6, 8]
  .forEach((time) => {
    movieTimes.push(`- ${time}:00 PM`);
  })
  .join("\n"); // Error: Cannot read properties of undefined

The code above does not work because .forEach() does not return an array (it returns undefined).

You can chain all data types, not just arrays.

const userInput = "the great gatsby"; // string

const title = userInput
  .replace("the ", "") // returns string
  .concat(", The") // returns string
  .split(" ") // returns array
  .map((word) => word[0].toUpperCase() + word.substring(1)) // returns array
  .join(" "); // return string

console.log(title); // Great Gatsby, The

Promises

Synchronous

Things happen one at a time, in a predictable order. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

Asynchronous

Multiple things happen at the same time, in an unpredictable order. When you start an action, your program continues to run.​

Big Words Alert!

Synchronous

Everything else

Asynchronous

  • Timers (e.g. window.setTimeout)
  • Promises
  • HTTP Requests (AJAX)
  • Some media and file handling tasks
  • Streams and pipes in Node.js

What is

What is

Philip Roberts: What the heck is the event loop anyways?

Styles of handling asynchronous code in JavaScript:

  • Callbacks
  • Promises
  • Async & Await

window.setTimeout

a timer; delays a function from being invoked for a given amount of time

window.setTimeout(() => {
  // code that is delayed
}, 1000); // amount of time to wait in milliseconds

What is the response below?

let data;

// window.setTimeout delays a function from being called on
// for a given amount of time
window.setTimeout(() => {
  // All code that should be delayed goes in here
  data = { success: true };
}, 1000); // Delaying by 1000 milliseconds (1 second)

console.log(data); // What is this?

We solve this using promises like this:

function delay(milliseconds) {
  return new Promise((resolve) => {
    window.setTimeout(resolve, milliseconds);
  });
}

let data;

delay(1000) // Delay for 1000 milliseconds (1 second)
  .then(() => {
    data = { success: true };
    return data;
  })
  .then((result) => {
    console.log(result); // Logs { success: true } after 1 second
  });

Promises

asyncFunction()
    .then(response => /* ... */) // success function
    .then(response => /* ... */) // which can be chained
    .then(response => /* ... */) // as long as it
    .then(response => /* ... */) // returns another promise
    .catch((error) => /* ... */); // error handler

Turning your code into a promise with the

Promise API

const asyncColorChanger = (color) => {
  // Wrapping in a promise so that I can chain with .then()
  return new Promise((resolve, reject) => {
    // Waits for 1 second
    window.setTimeout(() => {
      const h1 = document.querySelector('h1');
      // Changes the color
      h1.style.color = color;
      // Moves on to the function inside of the next .then()
      resolve(color);
    }, 1000);
  });
};

asyncColorChanger('red')
  .then(() => asyncColorChanger('orange'))
  .then(() => asyncColorChanger('#EBEB00'))
  .then(() => asyncColorChanger('green'))
  .then(() => asyncColorChanger('blue'))
  .then(() => asyncColorChanger('purple'));
const asyncFunction = () => {
    return new Promise((resolve, reject) {
         if (condition) resolve(/* ... */); // the .then() below
         else reject(); // the .catch() below
    }
};

asyncFunction()
    .then(data => /* ... */) // when resolve (success)
    .catch(() => /* ... */); // when reject (error)

↓ Scroll down

 

 

 

References & Resources

Week 7

By Jamal Taylor