Search
Duplicate

3.함수와 객체

목차(클릭하세요)

1. JS만의 함수 특징

1-1. 함수와 호이스팅

호이스팅(Hoisting)은 변수와 함수 선언이 해당 스코프의 최상단으로 "끌어올려지는" JavaScript의 독특한 동작

함수 선언문 호이스팅

// 함수 호출이 선언보다 먼저 와도 동작함 sayHello(); // "안녕하세요!" - 정상 동작 function sayHello() { console.log("안녕하세요!"); }
JavaScript
복사
이게 왜 될까?
자바스크립트는 코드를 실행하기전 준비단계를 거치는데, 이 때 중첩함수가 아닌 함수들은 모두 찾아 미리 생성해두는 자바스크립트의 괴랄한 특성때문 인듯
무슨 장점이라도?
함수 선언의 위치를 강제하지 않아 더 유연한 프로그래밍이 가능해짐

1-2.함수 표현식

함수를 변수에 할당하는 방식으로, 함수를 값처럼 다룰 수 있음
// 함수 선언문 function declaration() { return "선언문"; } // 함수 표현식 const expression = function() { return "표현식"; }; // 명명된 함수 표현식 const namedExpression = function myFunc() { return "명명된 표현식"; };
JavaScript
복사
[함수표현식에서 무슨 차이?]
함수 표현식 = 기본적으로 익명 함수를 변수/상수에 할당하는 방식.
명명된 함수 표현식 = 이름이 붙은 함수 표현식 (주로 디버깅 편의를 위해 사용).

1-3.화살표 함수

ES6에서 도입된 간결한 함수 표현 방식
익명함수를 매우 간결하게 작성할 때 사용
일종의 함수표현식의 단축문법
[사용법] let funcA = (매개변수) => 반환값;
JavaScript
복사
// 기본 화살표 함수 const add = (a, b) => { return a + b; }; // 한 줄 함수 - return 생략 가능 const multiply = (a, b) => a * b; // 매개변수가 하나일 때 - 괄호 생략 가능 const square = x => x * x; // 매개변수가 없을 때는 괄호 생략 불가능 const sayHello = () => "안녕하세요!";
JavaScript
복사
[화살표 함수 사용Tip]
화살표 함수(()=>{})를 변수에 담을 때는 let보다 const를 더 자주 사용
이유: 대부분의 경우 함수 정의가 한 번 정해지면 바뀌지 않으므로 const가 적합
const add = (a, b) => a + b; // 안전 // add = 100; ❌ 에러 발생
JavaScript
복사

1-3.함수 연습문제

//코드샌드박스에서 출력을 위한 기본 코드 import "./styles.css"; // HTML 먼저 생성 document.getElementById("app").innerHTML = ` <h1 id="title">🎮 게임 함수 마스터</h1> <p id="change">빈칸(???)을 채워서 게임을 완성하세요!</p> <div id="test" style="white-space: pre-line;"></div> `; let result = ""; // === 1. 게임 초기화 (함수 호이스팅) === result += "=== 1. 게임 초기화 ===\n"; // 사용자 이름 입력받기 ??? playerName = prompt("플레이어 이름을 입력하세요:") ??? "익명"; // 값이 변할 수 있는 변수 키워드, 기본값을 제공하는 연산자 // 함수 선언 전에 호출 가능! (호이스팅) let gameStart = startGame(playerName); result += gameStart + "\n"; ??? startGame(name) { // 호이스팅이 가능한 함수 선언 방식 return `${name}님의 모험이 시작됩니다!`; } result += "\n"; // === 2. 게임 플레이 (함수 표현식) === result += "=== 2. 게임 플레이 ===\n"; // 몬스터 전투 함수 (함수 표현식) ??? battleMonster = function(playerLevel, monsterLevel) { // 변하지 않는 변수에 함수를 저장 ??? damage = playerLevel * 10 - monsterLevel * 5; // 함수 내부에서 계산값을 저장할 변수 return damage > 0 ? "승리!" : "패배..."; }; // 아이템 획득 함수 (화살표 함수) ??? getItem = (itemName) ??? `${itemName} 획득!`; // 짧은 함수를 간단하게 쓰는 방법, 화살표 기호 let battle1 = battleMonster(5, 3); let battle2 = battleMonster(2, 4); let item = getItem("마법검"); result += `전투 1: ${battle1}\n`; result += `전투 2: ${battle2}\n`; result += `아이템: ${item}\n\n`; // === 3. 게임 결과 (화살표 함수 활용) === result += "=== 3. 게임 결과 ===\n"; let scores = [100, 250, 180, 320, 90]; // 고득점만 필터링 (화살표 함수) ??? highScores = scores.???(score ??? score >= 200); // 조건에 맞는 요소만 걸러내는 배열 메서드 // 점수 합계 계산 (화살표 함수) ??? total = scores.???(???sum, score) ??? sum + score, 0); // 배열을 하나의 값으로 축약하는 메서드, 누적값 매개변수 // 최종 등급 계산 (화살표 함수) ??? getGrade = (score) ??? score >= 300 ??? "S" ??? score >= 200 ??? "A" ??? "B"; // 조건에 따라 값을 선택하는 연산자 let finalGrade = getGrade(total); result += `${playerName}님의 결과:\n`; result += `고득점: ${highScores.join(", ")}\n`; result += `총점: ${total}\n`; result += `최종 등급: ${finalGrade}\n`; document.getElementById("test").textContent = result;
JavaScript
복사

