Home
โ† Back to bookshelf

๐ŸŒŸ Modern JavaScript: ECMAScript 6 (ES6, or ES2015) is a major update to the JavaScript standard released in 2015

These Study Room materials were written and edited by keysub with the assistance of AI.

๐Ÿ“œ 1. ES6โ€“7 through ES12: update history (click to expand)


1. es6

  • New variable declarations: let, which has block scope, and const for declaring constants were added in place of the existing var, making variable management safer.
  • Template literals: Backticks (`) let you easily insert variables into strings and include line breaks.
  • Destructuring: Extract only the values you need from an object or array and assign them to variables concisely.
  • Arrow functions: The => syntax makes function declarations more concise, and this binding is determined lexically.
  • Classes: Class syntax was introduced to make object-oriented programming more intuitive.
  • Promise: Promise became a standard object for escaping callback hell.
  • Spread syntax (...): Use it to create or combine copies without modifying the original array or object (preserving immutability).
  • Modules (import / export): The standard of the modern JavaScript ecosystem: split code across files and assemble it. Native module support lets you organize code into independent modules using import and export.
  • Map objects
  • Set objects


2. es7

  • Array.includes() (check whether a specific element exists in an array)


3. ES8 (ES2017)

  • async / await (the standard for asynchronous processing)
  • Object.values() / Object.entries(): Often used to turn an object's values into an array, or to iterate over its keys and values together.


6. ES11 (ES2020)

Preventing those frustrating errors These two features are lifesavers in production: they prevent a TypeError from crashing the screen.

  • Optional chaining (?.): When accessing nested objects, it safely returns undefined without throwing an error if an intermediate value is null or undefined. (Example: user?.profile?.address)
  • Nullish coalescing (??): Uses the value on the right only when the left side is null or undefined. It completely solves a bug with the existing || operator, which treats 0 as falsy.
  • Promise.allSettled() (returns the results of all Promises in an array)


7. ES12 (ES2021)

  • String.replaceAll() (replace every occurrence of a pattern in a string)
  • In the past, replacing every occurrence of a word required a regular expression (/word/g). Now this method makes replacing all occurrences intuitive and clean.

๐Ÿ’ก Concepts, focusing on what has changed

๐Ÿ“ฆ 1. Variables

1.1 Variable declarations

The differences among var, let, and const mainly concern reassignment, redeclaration, and scope (the region where a variable is valid). Modern JavaScript rarely uses var.

[Summary of Key Differences]
Scope        var                       let                       const
Reassignment      Allowed                      Allowed                      Not allowed (constant)
Duplicate Declaration    Allowed (risky)                Not allowed                    Not allowed
Scope      Function-based (Function)        Block-based ({ })           Block-based ({ }) 
Hoisting    Usable before declaration (undefined)    Cannot be used before declaration (error)    Cannot be used before declaration (error)


1.2 Examples

๐Ÿšจ 1.2.1 Redeclaration

var user = "Chul-soo";
var user = "Young-hee"; 
console.log(user); // "Young-hee" 

// [let / const] - Duplicate declarations not allowed  
let score = 100;
// let score = 200; // Error: SyntaxError (variable already declared)

const ID = "admin";
// const ID = "guest"; // Error: SyntaxError (variable already declared)

const ID_OBJ = {id1: 1, id2: 2};
ID_OBJ.id1 = 3;
console.log(ID_OBJ); // Object {id1: 3, id2: 2} // Even when declared with `const`, properties and property values of objects or arrays can be modified

๐Ÿ›ก๏ธ 1.2.2 Scope A var declared inside a function can be used anywhere within that function, but let and const can never escape the braces { } (block) that enclose them.

if (true) {
  var varName = "I am var";
}
console.log(varName); // "I am var" (Accessible even outside the block)

// [let / const] - Block scope
if (true) {
  let letName = "I am let";
  const constName = "I am const";

  console.log(letName); // "I am let" (Of course, this is possible within the block)
}


1.3 Data types

๐Ÿ’Ž 1.3.1 Symbol: a unique identifier that is never duplicated.

  • Uniqueness: Values created with Symbol() are treated as different even if their descriptions look the same.
  • Immutability: Once created, a Symbol cannot be changed.
  • Concealment: Symbols are not found by ordinary loops (for...in) or Object.keys(), making them useful for creating private object properties.
var sym = Symbol();

// 1. Symbol creation (The string inside the parentheses is merely a 'description')
const sym1 = Symbol('id');
const sym2 = Symbol('id');

console.log(sym1 === sym2); // false (Even if the descriptions are the same, the values are different)

// 2. Used as an object key
const user = {
  name: "John",
  [sym1]: 123 // Assign a symbol as a key
};

console.log(user[sym1]); // 123

// 3. Checking for encapsulation (The key is not visible using standard methods)
for (let key in user) {
  console.log(key); // Only "name" is printed; sym1 is skipped
}

console.log(Object.keys(user)); // ["name"]
  • When creating an object, putting square brackets [] in the key position means that the result of evaluating the expression inside as code will be used as the key. (This is also a modern JavaScript feature.)

๐Ÿ’ฌ 1.3.2 Template literals

1.3.2.1 The traditional quotes (' ', " ") are replaced with a backtick (`).
1.3.2.2 Placeholder: Use the ${...} syntax to insert a variable or expression directly in the middle of a string.

const name = "Jquery";
const version = "1.0";

// 1. Traditional method (ES5) โ€“ Using quotes and the + operator
const oldWay = "The current library is " + name + " and the version is " + version + ".";

