JavaScript 배열과 객체 | Array, Object 메서드 완벽 정리
이 글의 핵심
JavaScript 배열과 객체에 대한 실전 가이드입니다. Array, Object 메서드 완벽 정리 등을 예제와 함께 상세히 설명합니다.
들어가며
배열과 객체
배열(Array)은 순서가 있는 값의 목록이고, 객체(Object)는 이름(키)으로 값을 묶은 맵에 가깝습니다. 둘 다 동적이므로 실행 중에 요소·속성을 자유롭게 넣고 빼는 패턴이 흔합니다.
1. 배열 (Array)
배열 생성과 접근
// 배열 생성
let fruits = ["apple", "banana", "cherry"];
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, "hello", true, null, { name: "홍길동" }];
// 빈 배열
let empty1 = [];
let empty2 = new Array();
// 특정 크기 배열
let arr = new Array(5); // [empty × 5]
console.log(arr.length); // 5
// 인덱싱 (0부터 시작)
console.log(fruits[0]); // apple
console.log(fruits[1]); // banana
console.log(fruits[-1]); // undefined (음수 인덱스 없음)
// 마지막 요소
console.log(fruits[fruits.length - 1]); // cherry
// 수정
fruits[1] = "grape";
console.log(fruits); // ['apple', 'grape', 'cherry']
배열 메서드: 추가/삭제
let arr = [1, 2, 3];
// push: 끝에 추가
arr.push(4);
console.log(arr); // [1, 2, 3, 4]
// pop: 끝에서 제거
let last = arr.pop();
console.log(last); // 4
console.log(arr); // [1, 2, 3]
// unshift: 앞에 추가
arr.unshift(0);
console.log(arr); // [0, 1, 2, 3]
// shift: 앞에서 제거
let first = arr.shift();
console.log(first); // 0
console.log(arr); // [1, 2, 3]
// splice: 중간 삽입/삭제
arr.splice(1, 1); // 인덱스 1에서 1개 제거
console.log(arr); // [1, 3]
arr.splice(1, 0, 2); // 인덱스 1에 2 삽입
console.log(arr); // [1, 2, 3]
arr.splice(1, 1, 10, 20); // 인덱스 1에서 1개 제거하고 10, 20 삽입
console.log(arr); // [1, 10, 20, 3]
배열 메서드: 검색
let numbers = [1, 2, 3, 4, 5, 3];
// indexOf: 첫 번째 인덱스
console.log(numbers.indexOf(3)); // 2
console.log(numbers.indexOf(10)); // -1 (없음)
// lastIndexOf: 마지막 인덱스
console.log(numbers.lastIndexOf(3)); // 5
// includes: 존재 여부 (ES2016+)
console.log(numbers.includes(3)); // true
console.log(numbers.includes(10)); // false
// find: 조건에 맞는 첫 요소
let found = numbers.find(x => x > 3);
console.log(found); // 4
// findIndex: 조건에 맞는 첫 인덱스
let index = numbers.findIndex(x => x > 3);
console.log(index); // 3
// some: 하나라도 조건 만족
console.log(numbers.some(x => x > 4)); // true
// every: 모두 조건 만족
console.log(numbers.every(x => x > 0)); // true
console.log(numbers.every(x => x > 2)); // false
배열 메서드: 변환
배열을 변환하는 핵심 메서드들입니다:
let numbers = [1, 2, 3, 4, 5];
// 1. map: 각 요소를 변환하여 새 배열 생성
let doubled = numbers.map(x => x * 2);
// 동작:
// [1, 2, 3, 4, 5]
// ↓ ↓ ↓ ↓ ↓ 각 요소에 함수 적용
// [2, 4, 6, 8, 10]
//
// 원본 배열은 변경되지 않음 (불변성)
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (원본 유지)
// map 실전 예시: 객체 배열 변환
let users = [
{ name: "홍길동", age: 25 },
{ name: "김철수", age: 30 }
];
let names = users.map(user => user.name);
console.log(names); // ['홍길동', '김철수']
// 2. filter: 조건을 만족하는 요소만 선택
let evens = numbers.filter(x => x % 2 === 0);
// 동작:
// [1, 2, 3, 4, 5]
// ↓ ✓ ↓ ✓ ↓ 조건 체크 (짝수만 통과)
// [2, 4]
console.log(evens); // [2, 4]
// filter 실전 예시: 성인만 필터링
let adults = users.filter(user => user.age >= 20);
// 3. reduce: 배열을 하나의 값으로 축약
let sum = numbers.reduce((acc, x) => acc + x, 0);
// acc: 누적값 (accumulator)
// x: 현재 요소
// 0: 초기값
//
// 동작 과정:
// acc=0, x=1 → 0+1=1
// acc=1, x=2 → 1+2=3
// acc=3, x=3 → 3+3=6
// acc=6, x=4 → 6+4=10
// acc=10, x=5 → 10+5=15
console.log(sum); // 15
// reduce로 평균 계산
let avg = numbers.reduce((acc, x, i, arr) => {
// acc: 누적값
// x: 현재 요소
// i: 현재 인덱스
// arr: 원본 배열
acc += x;
// 마지막 요소면 평균 계산
return i === arr.length - 1 ? acc / arr.length : acc;
}, 0);
console.log(avg); // 3
// reduce로 객체 변환 (배열 → 객체)
let words = ["apple", "banana", "cherry"];
let wordLengths = words.reduce((acc, word) => {
// acc: 누적 객체 (처음엔 빈 객체 {})
// word: 현재 문자열
acc[word] = word.length; // 객체에 프로퍼티 추가
return acc; // 누적 객체 반환
}, {});
console.log(wordLengths);
// { apple: 5, banana: 6, cherry: 6 }
// reduce로 최댓값 찾기
let max = numbers.reduce((acc, x) => Math.max(acc, x));
console.log(max); // 5
// reduce로 배열 평탄화
let nested = [[1, 2], [3, 4], [5]];
let flattened = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flattened); // [1, 2, 3, 4, 5]
메서드 체이닝:
// map, filter, reduce를 연결하여 사용
let result = numbers
.filter(x => x > 2) // [3, 4, 5]
.map(x => x * 2) // [6, 8, 10]
.reduce((a, b) => a + b, 0); // 24
console.log(result); // 24
원본 배열 변경 여부:
- 변경함:
push,pop,shift,unshift,splice,sort,reverse - 변경 안 함:
map,filter,reduce,slice,concat
배열 메서드: 정렬
let numbers = [3, 1, 4, 1, 5, 9, 2];
// sort: 원본 변경
numbers.sort();
console.log(numbers); // [1, 1, 2, 3, 4, 5, 9]
// 내림차순
numbers.sort((a, b) => b - a);
console.log(numbers); // [9, 5, 4, 3, 2, 1, 1]
// 문자열 정렬
let words = ["banana", "apple", "cherry"];
words.sort();
console.log(words); // ['apple', 'banana', 'cherry']
// 객체 배열 정렬
let users = [
{ name: "홍길동", age: 25 },
{ name: "김철수", age: 30 },
{ name: "이영희", age: 20 }
];
users.sort((a, b) => a.age - b.age);
console.log(users);
// [{ name: '이영희', age: 20 }, ...]
// reverse: 역순
numbers.reverse();
console.log(numbers); // [1, 1, 2, 3, 4, 5, 9]
배열 메서드: 기타
let arr = [1, 2, 3, 4, 5];
// slice: 부분 배열 (원본 유지)
let sub = arr.slice(1, 4);
console.log(sub); // [2, 3, 4]
console.log(arr); // [1, 2, 3, 4, 5] (원본 유지)
// concat: 배열 연결
let arr1 = [1, 2];
let arr2 = [3, 4];
let combined = arr1.concat(arr2);
console.log(combined); // [1, 2, 3, 4]
// join: 문자열로 변환
let joined = arr.join(", ");
console.log(joined); // 1, 2, 3, 4, 5
// flat: 평탄화 (ES2019+)
let nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]
// flatMap: map + flat
let words = ["hello world", "foo bar"];
let chars = words.flatMap(word => word.split(" "));
console.log(chars); // ['hello', 'world', 'foo', 'bar']
2. 객체 (Object)
객체 생성과 접근
// 객체 리터럴
let person = {
name: "홍길동",
age: 25,
city: "서울"
};
// 빈 객체
let empty = {};
// 접근
console.log(person.name); // 홍길동 (점 표기법)
console.log(person["age"]); // 25 (대괄호 표기법)
// 동적 키
let key = "city";
console.log(person[key]); // 서울
// 추가/수정
person.job = "개발자"; // 추가
person.age = 26; // 수정
console.log(person);
// { name: '홍길동', age: 26, city: '서울', job: '개발자' }
// 삭제
delete person.city;
console.log(person);
// { name: '홍길동', age: 26, job: '개발자' }
객체 메서드
let person = {
name: "홍길동",
age: 25,
greet: function() {
console.log(`안녕하세요, ${this.name}입니다.`);
},
// 축약 문법 (ES6+)
introduce() {
console.log(`${this.age}살 ${this.name}입니다.`);
}
};
person.greet(); // 안녕하세요, 홍길동입니다.
person.introduce(); // 25살 홍길동입니다.
Object 정적 메서드
let person = { name: "홍길동", age: 25, city: "서울" };
// Object.keys(): 키 배열
console.log(Object.keys(person));
// ['name', 'age', 'city']
// Object.values(): 값 배열
console.log(Object.values(person));
// ['홍길동', 25, '서울']
// Object.entries(): [키, 값] 배열
console.log(Object.entries(person));
// [['name', '홍길동'], ['age', 25], ['city', '서울']]
// 순회
for (let [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
// Object.assign(): 객체 병합
let defaults = { theme: "dark", lang: "ko" };
let userSettings = { lang: "en" };
let settings = Object.assign({}, defaults, userSettings);
console.log(settings); // { theme: 'dark', lang: 'en' }
// Object.freeze(): 불변 객체
let frozen = Object.freeze({ x: 10 });
frozen.x = 20; // 무시됨
console.log(frozen.x); // 10
// Object.seal(): 추가/삭제 불가, 수정 가능
let sealed = Object.seal({ x: 10 });
sealed.x = 20; // OK
sealed.y = 30; // 무시됨
console.log(sealed); // { x: 20 }
3. 구조 분해 (Destructuring)
배열 구조 분해
let arr = [1, 2, 3, 4, 5];
// 기본
let [a, b] = arr;
console.log(a, b); // 1 2
// 나머지
let [first, ...rest] = arr;
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
// 건너뛰기
let [x, , z] = arr;
console.log(x, z); // 1 3
// 기본값
let [p, q, r, s, t, u = 0] = arr;
console.log(u); // 0
// 스왑
let [m, n] = [10, 20];
[m, n] = [n, m];
console.log(m, n); // 20 10
객체 구조 분해
let person = { name: "홍길동", age: 25, city: "서울" };
// 기본
let { name, age } = person;
console.log(name, age); // 홍길동 25
// 새 변수명
let { name: userName, age: userAge } = person;
console.log(userName, userAge); // 홍길동 25
// 기본값
let { name, age, job = "없음" } = person;
console.log(job); // 없음
// 나머지
let { name, ...rest } = person;
console.log(name); // 홍길동
console.log(rest); // { age: 25, city: '서울' }
// 중첩 구조 분해
let user = {
id: 1,
profile: {
name: "홍길동",
age: 25
}
};
let { profile: { name, age } } = user;
console.log(name, age); // 홍길동 25
함수 매개변수 구조 분해
// 객체 구조 분해
function printUser({ name, age, city = "서울" }) {
console.log(`${name} (${age}세, ${city})`);
}
printUser({ name: "홍길동", age: 25 });
// 홍길동 (25세, 서울)
// 배열 구조 분해
function getMinMax([first, ...rest]) {
return {
min: Math.min(first, ...rest),
max: Math.max(first, ...rest)
};
}
console.log(getMinMax([3, 1, 4, 1, 5]));
// { min: 1, max: 5 }
4. Spread와 Rest 연산자
Spread 연산자 (…)
// 배열 펼치기
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 배열 복사
let original = [1, 2, 3];
let copy = [...original];
copy[0] = 100;
console.log(original); // [1, 2, 3] (원본 유지)
console.log(copy); // [100, 2, 3]
// 함수 인자로 펼치기
function add(a, b, c) {
return a + b + c;
}
let numbers = [1, 2, 3];
console.log(add(...numbers)); // 6
// 객체 펼치기 (ES2018+)
let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3, d: 4 };
let merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
// 객체 복사
let person = { name: "홍길동", age: 25 };
let personCopy = { ...person };
personCopy.age = 26;
console.log(person.age); // 25 (원본 유지)
console.log(personCopy.age); // 26
// 속성 덮어쓰기
let defaults = { theme: "dark", lang: "ko" };
let userSettings = { ...defaults, lang: "en" };
console.log(userSettings); // { theme: 'dark', lang: 'en' }
Rest 연산자
// 함수 매개변수
function sum(...numbers) {
return numbers.reduce((acc, x) => acc + x, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
// 배열 구조 분해
let [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// 객체 구조 분해
let { name, ...otherInfo } = { name: "홍길동", age: 25, city: "서울" };
console.log(name); // 홍길동
console.log(otherInfo); // { age: 25, city: '서울' }
5. 배열 고급 메서드
forEach vs map
let numbers = [1, 2, 3];
// forEach: 반환값 없음
numbers.forEach(x => console.log(x * 2));
// 2 4 6
// map: 새 배열 반환
let doubled = numbers.map(x => x * 2);
console.log(doubled); // [2, 4, 6]
reduce 심화
let numbers = [1, 2, 3, 4, 5];
// 합계
let sum = numbers.reduce((acc, x) => acc + x, 0);
console.log(sum); // 15
// 최대값
let max = numbers.reduce((acc, x) => Math.max(acc, x));
console.log(max); // 5
// 빈도수 세기
let words = ["apple", "banana", "apple", "cherry", "banana", "apple"];
let frequency = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});
console.log(frequency);
// { apple: 3, banana: 2, cherry: 1 }
// 배열을 객체로
let users = [
{ id: 1, name: "홍길동" },
{ id: 2, name: "김철수" }
];
let userMap = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
console.log(userMap);
// { 1: { id: 1, name: '홍길동' }, 2: { id: 2, name: '김철수' } }
// 그룹화
let students = [
{ name: "홍길동", grade: "A" },
{ name: "김철수", grade: "B" },
{ name: "이영희", grade: "A" }
];
let grouped = students.reduce((acc, student) => {
if (!acc[student.grade]) {
acc[student.grade] = [];
}
acc[student.grade].push(student.name);
return acc;
}, {});
console.log(grouped);
// { A: ['홍길동', '이영희'], B: ['김철수'] }
메서드 체이닝
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 짝수만 필터 → 제곱 → 합계
let result = numbers
.filter(x => x % 2 === 0) // [2, 4, 6, 8, 10]
.map(x => x * x) // [4, 16, 36, 64, 100]
.reduce((acc, x) => acc + x, 0); // 220
console.log(result); // 220
// 실전 예제: 사용자 데이터 처리
let users = [
{ name: "홍길동", age: 25, active: true },
{ name: "김철수", age: 17, active: false },
{ name: "이영희", age: 30, active: true },
{ name: "박민수", age: 20, active: true }
];
let activeAdultNames = users
.filter(user => user.active && user.age >= 18)
.map(user => user.name)
.sort();
console.log(activeAdultNames);
// ['박민수', '이영희', '홍길동']
6. 실전 예제
예제 1: 배열 유틸리티
// 중복 제거
function unique(arr) {
return [...new Set(arr)];
}
console.log(unique([1, 2, 2, 3, 3, 3])); // [1, 2, 3]
// 배열 청크
function chunk(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
console.log(chunk([1, 2, 3, 4, 5, 6, 7], 3));
// [[1, 2, 3], [4, 5, 6], [7]]
// 배열 차집합
function difference(arr1, arr2) {
return arr1.filter(x => !arr2.includes(x));
}
console.log(difference([1, 2, 3, 4], [2, 4])); // [1, 3]
// 배열 교집합
function intersection(arr1, arr2) {
return arr1.filter(x => arr2.includes(x));
}
console.log(intersection([1, 2, 3, 4], [2, 3, 5])); // [2, 3]
예제 2: 객체 유틸리티
// 객체 깊은 복사
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (let key in obj) {
cloned[key] = deepClone(obj[key]);
}
return cloned;
}
let original = { a: 1, b: { c: 2 } };
let cloned = deepClone(original);
cloned.b.c = 100;
console.log(original.b.c); // 2 (원본 유지)
console.log(cloned.b.c); // 100
// 객체 병합 (깊은 병합)
function deepMerge(target, source) {
for (let key in source) {
if (source[key] instanceof Object && key in target) {
Object.assign(source[key], deepMerge(target[key], source[key]));
}
}
return Object.assign(target || {}, source);
}
// 객체 필터링
function filterObject(obj, predicate) {
return Object.keys(obj)
.filter(key => predicate(obj[key], key))
.reduce((acc, key) => {
acc[key] = obj[key];
return acc;
}, {});
}
let data = { a: 1, b: 2, c: 3, d: 4 };
let filtered = filterObject(data, value => value > 2);
console.log(filtered); // { c: 3, d: 4 }
예제 3: 데이터 변환
// CSV → 객체 배열
function parseCSV(csv) {
const lines = csv.trim().split("\n");
const headers = lines[0].split(",");
return lines.slice(1).map(line => {
const values = line.split(",");
return headers.reduce((obj, header, i) => {
obj[header] = values[i];
return obj;
}, {});
});
}
const csv = `name,age,city
홍길동,25,서울
김철수,30,부산
이영희,28,서울`;
console.log(parseCSV(csv));
// [
// { name: '홍길동', age: '25', city: '서울' },
// { name: '김철수', age: '30', city: '부산' },
// { name: '이영희', age: '28', city: '서울' }
// ]
// 객체 배열 → CSV
function toCSV(arr) {
if (arr.length === 0) return "";
const headers = Object.keys(arr[0]);
const headerRow = headers.join(",");
const dataRows = arr.map(obj => {
return headers.map(header => obj[header]).join(",");
});
return [headerRow, ...dataRows].join("\n");
}
let users = [
{ name: "홍길동", age: 25 },
{ name: "김철수", age: 30 }
];
console.log(toCSV(users));
// name,age
// 홍길동,25
// 김철수,30
7. 자주 하는 실수와 해결법
실수 1: 배열 복사
// ❌ 참조 복사
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2[0] = 100;
console.log(arr1); // [100, 2, 3] (원본도 변경!)
// ✅ 얕은 복사
let arr1 = [1, 2, 3];
let arr2 = [...arr1]; // 또는 arr1.slice()
arr2[0] = 100;
console.log(arr1); // [1, 2, 3] (원본 유지)
// ⚠️ 중첩 배열은 얕은 복사
let nested = [[1, 2], [3, 4]];
let copy = [...nested];
copy[0][0] = 100;
console.log(nested); // [[100, 2], [3, 4]] (원본도 변경!)
// ✅ 깊은 복사
let deepCopy = JSON.parse(JSON.stringify(nested));
deepCopy[0][0] = 999;
console.log(nested); // [[100, 2], [3, 4]] (원본 유지)
console.log(deepCopy); // [[999, 2], [3, 4]]
실수 2: sort의 함정
// ❌ 숫자 정렬 (문자열로 비교됨!)
let numbers = [1, 10, 2, 20, 3];
numbers.sort();
console.log(numbers); // [1, 10, 2, 20, 3] (잘못됨!)
// ✅ 비교 함수 사용
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 2, 3, 10, 20]
실수 3: 객체 비교
// ❌ 객체는 참조 비교
let obj1 = { a: 1 };
let obj2 = { a: 1 };
console.log(obj1 === obj2); // false
// ✅ 깊은 비교 (lodash 사용 또는 직접 구현)
function deepEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' ||
obj1 === null || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
console.log(deepEqual({ a: 1 }, { a: 1 })); // true
8. 연습 문제
문제 1: 배열 평탄화
중첩된 배열을 1차원 배열로 변환하세요.
function flatten(arr) {
return arr.reduce((acc, item) => {
return acc.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
// 테스트
console.log(flatten([1, [2, 3], [4, [5, 6]]]));
// [1, 2, 3, 4, 5, 6]
문제 2: 객체 배열 그룹화
객체 배열을 특정 키로 그룹화하세요.
function groupBy(arr, key) {
return arr.reduce((acc, obj) => {
const groupKey = obj[key];
if (!acc[groupKey]) {
acc[groupKey] = [];
}
acc[groupKey].push(obj);
return acc;
}, {});
}
// 테스트
let users = [
{ name: "홍길동", age: 25, city: "서울" },
{ name: "김철수", age: 30, city: "부산" },
{ name: "이영희", age: 28, city: "서울" }
];
console.log(groupBy(users, "city"));
// {
// 서울: [{ name: '홍길동', ... }, { name: '이영희', ... }],
// 부산: [{ name: '김철수', ... }]
// }
문제 3: 배열 회전
배열을 오른쪽으로 k번 회전하세요.
function rotateArray(arr, k) {
k = k % arr.length;
return [...arr.slice(-k), ...arr.slice(0, -k)];
}
// 테스트
console.log(rotateArray([1, 2, 3, 4, 5], 2));
// [4, 5, 1, 2, 3]
정리
핵심 요약
-
배열 메서드:
- 추가/삭제:
push,pop,shift,unshift,splice - 검색:
indexOf,includes,find,findIndex - 변환:
map,filter,reduce - 정렬:
sort,reverse
- 추가/삭제:
-
객체:
- 생성: 리터럴
{} - 접근:
.또는[] - 메서드:
Object.keys(),Object.values(),Object.entries()
- 생성: 리터럴
-
구조 분해:
- 배열:
let [a, b] = arr - 객체:
let { name, age } = obj
- 배열:
-
Spread/Rest:
- Spread: 펼치기
...arr - Rest: 나머지 모으기
...rest
- Spread: 펼치기
-
고차 함수:
map: 변환filter: 필터링reduce: 축약
베스트 프랙티스
- ✅
map/filter/reduce활용 - ✅ 불변성 유지 (원본 변경 최소화)
- ✅ 메서드 체이닝으로 가독성 향상
- ✅ Spread 연산자로 복사
- ✅ 구조 분해로 코드 간결화
다음 단계
- JavaScript 비동기 프로그래밍
- JavaScript 클래스
- JavaScript 모듈
관련 글
- JavaScript 시작하기 | 웹 개발의 필수 언어 완벽 입문