2. 객체와 배열

2-1. 객체 생성과 프로퍼티(with 리터럴??)

객체는 키(key)와 값(value)의 쌍으로 데이터를 저장하는 JavaScript의 핵심 데이터 구조
// 기본 객체 생성 const student = { name: "양파고", age: 17, school: "한국고등학교", isStudent: true }; // 중첩 객체 const player = { name: "철수", stats: { level: 15, hp: 100, mp: 50 }, inventory: ["검", "방패", "포션"] };
JavaScript
복사
[리터럴이란?]
literal = “값 그 자체를 코드에 직접 적어 표현한 것”
즉, 프로그래머가 코드에 직접 써넣은 값을 뜻함
예시
숫자 리터럴: 10, 3.14
문자열 리터럴: "hello", 'world'
불리언 리터럴: true, false
객체 리터럴이란?
중괄호 {}를 사용해 객체를 직접 정의하는 문법
객체 리터럴과 반대되는 개념이 표현식이라 생각하면 편함
new Object() → 객체를 만드는 표현식(expression)
{ name: "A" } → 직접 속성과 값을 적어 객체를 만드는 리터럴(literal)
구분
코드 예시
설명
사용 빈도
리터럴
const obj = { name: "민수", age: 17 };
직접 {} 안에 값 써서 객체 생성
★★★★★
표현식 - 생성자 함수 호출
new Student("민수", 17)
함수 정의 후 new로 호출
★★★☆☆
표현식 - new Object()
new Object()
내장 생성자 호출 후 속성 추가
★☆☆☆☆
[생성자 함수로 객체생성하기]
function Student(name, age) { this.name = name; this.age = age; this.study = function() { return `${this.name}이(가) 공부 중입니다.`; }; } const student1 = new Student("민수", 17);
JavaScript
복사

2-2.객체 프로퍼티 다루기

[프로퍼티란?]
객체(Object)는 키(key)와 값(value)의 쌍으로 이루어짐.
이때 를 프로퍼티 이름(property name), 을 프로퍼티 값(property value)라고 부름.
즉, 프로퍼티 = 키 + 값 쌍 전체를 가리킴
1.
프로퍼티의 특징
동적 추가: 언제든지 새로운 프로퍼티 추가 가능
다양한 타입: 문자열, 숫자, 불리언, 함수, 객체 등 모든 타입 저장 가능
메서드: 객체 내부의 함수를 메서드라고 함
2.
프로퍼티의 접근방법은 크게 2가지: 점 표기법, 대괄호 표기법
점표기법
const user = { name: "양파고", age: 17, email: "yangphago@example.com" }; console.log(user.name); // "양파고" console.log(user.age); // 17
JavaScript
복사
대괄호 표기법
console.log(user["name"]); // "양파고" console.log(user["age"]); // 17 // 변수를 키로 사용 가능 const key = "email"; console.log(user[key]); // "yangphago@example.com" // 공백이나 특수문자가 있는 키 const game = { "player name": "철수", "high-score": 1500 }; console.log(game["player name"]); // "철수"
JavaScript
복사
3.
프로퍼티 추가&수정하기
const car = { brand: "현대", model: "아반떼" }; // 프로퍼티 추가 car.year = 2023; car["color"] = "흰색"; // 프로퍼티 수정 car.model = "소나타";
JavaScript
복사
4.
프로퍼티 삭제하기
delete car.color; console.log(car.color); // undefined
JavaScript
복사