// 2. Template literal method (ES6) - Back
const newWay = `The current library is ${name} and the version is ${version}.`;

// 3. Line break example
const multiLine = `
  This is an example that 
  outputs multiple lines 
  exactly as they are.
`;

console.log(newWay);
console.log(multiLine);

1.3.2.3 Usage examples

// 1. Traditional method 
var html = "<div class='user'>" + 
           "\n  <h1>Name: " + this.name + "</h1>" + 
           "\n  <p>Age: " + this.age + "</p>" + 
           "\n</div>";              

// 2. Template Literal      
const htmlTemplate = `
  <div class="user">
    <h1>Name: ${this.name}</h1>
    <p>Age: ${this.age}</p>
  </div>
`;

๐Ÿงฉ 2. Objects: ES6 introduced property shorthand and computed properties for writing objects more concisely.

2.1 Basic object concepts

๐Ÿ“ 2.1.1 Create an object with an object literal

var me = {name : "keysub", age: 11};

๐Ÿ” 2.1.2 Read or write values in an object

me.name or me["age"]

โž• 2.1.3 Assigning a value to a nonexistent property adds a new property:

me.hobby = "Swimming"; 

โ“ 2.1.4 Check whether a property exists with the in operator:

console.log("hobby" in me); // true


2.2 Objects and collections (arrays, Set, Map)

โœ‚๏ธ 2.2.1 Property shorthand

If the variable name and object key are the same, you only need to write it once.

const name = "Younghee";
const age = 30;

// ES5
const user5 = { name: name, age: age };

// ES6 (shorthand)
const user6 = { name, age }; 
console.log(user6); // { name: "Younghee", age: 30 }

โšก 2.2.2 Method shorthand

You can omit the function keyword when defining a function.

const dog = {
  name: "Mungmungi",
  // ES5: bark: function() { ... }
  bark() { 
    console.log("Woof woof!");
  }
};

๐Ÿงฎ 2.2.3 Computed property names

You can set an object's key dynamically using a variable or expression.

const prefix = "user";
const i = 1;

const member = {
  [`${prefix}_${i}`]: "Hong Gil-dong" // A key named 'user_1' is created
};
console.log(member.user_1); // "Hong Gil-dong"


2.3 Set (a container that does not allow duplicates)

๐Ÿšซ 2.3.1 Arrays in the past and how Set differs

In the past, arrays were the default way to store multiple values. But arrays allow duplicates, so code could get messy when asked to "remove all duplicate data." Set is a container that never allows duplicates by design. Add the same value multiple times, and only one copy remains.

๐Ÿ› ๏ธ 2.3.2 Creating a Set (constructor)

Creating a Set is simple: use the new keyword.

โ‘  Create an empty container

const mySet = new Set();

โ‘ก Create it with data from the start (most common!) Pass iterable data, such as an array, to the constructor, and it removes duplicates to create a Set.

// Let's try passing an array that contains duplicate numbers 2 and 3.
const numbers = new Set([1, 2, 2, 3, 3, 3, 4]);

console.log(numbers); 
// Output: Set(4) { 1, 2, 3, 4 }  <- Only one instance each of the duplicate elements 2 and 3 remains!

๐Ÿช„ 2.3.3 Set's useful tools (methods and properties)

Set provides intuitive methods for working with data.

โ‘  Add data (add)

mySet.add("apple");
mySet.add("banana");
mySet.add("apple"); // Ignored because "apple" is already in the set!

console.log(mySet); // Output: Set(2) { 'apple', 'banana' }

๐Ÿ’ก Tip: You can chain calls like mySet.add(1).add(2);.