2-3.배열과 배열 인덱스

배열은 순서가 있는 데이터의 집합으로, 인덱스를 통해 요소에 접근
자바스크립트의 배열은 단순히 순서를 가진 특별한 객체
인덱스(0, 1, 2, …)가 처럼 작동하고,
배열 객체 안에는 length 같은 프로퍼티push, pop 같은 메서드가 기본 내장돼 있음
const games = ["롤토체스", "리그오브레전드"]; // 끝에 추가/제거 games.push("발로란트"); // ["롤토체스", "리그오브레전드", "발로란트"] games.pop(); // "발로란트" 제거 // 앞에 추가/제거 games.unshift("오버워치"); // ["오버워치", "롤토체스", "리그오브레전드"] games.shift(); // "오버워치" 제거
JavaScript
복사

2-4.date객체와 날짜

Date 객체는 날짜와 시간을 다루는 JavaScript의 내장 객체
const now = new Date(); // 기본 문자열 변환 console.log(now.toString()); // 전체 날짜 시간 console.log(now.toDateString()); // 날짜만 console.log(now.toTimeString()); // 시간만 // 로케일 형식 console.log(now.toLocaleDateString("ko-KR")); // "2025. 3. 7." console.log(now.toLocaleTimeString("ko-KR")); // "오후 2:30:45" //date함수의 사용법 function formatDate(date) { // 1. 연도(yyyy) 추출 const year = date.getFullYear(); // 2. 월(mm) 추출 → getMonth()는 0~11을 반환하므로 +1 // padStart(2, '0')로 두 자리 맞추기 (예: 3 → 03) const month = String(date.getMonth() + 1).padStart(2, '0'); // 3. 일(dd) 추출 → padStart로 두 자리 맞춤 const day = String(date.getDate()).padStart(2, '0'); // 4. yyyy-mm-dd 형식의 문자열로 반환 return `${year}-${month}-${day}`; }
JavaScript
복사

2-5.연습문제