โ‘ก Check for data (has) Checks whether a specific value exists in the container. (It serves the same role as an array's includes, but is much faster!)

console.log(mySet.has("apple"));   // Output: true
console.log(mySet.has("watermelon"));   // Output: false

โ‘ข Delete data (delete / clear)

mySet.delete("banana"); // Removes only "banana"
mySet.clear();        // Empties the entire set! (Initializes it)

โ‘ฃ Check the size (the size property) Arrays use .length to get their length; Sets always use .size.

const animalSet = new Set(["dog", 'cat', "rabbit"]);
console.log(animalSet.size); // Output: 3

๐ŸŽฏ 2.3.4 The perfect time to use Set in practice

When is Set most commonly used in practice? When removing duplicates from an array. Previously, removing duplicates required complex filtering or loops. Combining Set with spread syntax (...) reduces it to a single line.

const dirtyArray = ["Hong Gil-dong", "Lee Sun-shin", "Hong Gil-dong", "Yoo Gwan-soon", "Lee Sun-shin"];

// 1. Convert the array to a Set to remove duplicates,
// 2. Then use square brackets [] and ... to wrap it back into an array.
const cleanArray = [...new Set(dirtyArray)];

console.log(cleanArray); 
// Output: [ 'Hong Gil-dong', 'Lee Sun-shin', 'Yoo Kwan-soon' ]


2.4 Map (a locker that stores key-value pairs)

๐Ÿ’ก 2.4.1 Why Map was introduced

In JavaScript, ordinary objects ({}) can also act as lockers for keys and values, but Map is an upgraded locker designed to address the drawbacks of objects.

๐Ÿ—„๏ธ 2.4.2 Working with Map

2.4.2.1 Creating a Map

โ‘  Create an empty locker

const myMap = new Map();

โ‘ก Create it with data from the start Map requires keys and values to come in pairs, so initialize it with small [key, value] arrays inside an array.

const userMap = new Map([
  ["name", "Hong Gil-dong"],
  ["age", 25],
  ["role", "admin"]
]);

2.4.2.2 Essential methods for working with Map

Add or update data (set) Put an item (Value) in a locker (Key). If the key already exists, its value is overwritten (updated).

myMap.set("color", "red");
myMap.set("size", 100);
myMap.set("color", "blue"); // The value of the "color" key changes from 'red' to "blue"!

// Chaining is also possible, just like with a Set.
myMap.set("a", 1).set("b", 2);

Retrieve data (get) Pass a key to get its value. If the key does not exist, undefined is returned.

console.log(myMap.get("color")); // Output: 'blue'

Check for data (has) Checks whether the key exists. (It looks only for the key, regardless of the value.)

console.log(myMap.has("size"));  // Output: true

Delete data and check the size (delete, clear, size)

myMap.delete("size"); // Delete the "size" key and its value
console.log(myMap.size); // Check the number of data pairs currently stored
myMap.clear();        // Clear the entire map

๐Ÿ’ก 2.4.2.3 Why use Map instead of an object ({})?

โ‘  You can use anything as a key! (the biggest difference) Ordinary objects require keys to be strings. Even if you use a number, it is converted to a string internally. Map, however, lets you use numbers, booleans (true/false), and even functions or other objects as keys.

const map = new Map();

// 1. Using a number as a key
map.set(1, "This is number 1"); 

// 2. Using another object as a key (Magic that's absolutely impossible with a regular object {}!)
const kim = { id: 101, name: "Kim Cheol-su" };
map.set(kim, "This user is a VIP member.");

console.log(map.get(kim)); // Output: "This user is a VIP member."

โ‘ก You can get the size immediately. With an ordinary object, you need a calculation such as Object.keys(obj).length to find the number of entries. With Map, just use map.size.

โ‘ข It remembers insertion order perfectly. With an ordinary object, iterating later does not guarantee that entries will come out in insertion order. Map always returns them in the order they were added.


๐Ÿ—๏ธ 2.5 Object.keys, values, entries: Convert object data to arrays for processing



2.5.1 Object.keys(): Extract only an object's keys into an array

๐Ÿ”‘ Useful for checking an object's properties or looping through its keys.

const user = {
  name: "Son Heung-min",
  age: 32,
  team: "Tottenham"
};

const keys = Object.keys(user);

console.log(keys); 
// Output: ["name", 'age', "team"]

// Application: Iterating through a key-value array
keys.forEach(key => {
  console.log(`${key}: ${user[key]}`);
});


2.5.2 Object.values(): Extract only an object's values into an array (ES8)

๐Ÿ’Ž Use this when you do not need the key names and only need the data they contain.

const scores = {
  korean: 90,
  english: 85,
  math: 100
};

const values = Object.values(scores);

console.log(values); 
// Output: [90, 85, 100]

// Application: Calculating the total score
const totalScore = values.reduce((acc, curr) => acc + curr, 0);
console.log(`Total Score: ${totalScore}`); // Total Score: 275


2.5.3 Object.entries(): Turn keys and values into [Key, Value] pairs (ES8)

๐Ÿ‘ฏ Converts an object into a two-dimensional array of [key, value] pairs. Essential for converting to a Map or performing complex transformations.

const user = {
  id: 1,
  role: "admin",
  status: "active"
};

const entries = Object.entries(user);

console.log(entries);
/* Output:
[
  ["id", 1],
  ["role", "admin"],
  ["status", "active"]
]
*/

// Application: Combining the `for...of` loop with destructuring assignment (Recommended for real-world use๐ŸŒŸ)
for (const [key, value] of Object.entries(user)) {
  console.log(`The value of the ${key} field is ${value}.`);
}


2.5.4 Practical tip: Convert an object back to an object (Object.fromEntries)

๐Ÿ”„ Use this when you want to turn data converted to an array back into an object.

const arr = [
  ['name', 'Lee Kang-in'],
  ['age', 23]
];

const obj = Object.fromEntries(arr);

console.log(obj);
// Output: { name: "Lee Kang-in", age: 23 }


๐Ÿ” 2.6 includes: Check whether a value is present (Array & String)



2.6.1 includes on arrays (Array.prototype.includes) - ES7

๐ŸŽ Completely replaces the old, cumbersome indexOf() !== -1 approach and returns true / false.

const fruits = ['apple', 'banana', 'mango'];

// [Old ES5 approach]
// `indexOf` returned the index number (0, 1, 2...) if the value was found, and -1 if it wasn't.
// Therefore, cumbersome code was required to check whether the result was -1 or not.
if (fruits.indexOf('banana') !== -1) {
  console.log("There is a banana!");
}

// [Latest ES7 method] ๐ŸŒŸ
// If you simply ask, "Does this contain it?", it gives a clear true/false answer.
if (fruits.includes('banana')) {
  console.log("There is a banana!");
}

// ๐Ÿ’ก (Important difference) Finding NaN (Not a Number)
// indexOf cannot find NaN, but includes finds it accurately.
const arr = [1, 2, NaN];
console.log(arr.indexOf(NaN));   // -1 (not found)
console.log(arr.includes(NaN));  // true (found)


2.6.2 includes on strings (String.prototype.includes) - ES6

๐Ÿ“ You can easily check whether a specific word appears in a long text (string).

const sentence = "Hello, I'm studying JavaScript.";

console.log(sentence.includes("JavaScript")); // true
console.log(sentence.includes("Python"));       // false

// ๐Ÿ’ก Tip: You can specify the "starting position for the search" as the second argument.
// Checks for "Hello" starting from index 10.
console.log(sentence.includes("Hello", 10));     // false


2.6.3 Practical tip: Simplify multiple OR conditions

โœ‚๏ธ When an if statement has an overly long || (OR) expression, using an array and includes can dramatically shorten the code.

const myFruit = 'apple';

// ๐Ÿšจ [Bad Example] The code becomes infinitely long as conditions increase
if (myFruit === 'apple' || myFruit === 'banana' || myFruit === 'mango') {
  console.log("These are the fruits in the fruit basket.");
}

// โœ… [Using `includes`] Group the items to be checked into an array (Recommended for real-world use๐ŸŒŸ)
const bucket = ['apple', 'banana', 'mango', 'grape', 'orange'];

if (bucket.includes(myFruit)) {
  console.log("These are the fruits in the fruit basket.");
}

โœ‚๏ธ 3. Destructuring


3.1 Object destructuring:

When extracting data from an object, variables are created based on the key names.

const user = {
  name: "Son Heung-min",
  age: 32,
  team: "Tottenham"
};

// [ES5 approach] Create and assign variables one by one
var name = user.name;
var age = user.age;

// [ES6 approach] Create and assign variables all at once
const { name, age, team } = user;

console.log(name); // "Son Heung-min"
console.log(team); // "Tottenham"

// What if you want to rename a variable? (name: newName)
const { name: userName, age: userAge } = user;

console.log(userName); // "Son Heung-min"


3.2 Array destructuring:

Arrays have no keys, so variables are created based on their order (index).

const colors = ["Red", "Green", "Blue"];

// [ES5 approach] Create and assign variables one by one
var first  = colors[0];
var second = colors[1];
var third  = colors[2];

// [ES6 Style] Using square brackets []
const [first, second, third] = colors;
console.log(first);  // "Red"
console.log(second); // "Green"

// When you want to skip a specific element (using commas)
const [first_color , , third_color] = colors;
console.log(third_color); // "Blue"


3.3 Where it shines in practice

๐Ÿ’ผ 3.3.1 Passing an object as a function parameter

Instead of receiving the whole object and accessing properties with dot notation, you can name just the data you need.

function printUser({ name, age }) {
  console.log(`My name is ${name}, and I am ${age} years old.`);
}

const person = { name: "Lee Kang-in", age: 23, city: "Paris" };

printUser(person); // "Name is Lee Kang-in, age is 23."

๐Ÿ›ก๏ธ 3.3.2 Setting default values:

You can specify a default value to use when the object does not contain the property.

const { name, hobby = "soccer" } = { name: "Kim Min-jae" };

console.log(hobby); // Since the object doesn't have a "hobby" property, the default value "soccer" is printed

๐Ÿ› ๏ธ 4. Functions


4.1 Create a function with a function declaration:

A function name must follow the function keyword. (Feature: hoisting applies.)

sayHello(); // Executes successfully! (Hoisting)

function sayHello() {
  console.log("Hello!");
}


4.2 Function literals (function expressions)

A way to assign a function to a variable as if it were a value. (Feature: it is not hoisted.)

// sayHi(); // Throws an error!

const sayHi = function() {
  console.log("Nice to meet you!");
};

sayHi(); // Can only be called after declaration


4.3 Arrow functions

4.3.1 Differences from function expressions

4.3.1.1 ๐Ÿฆ• Traditional approach (function expression)

const add = function(a, b) {
  return a + b;
};

4.3.1.2 ๐Ÿš€ Arrow function

const add = (a, b) => {
  return a + b;
};

// If there is only one argument, () can be omitted
const add1 = a => { return a + 1; };

// Cannot omit () when there are no arguments
const add2 = () => { return 1 + 2; };       

// If the only statement inside a function is `return`, the curly braces and `return` can be omitted
const add3 = a => a + 1;                    

// Even if the only statement inside a function is `return`, if the return value is an object literal, it must be enclosed in parentheses ()
const f = (a, b) => ({ x: a, y: b });       

๐ŸŒŸ 4.3.2 The real difference in how they work: how this is determined (key point ๐ŸŒŸ)

4.3.2.1 ๐Ÿฆ• this in traditional functions: "Who called me?" (dynamic binding) In a traditional function, this changes depending on who calls it when the function is executed.

const user = {
  name: "Hong Gil-dong",
  sayHi: function() {
    console.log(this.name); // Outputs correctly: Hong Gil-dong

    // ๐Ÿšจ Problem occurs: A function that greets the user after 1 second is created inside this function
    setTimeout(function() {
      // Since this inner function is treated as having been called by the browser (window), it loses `this`.
      console.log(`1 second later: Hello, I'm ${this.name}`); 
    }, 1000);
  }
};

user.sayHi(); 
// Output: Hong Gil-dong
// Output 1 second later: Hello, I'm undefined  <-- ๐Ÿ’ฅ Error! Lost `this`!

4.3.2.2 ๐Ÿš€ this in arrow functions: "Where is my birthplace?" (lexical binding) An arrow function uses the this value of the surrounding environment where it was created.

const user = {
  name: "Hong Gil-dong",
  sayHi: function() {
    console.log(this.name); // Outputs correctly: Hong Gil-dong

    // โœ… Solution: Use an arrow function
    setTimeout(() => {
      // The arrow function inherits the `this` (user) from the outer `sayHi` function.
      console.log(`1 second later: Hello, I'm ${this.name}`); 
    }, 1000);
  }
};

user.sayHi(); 
// Output: Hong Gil-dong
// Outputs after 1 second: Hello, I'm Hong Gil-dong  <-- ๐ŸŽ‰ Works perfectly!

๐Ÿ› ๏ธ 4.3.3 Using arrow functions

๐Ÿ“ฆ Scenario: Customer data received from a server (raw data)

const users = [
  { id: 1, name: "Hong Gil-dong", age: 25, isVip: true },
  { id: 2, name: "Lee Sun-shin", age: 40, isVip: false },
  { id: 3, name: "Yoo Gwan-soon", age: 18, isVip: true },
  { id: 4, name: "Gang Gam-chan", age: 35, isVip: false }
];

1. map: "data-processing factory" (change the shape)

// Iterate through the `users` array and create a new array containing only the names (`u.name`) of each user.
const names = users.map(u => u.name);

console.log(names); 
// Output: ["Hong Gil-dong", "Lee Sun-shin", "Yoo Gwan-soon", "Kang Gam-chan"]

2. filter: "data sieve" (keep only items that match a condition)

// Iterate through the `users` array and keep only the users for whom `isVip` is true.
const vipUsers = users.filter(u => u.isVip === true); 

console.log(vipUsers);
/* Output: 
[
  { id: 1, name: 'Hong Gil-dong', age: 25, isVip: true },
  { id: 3, name: 'Yoo Gwan-soon', age: 18, isVip: true }
]
*/

๐Ÿ‘‘ 3. The practical champion: chaining

const vipNames = users
  .filter(u => u.isVip) // Step 1: Filter out only VIPs first
  .map(u => u.name);    // Step 2: Extract only the names from the remaining data

console.log(vipNames); 
// Output: ["Hong Gil-dong", "Yoo Gwan-soon"]


4.4 Rest parameters:

Syntax that gathers an unspecified number of arguments into a single array

4.4.1 Basic example: Receive a function's arguments as an array

function sumAll(...numbers) {
  // `numbers` becomes an array of [1, 2, 3, 4, 5].
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

console.log(sumAll(1, 2));           // 3
console.log(sumAll(1, 2, 3, 4, 5));  // 15 

4.4.2 Using it with ordinary parameters (it must always be last)

function showGrade(name, ...scores) {
  console.log(`Number of scores for ${name}: ${scores.length}`);
  console.log(scores); // [90, 85, 100]
}

showGrade("Cheol-su", 90, 85, 100);

4.4.3 Rest in destructuring

// โ‘  Usage with arrays
const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first);  // 1
console.log(others); // [2, 3, 4, 5]

// โ‘ก Usage with objects
const user = { id: 1, name: "Young-hee", age: 20, city: "Seoul" };
const { id, ...userInfo } = user;

console.log(id);       // 1
console.log(userInfo); // { name: "Young-hee", age: 20, city: "Seoul" }


4.5 Spread syntax:

Unpacks an "array or object container" and spreads its contents out as individual items

4.5.1 Spread syntax with arrays

const parts = ["shoulder", "knee"];
const body = ["head", ...parts, "feet"]; 

console.log(body); // ["head", "shoulders", 'knees', "feet"]

// Copy an array (creates a completely new array) 
const original = [1, 2, 3];
const copy = [...original];

4.5.2 Spread syntax with objects

const user = { name: "Chul-soo", age: 20 };
const updatedUser = { ...user, city: "Seoul" };

console.log(updatedUser); // { name: "Chul-soo", age: 20, city: "Seoul" }

4.5.3 Distinguishing Rest (gathering) from Spread (unpacking) They look the same (...), but their purposes are opposite. * Rest: Takes multiple arguments and makes one array. (Function parameter list) * Spread: Splits one array/object into multiple values. (Function calls or outside declarations)


4.6 Shallow copy and deep copy

๐Ÿ“„ 4.6.1 Shallow copy

Copies only the top level of an object; nested objects inside it are copied only as references (addresses).

const original = {
  name: "Chul-su",
  skills: ["JS", "HTML"] // Nested array (object)
};

const copy = { ...original }; // Shallow copy

copy.name = "Young-hee";         // Modify values at level 1
copy.skills.push("CSS");   // Modify values at level 2 (nested object)

console.log(original.name);   // "Chul-soo" (Level 1 remains unaffected)
console.log(original.skills); // ["JS", 'HTML', "CSS"] (The original is also changed!)

๐Ÿ“‘ 4.6.2 Deep copy

Copies every level of an object into entirely new memory. (The original and copy are completely separate.)

const original = {
  name: "Chul-su",
  skills: ["JS", "HTML"]
};

// Use `structuredClone`, the modern browser standard (recommended)
const deepCopy = structuredClone(original); 

deepCopy.skills.push("React");

console.log(original.skills); // ["JS", "HTML"] (The original is preserved!)
console.log(deepCopy.skills); // ["JS", 'HTML', "React"] (Only the copy is modified)    


4.7 Closures (reference):

๐ŸŽ’ A function carries a "backpack" that remembers the environment (variables) of the place where it was created

4.7.1 A normal world without closures

function sayHello() {
  let name = "Hong Gil-dong"; // Create a variable inside the function
  console.log(name);
}

sayHello(); // Outputs "Hong Gil-dong"
// Since the function has finished executing at this point, the variable `name` is completely destroyed.

4.7.2 Create an inner function inside a function and return it to preserve variables

function makeCounter() {
  let count = 0; // 1. Variable in the outer function

  // 2. Create an inner function and return it to the outer function.
  return function() { 
    count = count + 1; // 3. Uses the variable `count` from the home environment.
    return count;
  };
}

const myCounter = makeCounter();

// The execution of `makeCounter()` has ended, and the `count` variable should be destroyed, but it continues to work!
console.log(myCounter()); // Output: 1
console.log(myCounter()); // Output: 2
console.log(myCounter()); // Output: 3

๐Ÿ—๏ธ 5. Introducing classes


5.1 ES5: Constructor functions and the new operator

โš™๏ธ 5.1.1 How it works:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

// The method is explicitly added to the prototype
Person.prototype.sayHello = function() {
  console.log("Hello, my name is " + this.name + ".");
};

// Create an instance using `new`
var user1 = new Person("Kim Cheol-su", 25);
user1.sayHello(); // Output: Hello, my name is Kim Cheol-su.


5.2 ES6: Introduction of the class keyword

๐Ÿš€ 5.2.1 How it works:

class Person {
  // [Element 2: Properties] Data that must never be exposed externally (private fields)
  #name;  

  // [Element 1: Constructor] Initializing properties
  constructor(name) {
    // This does not assign a value directly to the `this.#name` variable; instead, it calls the setter.
    this.name = name; 
  }

  // [Element 4: Getter]
  get name() {
    return this.#name;
  }

  // [Element 4: Setter] Add a setter if you want to allow the name to be changed from outside
  set name(value) {
    this.#name = value;
  }

  // [Element 3: Method] Methods are automatically added to the prototype.
  sayHello() {
    // Using `this.name` here automatically calls the getter to retrieve the value
    console.log(`Hello, my name is ${this.name}.`);
  }
}

// The method for creating an instance is the same
const user = new Person("Lee Young-hee");
user.sayHello(); // Output: Hello, my name is Lee Young-hee.

// Call the setter
user.name = "Chul-soo"; 

// Call the getter 
console.log(user.name);      // Output: Cheol-su

// Attempting to access a private field directly (not allowed outside the class)
// console.log(user.#name);  // ๐Ÿ’ฅ Error!

๐Ÿ“ฆ 6. Modules: Syntax for splitting and assembling code by file (import / export)


6.1 Named export & import (exporting / importing multiple named items)

๐Ÿงฉ Use this to export multiple functions or variables from one file. They must be imported with braces { } using their original names.

[math.js] - the file that creates the components (module)

// Prepend `export` to the declaration to export it externally.
export const PI = 3.141592;

export function add(a, b) {
  return a + b;
}

export const multiply = (a, b) => a * b;

// ๐Ÿ’ก (Note) You can also group them together and export them all at once at the very bottom of the file.
// export { PI, add, multiply };

[app.js] - the file that imports and uses the components

// Just like destructuring assignment, you can import only what you need by enclosing them in curly braces {}.
import { PI, add } from './math.js';

console.log(PI);          // 3.141592
console.log(add(10, 20)); // 30

// ๐Ÿ’ก If you want to rename the imported module, use the `as` keyword.
import { multiply as multi } from './math.js';
console.log(multi(2, 5)); // 10


6.2 Default export & import (exporting / importing one main item per file)

๐Ÿ‘‘ Use this when a file has one main class or function. You can import it under any name you choose, without braces { }.

[User.js] - the file that creates the components

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, I am ${this.name}.`);
  }
}

// Specify the `User` class as the default module for this file.
export default User; 

[main.js] - the file that imports and uses the components

// Modules exported by default can be imported using any name without parentheses.
import Customer from './User.js'; // Import User under the name "Customer"

const user1 = new Customer("Hong Gil-dong");
user1.greet(); // Hello, I am Hong Gil-dong.


6.3 Import everything at once with * (wildcard)

๐Ÿ“ฆ If there are too many exported modules to list one by one, import the entire file as a single object.

[app.js]

// Imports all exports from math.js and places them into a single object named 'MathUtils'.
import * as MathUtils from './math.js';

// Accesses them via the object's properties (dot notation).
console.log(MathUtils.PI);
console.log(MathUtils.add(5, 5));


6.4 Linking modules in HTML (important caveat ๐Ÿšจ)

๐ŸŒ To use import / export in a browser (HTML), the script tag must include the type=&quot;module&quot; attribute.

[index.html]

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>Module Test</title>
</head>
<body>

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

</body>
</html>

โณ 7. Promise: The standard object for asynchronous processing that escapes callback hell


7.1 The three states of a Promise

๐Ÿšฆ A Promise is an object that "promises" a result when a task finishes. The task can be in one of three states.

  • Pending: Asynchronous processing has not finished yet (the promise was made, but there is no result yet).
  • Fulfilled (success): Asynchronous processing completed successfully and returned the promised result.
  • Rejected (failure): Asynchronous processing failed or an error occurred.


7.2 Creating a Promise (producer)

๐Ÿ› ๏ธ Create one with new Promise, passing two callback functions: resolve (called on success) and reject (called on failure).

// Let's assume a scenario where data is retrieved from the server.
const fetchData = new Promise((resolve, reject) => {
  console.log("Retrieving data...");

  setTimeout(() => {
    const success = true; // Indicates whether communication was successful (for testing purposes)

    if (success) {
      // 1. When successful: Return the result data inside `resolve()`. (Status: Fulfilled)
      resolve({ id: 1, name: "Hong Gil-dong" }); 
    } else {
      // 2. On failure: Pass an error object inside `reject()`. (Status: Rejected)
      reject(new Error("A network error occurred."));
    }
  }, 2000); // Assuming the operation takes 2 seconds
});


7.3 Using a Promise (consumer)

๐ŸŽง Use .then() for success, .catch() for failure, and .finally() for code that always runs.

// Let's use the fetchData(Promise object) created above.

fetchData
  .then((data) => {
    // 1. This code runs when resolve() is called.
    console.log("โœ… Success:", data); // Output: โœ… Success: { id: 1, name: 'Hong Gil-dong' }
  })
  .catch((error) => {
    // 2. This code runs when reject() is called. (Error handling)
    console.log("โŒ Failure:", error.message);
  })
  .finally(() => {
    // 3. This code runs exactly once at the end, regardless of success or failure. (Typically used to remove the loading spinner)
    console.log("๐Ÿ All tasks are complete.");
  });


7.4 A practical favorite: Promise chaining

โ›“๏ธ Previously, nested callbacks created "callback hell." Promises handle this cleanly by linking .then() calls like train cars.

// A sequence of operations: retrieve a number from the server -> find the user by that number -> find that user's posts

const fetchNumber = new Promise((resolve) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then(num => {
    console.log(`First result: ${num}`); // 1
    return num * 10; // When `return` is called, this value is passed to the next `.then()` call. (This automatically becomes a new Promise)
  })
  .then(num => {
    console.log(`Second result: ${num}`); // 10
    return num * 10;
  })
  .then(num => {
    console.log(`Third result: ${num}`); // 100
  })
  .catch(error => {
    // If an error occurs anywhere in between, the nearest `catch` block catches it all.
    console.log("Error occurred!", error);
  });


7.5 Commonly used Promise methods in practice

๐Ÿ“ฆ These methods are useful for handling several asynchronous tasks together and concurrently.

const p1 = new Promise(resolve => setTimeout(() => resolve("Data #1"), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve("Data #2"), 2000));

// 1. Promise.all()
// Wait until all promises "succeed," then receive the results as an array all at once. 
// (However, if even one fails, the entire chain is immediately caught by the catch block)
Promise.all([p1, p2])
  .then(results => console.log("All complete:", results)); 
  // Output after 2 seconds: All complete: ["Data #1", "Data #2"]

// 2. Promise.race()
// Like a running race, this retrieves only the result of the "first to finish" among multiple tasks.
Promise.race([p1, p2])
  .then(result => console.log("1st place:", result)); 
  // Output after 1 second: 1st place: Data #1


7.6 Promise.allSettled() (returns the results of all Promises in an array)

const p1 = new Promise(resolve => setTimeout(() => resolve("Success Data"), 1000));
const p2 = new Promise((_, reject) => setTimeout(() => reject("Failure Error"), 2000));

Promise.allSettled([p1, p2])
  .then(results => console.log(results));

/*
Output (returns an array after 2 seconds):
[
  { status: "fulfilled", value: "Success Data" },
  { status: "rejected", reason: "Failure Error" }
]
*/

๐Ÿš€ 8. async / await: The magic that makes Promises as readable as synchronous code (ES8)


8.1 Basic rules for async and await

๐Ÿ’ก Remember just these two keywords, and asynchronous code becomes surprisingly easy.

  • async: Put it before a function to make that function always return a Promise object.
  • await: Can be used only inside an async function. It pauses and waits until a Promise operation finishes. (In other words, it replaces .then().)


8.2 Comparing Promise (.then) and async/await

๐ŸฅŠ Compare the code to see why async/await is useful. (Scenario: retrieving user information from a server)

[Traditional approach] Promise chaining (using .then)

function getUser() {
  // fetch() is a built-in Promise function that retrieves data from the server.
  fetch('[https://jsonplaceholder.typicode.com/users/1](https://jsonplaceholder.typicode.com/users/1)')
    .then(response => response.json())
    .then(user => {
      console.log("Username:", user.name);
    })
    .catch(error => {
      console.log("Error occurred:", error);
    });
}

[Modern approach] Using async / await ๐ŸŒŸ (the standard in practice)

// Prefix the function with "async".
async function getUserAsync() {
  // Using `await` waits for the data to arrive, and when it does, it assigns it to the variable on the left.
  const response = await fetch('[https://jsonplaceholder.typicode.com/users/1](https://jsonplaceholder.typicode.com/users/1)');
  const user = await response.json();

  // It reads from top to bottom like water flowing, without any callback functions (arrows)!
  console.log("Username:", user.name);
}

getUserAsync();


8.3 Error handling (try ... catch)

๐Ÿ›ก๏ธ With async/await, use JavaScript's traditional error handling, try/catch, instead of .catch().

async function getUserSafe() {
  try {
    // 1. Place the code to be executed inside the `try` block.
    const response = await fetch('https://invalid.example/users/1');
    const user = await response.json();
    console.log(user);

  } catch (error) {
    // 2. If an error occurs inside the `try` block, the code does not stop but immediately proceeds to the `catch` block.
    console.log("โŒ A problem occurred during communication!", error.message);

  } finally {
    // 3. (Optional) This runs at the end, regardless of whether the operation succeeds or fails.
    console.log("Loading screen complete");
  }
}


8.4 Practical tip: Use it with Promise.all (parallel processing)

โšก await makes the code wait. If you blindly use await to fetch unrelated data, the operation becomes slow.

๐Ÿšจ Slow approach (sequential processing)

async function getDashboardData() {
  // Wait 2 seconds to fetch the weather
  const weather = await fetch('/api/weather'); 
  // Then wait 2 seconds to fetch the news (Takes a total of 4 seconds)
  const news = await fetch('/api/news');       
}

๐Ÿš€ Fast approach (parallel processing - using Promise.all) If the data can be fetched at the same time, group the operations with Promise.all.

async function getDashboardDataFast() {
  // Send both requests simultaneously and wait only once (await) until both are complete. (Takes a total of 2 seconds)
  const [weather, news] = await Promise.all([
    fetch('/api/weather'),
    fetch('/api/news')
  ]);

  console.log("Data loading complete!");
}

๐Ÿ›ก๏ธ 9. Optional chaining (?.): Safely find values in nested objects without errors (ES11)


9.1 The painful error handling of the past

๐Ÿ’ฃ When accessing a nested object, a missing intermediate value (undefined) causes a TypeError and can crash the screen.

const user = {
  name: "Hong Gil-dong",
  // Let's assume the user has no profile information at all.
};

// ๐Ÿšจ [Danger] An error occurred! This is because an attempt was made to read `undefined.address`.
// console.log(user.profile.address); 

// ๐Ÿฆ– [Old ES5 style] To prevent the error, we had to chain the `&&` operators together like a train.
if (user && user.profile && user.profile.address) {
  console.log(user.profile.address);
} else {
  console.log("No address information.");
}


9.2 Introducing optional chaining (?.)

๐Ÿช„ Using ?. prevents an error when a value is missing in the middle; it quietly returns undefined and stops.

const user = { name: "Hong Gil-dong" };

// โœ… [Latest ES11 approach] 
// "Is there a 'profile' property in 'user'? (?. ) If so, get the 'address' too!"
const address = user?.profile?.address;

console.log(address); 
// It quietly outputs 'undefined' without throwing an error.


9.3 Using it with arrays and functions

๐ŸŽฏ You can safely access not only object properties, but also array indexes and function calls.

// 1. Safely accessing array elements
const users = null;
// Returns `undefined` without an error even if `users` is not an array or is null
console.log(users?.[0]?.name); 

// 2. Safely calling functions (methods)
const admin = {
  adminPlay() {
    console.log("Running in admin mode!");
  }
};
const guest = {};

// Execute only if the function "adminPlay" exists!
admin.adminPlay?.(); // "Run in admin mode!"
guest.adminPlay?.(); // Proceeds silently without an error

๐Ÿ›Ÿ 10. Nullish coalescing (??): Set reliable, safe default values (ES11)


10.1 The critical bug in the existing OR (||) operator

๐Ÿšจ Previously, || was used to provide a default when a value was missing, but it also treated 0 and an empty string ("") as false (missing), causing a serious bug.

// Let's assume the quantity in the shopping cart for a product has been set to 0.
const count = 0;

// Developer's intent: "If the 'count' value is completely missing, set the default value to 10!"
const result = count || 10; 

console.log(result); 
// Output: 10 (Bug occurs ๐Ÿ’ฅ)
// Since JavaScript treats the number 0 as "false," it ignores the 0 entered by the user.


10.2 The perfect solution: nullish coalescing (??)

๐Ÿ›ก๏ธ ?? uses the value on the right only when the value is null or undefined. It treats 0 and empty strings as valid values.

const count = 0;
const text = "";

// โœ… [Latest ES11 approach]
const safeCount = count ?? 10;
const safeText = text ?? "default text";

console.log(safeCount); // Output: 0 (Works as expected! Recognizes 0 as a valid value)
console.log(safeText);  // Output: "" (Works as expected! An empty string is also recognized as a valid value)

// Returns the default value only when the value is null or undefined.
const notExist = null ?? "Default value works";
console.log(notExist);  // Output: "Default value in effect"


10.3 A practical champion combo: use ?. and ?? together

๐Ÿ‘‘ A perfect practical pattern: prevent errors with optional chaining and set a default with nullish coalescing.

const user = {
  name: "Lee Sun-shin",
  // No profile information
};

// "Find the user's city, using the ?. operator to prevent the chain from breaking,
// and if there's ultimately no value (??), display 'Unknown'!"

const userCity = user?.profile?.city ?? "Unknown";

console.log(`Location: ${userCity}`); 
// Output: Location: Unknown

๐Ÿ”„ 11. String.replaceAll(): Replace every occurrence in a string at once (ES12)


11.1 The limitations of replace() and the old approach

๐Ÿšจ The old replace() had the serious limitation of changing only the first occurrence of a word.

const text = "apple, banana, apple, grape, apple";

// 1. Using the standard `replace()` method (only the first "apple" is replaced with "watermelon")
const result1 = text.replace("apple", "watermelon");
console.log(result1); 
// Output: "watermelon, banana, apple, grape, apple"

// 2. [Before ES11] To replace the entire string, you had to use complex "regular expressions (Regex)" and the global flag (g).
const result2 = text.replace(/apple/g, "watermelon");
console.log(result2); 
// Output: "watermelon, banana, watermelon, grape, watermelon"


11.2 The perfect solution: replaceAll()

โœจ Now you can replace every occurrence using an intuitive string, without complex regular-expression syntax (/ /g).

const text = "apple, banana, apple, grape, apple";

// โœ… [Latest ES12 method] Just enter the word you want to replace as a stringโ€”that's it!
const finalResult = text.replaceAll("apple", "watermelon");

console.log(finalResult); 
// Output: "watermelon, banana, watermelon, grape, watermelon"


11.3 Practical tip: Method chaining and data cleansing

๐Ÿงน Very useful for cleaning up messy user input (such as excessive special characters) all at once.

// User-entered messy phone number data
const dirtyPhone = "010-1234-5678 (myself)";

// When you want to remove all '-' symbols and the text '(myself)' (using chaining)
const cleanPhone = dirtyPhone
  .replaceAll("-", "")       // Remove all hyphens
  .replaceAll(" (Me)", ''); // Remove the text " (Me)"

console.log(cleanPhone); 
// Output: "01012345678"