객체 생성:
const aespa = {}
프로퍼티 접근:
점 표기법: aespa.groupName
대괄호 표기법: aespa["hit2022"]
배열 메서드:
filter(), map(), reduce(), length
Date 객체:
new Date(), getFullYear(), getMonth(), getDate()
//코드샌드박스에서 출력을 위한 기본 코드 import "./styles.css"; // HTML 먼저 생성 document.getElementById("app").innerHTML = ` <h1 id="title">🎵 aespa 히트곡 관리 시스템</h1> <p id="change">빈칸(???)을 채워서 aespa 정보 시스템을 완성하세요!</p> <div id="test" style="white-space: pre-line;"></div> `; let result = ""; // === 1. 객체 리터럴로 aespa 기본 정보 생성 === result += "=== 1. aespa 기본 정보 ===\n"; ??? aespa = { // 변경되지 않는 객체를 선언할 때 사용하는 키워드 groupName: "aespa", debutDate: new Date("2020-11-17"), company: "SM Entertainment", members: ["카리나", "지젤", "윈터", "닝닝"], }; result += `그룹명: ${aespa.???}\n`; // 객체의 속성에 접근하는 방법 중 하나 result += `소속사: ${aespa.???}\n\n`; // 위와 같은 방식으로 접근 // === 2. 히트곡 프로퍼티 추가 (점 표기법) === result += "=== 2. 히트곡 추가 (점 표기법) ===\n"; // 점 표기법으로 히트곡 추가 aespa.??? = "Black Mamba"; // 새로운 속성 이름을 정해보세요 aespa.??? = []; // 여러 곡을 담을 수 있는 데이터 구조 aespa.hit.???("Next Level"); // 배열에 요소를 추가하는 메서드 aespa.hit.???("Savage"); // 같은 메서드를 사용 aespa.hit.???("위 플래쉬"); // 계속 같은 메서드 result += `데뷔곡: ${aespa.debut_song}\n`; result += `히트곡 리스트: ${aespa.hit}\n`; // === 3. 추가 정보 (대괄호 표기법) === result += "\n=== 3. 추가 정보 (대괄호 표기법) ===\n"; // 대괄호 표기법으로 정보 추가 aespa["???"] = aespa.members.???; // 앨범 수와 관련된 속성명, 배열의 크기를 알 수 있는 속성 result += `발매 앨범의 갯수: ${aespa.total_albums}\n\n`; // === 4. 인기 멤버 순서에 따라 배열로 변환 === result += "=== 4. 인기멤버 배열 관리 ===\n"; // 인기순으로 멤버 재배열 ??? superstar_number = [ // 변경되지 않을 배열 선언 aespa.members[???], // 첫 번째 멤버 aespa.members[???], // 세 번째 멤버 aespa.members[???], // 네 번째 멤버 aespa.members[???], // 두 번째 멤버 ]; // 가장 인기 있는 멤버 (첫 번째) ??? mostPopular = superstar_number[???]; // 변경되지 않을 변수, 첫 번째 요소 result += `최고의 멤버: ${mostPopular}\n\n`; // === 5. Date 객체를 활용한 기념일 계산 === result += "=== 5. 데뷔 기념일 계산 ===\n"; ??? today = new Date(); // 오늘 날짜를 담을 변수 ??? debutYear = aespa.debutDate.???(); // 연도를 가져오는 메서드 ??? currentYear = today.???(); // 현재 연도를 가져오는 메서드 ??? yearsActive = currentYear - debutYear; // 활동 기간 계산 // 올해 데뷔 기념일 ??? thisYearAnniversary = new Date( // 올해 기념일 날짜 객체 currentYear, aespa.debutDate.???(), // 월을 가져오는 메서드 aespa.debutDate.???() // 일을 가져오는 메서드 ); // 기념일까지 남은 일수 ??? daysUntilAnniversary = Math.ceil( // 값이 변경될 수 있는 변수 (thisYearAnniversary - today) / (1000 * 60 * 60 * 24) ); // 기념일이 지났다면 내년 기념일 계산 if (daysUntilAnniversary < 0) { thisYearAnniversary.???(currentYear + 1); // 연도를 설정하는 메서드 daysUntilAnniversary = Math.ceil( (thisYearAnniversary - today) / (1000 * 60 * 60 * 24) ); } result += `데뷔: ${debutYear}년\n`; result += `활동 기간: ${yearsActive}년\n`; result += `다음 기념일까지: ${daysUntilAnniversary}일\n\n`; document.getElementById("test").textContent = result;
JavaScript
복사

3. 비동기 처리

파이썬과 달리 JS에서 비동기 처리가 중요한 이유?
fetch() 같은 HTTP 요청은 시간이 오래 걸리므로 비동기로 처리해야 UI가 멈추지 않음.
동기(Synchronous): 작업이 순서대로 하나씩 완료되어야 다음 작업을 진행
비동기(Asynchronous): 작업을 시작하고 완료를 기다리지 않고 다음 작업을 바로 진행
구분
동기(Sync)
비동기(Async)
실행 흐름
한 줄 끝나야 다음 줄 실행
오래 걸리는 작업은 백그라운드로 넘기고, 나머지 코드 먼저 실행
장점
코드 이해가 단순
UI 멈춤 방지, 효율적 리소스 활용
예시
alert("hi")
fetch(), setTimeout()

3-1. 동기와 비동기

은행창구는 동기 처리일까? 비동기 처리일까?
은행창구 = 동기 처리
한 사람의 업무가 끝나야 다음 손님이 처리 가능
까페운영은 동기 처리일까? 비동기 처리일까?
카페 = 비동기 처리
주문받고 나서 음료가 나올 때까지 기다리는 동안, 다른 손님의 주문도 처리 가능

3-2.비동기함수의 실제 사용법(초급)

setTimeout을 이용한 비동기 처리
1.
자바스크립트는 setTimeout 메서드를 이용해 비동기적 처리 가능
console.log("주문 접수"); setTimeout(() => { console.log("커피 준비 완료 ☕"); }, 2000); // 2초 뒤 실행 console.log("다른 손님 주문 받는 중...");
JavaScript
복사
2.
조금 더 복잡한 형태의 setTimeout 이용
function processOrder() { console.log("주문 접수"); setTimeout(() => { console.log("재료 준비 완료"); setTimeout(() => { console.log("조리 완료"); setTimeout(() => { console.log("서빙 완료"); }, 2000); }, 3000); }, 1000); } processOrder()
JavaScript
복사

3-2.비동기함수의 실제 사용법(고급)

프로미스 객체를 이용한 비동기 처리
1.
3가지 상태: 대기, 성공, 실패
대기(Pending): 아직 결과가 나오지 않음
성공(Fulfilled): 작업이 성공적으로 완료됨
실패(Rejected): 작업이 실패함
2.
실행함수가 제공받는 2개의 매개변수: resolve, reject
resolve: 작업 성공 시 호출하는 함수
reject: 작업 실패 시 호출하는 함수
Promise를 만들 때 resolve만 쓰고 reject는 생략하는 게 가능
개발자가 가독성을 위해 이름을 줄일 수도 있음 →
resolveres, rejectrej
const myPromise = new Promise((resolve, reject) => { // resolve: 성공했을 때 호출하는 함수 // reject: 실패했을 때 호출하는 함수 }); const myPromise = new Promise((res, rej) => { const num = Math.random(); // 0~1 사이 난수 if (num > 0.5) { res(`✅ 성공! 난수=${num}`); } else { rej(`❌ 실패! 난수=${num}`); } }); myPromise .then(msg => console.log("성공:", msg)) .catch(err => console.log("실패:", err));
JavaScript
복사
3.
프로미스 객체를 활용한 예시
// Promise 생성 function fetchUserData(userId) { return new Promise((resolve, reject) => { // 실제로는 서버 요청이지만 시뮬레이션 setTimeout(() => { if (userId > 0) { resolve({ id: userId, name: "양파고", email: "yangphago@example.com" }); } else { reject(new Error("유효하지 않은 사용자 ID")); } }, 1000); }); } // Promise 사용 fetchUserData(123) .then(userData => { console.log("사용자 정보:", userData); }) .catch(error => { console.log("오류 발생:", error.message); });
JavaScript
복사
[참고] settimeout메서드를 쓰는 것과 프로미스 객체를 사용하는 것의 차이?
1.
setTimeout 메서드
타이머 기반 비동기 처리
일정 시간이 지난 후 특정 코드를 실행하는 용도
단순한 이벤트 지연/예약 실행에 적합
콜백(callback) 방식으로 동작
2.
Promise 객체
Promise는 비동기 작업의 완료(성공) 또는 실패를 나타내는 객체로, 그 결과값을 나중에 사용할 수 있게 해줌
네트워크 요청, 파일 읽기/쓰기 같은 결과가 나중에 도착하는 작업을 처리할 때 사용
세 가지 상태(Pending → Fulfilled/Rejected)를 가짐
then, catch, finally 체이닝 가능 → 가독성 좋고 에러 처리 쉬움
.then() → 작업 성공 시 실행할 콜백 등록
.catch() → 작업 실패 시 실행할 콜백 등록
.finally() → 성공/실패와 관계없이 마지막에 실행할 콜백 등록
[JS의 콜백함수?]
매개변수로 함수 객체를 전달해서 호출 함수 내에서 매개변수 함수를 실행하는 것 콜백 함수란 파라미터로 일반적인 변수나 값을 전달하는 것이 아닌 함수 자체를 전달하는 것 어차피 매개변수에 함수를 전달해 일회용으로 사용하기 때문에 굳이 함수의 이름을 명시할 필요가 없어 보통 콜백 함수 형태로 함수를 넘겨줄때 함수의 이름이 없는 '익명 함수' 형태로 넣어주게 됨
function sayHello(name, callback) { const words = '안녕하세요 내 이름은 ' + name + ' 입니다.'; callback(words); // 매개변수의 함수(콜백 함수) 호출 } sayHello("양파고", function printing(name) { console.log(name); // 안녕하세요 내 이름은 인파 입니다. });
JavaScript
복사
화살표 함수모양의 콜백함수
function sayHello(callback) { let name = "phago"; callback(name); // 콜백 함수 호출 } // 익명 화살표 콜백 함수 sayHello((name) => { console.log("Hello, " + name); }); // Hello, Alice
JavaScript
복사
3.
체이닝 방식의 비동기 처리
[체이닝?]
메서드를 연속적으로 연결해서 호출하는 개념, 체이닝은 약간의 순차 실행 느낌
const order = new Promise((resolve, reject) => { const stock = true; if (stock) resolve("커피 준비 완료 ☕"); else reject("재고 없음 ❌"); }); order .then(msg => { console.log("성공:", msg); return "손님께 전달 완료 ✅"; // 다음 then으로 값 전달 }) .then(nextMsg => { console.log(nextMsg); }) .catch(err => { console.log("실패:", err); }) .finally(() => { console.log("작업 종료 🚪"); });
JavaScript
복사
4.
병렬처리 방식의 비동기처리
// 각각의 독립적인 작업들을 별도의 Promise로 분리 const checkStock = new Promise((resolve, reject) => { const stock = true; if (stock) resolve("재고 확인 완료 ✅"); else reject("재고 없음 ❌"); }); const prepareCoffee = new Promise((resolve) => { setTimeout(() => resolve("커피 준비 완료 ☕"), 1000); }); const preparePackaging = new Promise((resolve) => { setTimeout(() => resolve("포장 준비 완료 📦"), 800); }); const notifyCustomer = new Promise((resolve) => { setTimeout(() => resolve("손님 호출 완료 📢"), 500); }); // Promise.all로 병렬 처리 Promise.all([checkStock, prepareCoffee, preparePackaging, notifyCustomer]) .then(results => { console.log("모든 작업 완료:"); results.forEach(result => console.log(result)); console.log("주문 처리 완료 🎉"); }) .catch(err => { console.log("실패:", err); }) .finally(() => { console.log("작업 종료 🚪"); });
JavaScript
복사
[어려움]
체이닝 = 비동기 순차 실행
병렬 = 비동기 동시 실행
즉, 둘 다 비동기이지만,
체이닝은 "비동기 + 한 줄씩 기다리며 처리"
병렬은 "비동기 + 여러 줄을 동시에 실행"

3-4.연습문제

[중요개념 돌아보기]
1.
resolve (Promise 성공 처리)
2.
task (타이머 함수)
3.
await (비동기 작업 기다리기)
4.
Promise.all (여러 비동기 작업을 동시에 처리)
5.
async (비동기 함수 선언)
// 기본 셋업 import "./styles.css"; document.getElementById("app").innerHTML = ` <h1>🍽️ 저녁식사 준비 - 비동기 실습</h1> <div id="test" style="white-space: pre-line;"></div> `; // ??? 문자열을 받아 특정 영역에 출력하고 줄바꿈("\n")을 추가하는 함수 function write(msg) { document.getElementById("test").textContent += msg + "\n"; } // 공통 타이머 작업 (ms 뒤 ??? 호출) // 힌트: 일정 시간이 지난 뒤 Promise를 성공 상태로 바꿔주는 함수 function task(taskName, ms) { return new Promise(function (????) { setTimeout(function () { ????(taskName); // 일정 시간 뒤 작업 이름 반환 }, ms); }); } // 개별 작업 실행 + 시간 측정/출력 async function doTask(taskName, ms) { const s = performance.now(); await ????(taskName, ms); // 힌트: 위에서 만든 "타이머 함수" 호출 write(`${taskName} 성공 (${((performance.now() - s) / 1000).toFixed(2)}s)`); } // 시간 상수 const T_CHICKEN = 5000; const T_SALAD = 3000; const T_TABLE = 2000; // 1) 체이닝(순차) // ??? 키워드를 사용해 작업을 순서대로 기다리며 실행 async function runChaining() { write("=== 체이닝 (순차 실행) ==="); const t0 = performance.now(); await ????("치킨", T_CHICKEN); await ????("샐러드", T_SALAD); await ????("상차리기", T_TABLE); write( `총 실제 소요 시간: ${((performance.now() - t0) / 1000).toFixed(2)}s\n` ); } // 2) 병렬(동시) // 여러 비동기 작업을 동시에 실행하려면 ??? 메서드 사용 async function runParallel() { write("=== 병렬 (동시 실행) ==="); const t0 = performance.now(); await ????.????([ doTask("치킨", T_CHICKEN), doTask("샐러드", T_SALAD), doTask("상차리기", T_TABLE), ]); write( `총 실제 소요 시간: ${((performance.now() - t0) / 1000).toFixed(2)}s\n` ); } // 실행 (순서 보장) // ??? 함수 표현식 → 비동기 실행 흐름 제어 가능 (???? () => { await runChaining(); await runParallel(); })();
JavaScript
복사