개발

[모던 자바스크립트 DeepDive] Js 심화 스터디 week 7

sjindev 2025. 8. 27. 11:40

 

심벌 (Symbol)

1. 심벌값의 생성

  • 심벌은 ES6에서 도입된 원시 타입 중 하나임
  • Symbol() 함수 호출로 생성함
  • 생성된 심벌값은 유일무이함
  • new 연산자 사용 불가
  • 심벌값은 문자열로 자동 변환되지 않음
const sym1 = Symbol('desc');
const sym2 = Symbol('desc');
console.log(sym1 === sym2); // false

2. 심벌과 상수

  • 중복되지 않는 상수 값 생성 시 유용함
  • 주로 enum 대체 용도로 사용됨
const DIRECTION = {
  UP: Symbol('up'),
  DOWN: Symbol('down')
};

3. 심벌과 프로퍼티 키

  • 객체 프로퍼티 키로 심벌 사용 가능함
  • 문자열 키와 달리 충돌 위험 없음
const key = Symbol('key');
const obj = {
  [key]: 'value'
};
console.log(obj[key]); // value

4. 심벌과 프로퍼티 은닉

  • 심벌 키 프로퍼티는 기본적인 순회에서 노출되지 않음
  • for...in, Object.keys(), JSON.stringify() 등에서 제외됨
  • 은닉 프로퍼티처럼 사용 가능함
const secret = Symbol('secret');
const user = {
  name: 'Alice',
  [secret]: 'hidden'
};
console.log(Object.keys(user)); // ['name']

5. 심벌과 표준 빌트인 객체 확장

  • 빌트인 객체의 기존 메서드 충돌 없이 새로운 메서드 추가 가능함
const custom = Symbol('custom');
Array.prototype[custom] = function() {
  return 'custom method';
};
const arr = [];
console.log(arr[custom]()); // custom method

6. Well-known Symbol

  • 자바스크립트 엔진이 미리 정의한 심벌 값
  • 객체 동작을 커스터마이징하는 데 사용함

심벌 설명

Symbol.iterator for...of 루프 동작 정의
Symbol.asyncIterator 비동기 반복자 정의
Symbol.toStringTag 객체의 기본 toString 태그 변경
Symbol.hasInstance instanceof 연산자 동작 정의
Symbol.isConcatSpreadable 배열 concat 시 전개 여부 결정
Symbol.species 파생 객체 생성자 결정
Symbol.match 문자열 매칭 동작 정의
Symbol.replace 문자열 치환 동작 정의
Symbol.search 문자열 검색 동작 정의
Symbol.split 문자열 분할 동작 정의
class Custom {
  get [Symbol.toStringTag]() {
    return 'CustomObject';
  }
}
const c = new Custom();
console.log(Object.prototype.toString.call(c)); // [object CustomObject]

 

이터러블(Iterable)

1. 이터레이션 프로토콜

  • 이터러블 프로토콜과 이터레이터 프로토콜로 구성됨
  • 이터러블 프로토콜: 객체가 Symbol.iterator 메서드를 가져야 함
  • 이터레이터 프로토콜: next() 메서드를 호출할 때 { value, done } 형태 객체 반환해야 함
  • done이 true이면 반복 종료 의미함
  • 자바스크립트의 순회 가능한 객체는 이 두 프로토콜을 모두 준수함
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }

2. 빌트인 이터러블

  • Array, String, Map, Set, TypedArray, arguments, DOM 컬렉션(NodeList 등) 등이 빌트인 이터러블 객체임
  • 이들은 모두 for...of문, 스프레드 문법, 배열 디스트럭처링 등에 사용 가능함
const str = 'hello';
for (const char of str) {
  console.log(char);
}

3. for ...of 문

  • 이터러블 객체 순회 전용 반복문임
  • 내부적으로 이터레이터 프로토콜 사용함
  • for...in은 열거 가능한 속성 순회, for...of는 값 자체 순회
const arr = [10, 20, 30];
for (const val of arr) {
  console.log(val);
}

4. 이터러블과 유사 배열 객체

  • 유사 배열 객체(Array-like Object)는 length 프로퍼티를 가진 객체임
  • 예: arguments, NodeList 등
  • 배열 메서드 사용 불가, 이터러블이 아닐 수도 있음
  • 스프레드 문법이나 Array.from()으로 배열 변환 가능함
function test() {
  console.log(arguments); // 유사 배열 객체
  const arr = Array.from(arguments);
  console.log(arr.map(x => x * 2));
}
test(1, 2, 3);

5. 이터레이션 프로토콜의 필요성

  • 다양한 데이터 소스를 동일한 방법으로 순회 가능하게 함
  • 일관된 순회 인터페이스 제공으로 코드 재사용성 향상
  • ES6의 스프레드 문법, for...of, 디스트럭처링 할당, Promise.all 등에서 활용됨
const set = new Set([1, 2, 3]);
console.log([...set]); // [1, 2, 3]

6. 사용자 정의 이터러블

  • 직접 Symbol.iterator 메서드 구현하여 이터러블 객체 생성 가능함
const customIterable = {
  values: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const values = this.values;
    return {
      next() {
        if (index < values.length) {
          return { value: values[index++], done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (const val of customIterable) {
  console.log(val);
}
  • 위 예시에서 customIterable 객체는 직접 이터레이터를 구현했기 때문에 for...of문 사용 가능함

스프레드 문법(Spread Syntax)

1. 함수 호출문의 인수 목록에서 사용하는 경우

  • 스프레드 문법은 ... 기호 사용함
  • 배열이나 이터러블 요소를 개별 인수로 확장하는 역할 함
  • Function.prototype.apply()를 대체하는 간결한 문법 제공함
  • 함수 호출 시 가독성 향상 및 코드 간결화 가능함
function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
  • Math.max() 같이 여러 인수를 받는 함수에도 사용 가능함
const values = [10, 20, 30];
console.log(Math.max(...values)); // 30

2. 배열 리터럴 내부에서 사용하는 경우

  • 배열 결합, 복사 시 활용 가능함
  • concat() 메서드 없이도 간결하게 배열 병합 가능함
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4]
  • 배열 복사 시 얕은 복사 수행함
const original = [1, 2, 3];
const copy = [...original];
console.log(copy); // [1, 2, 3]
console.log(original === copy); // false
  • 문자열도 배열처럼 전개 가능함
const str = 'hello';
console.log([...str]); // ['h', 'e', 'l', 'l', 'o']

3. 객체 리터럴 내부에서 사용하는 경우

  • ES2018에서 도입된 기능임
  • 기존 객체를 전개하여 새로운 객체 생성 가능함
  • 객체 병합 시 Object.assign()을 대체함
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 3, c: 4 }
  • 얕은 복사 수행, 중첩 객체는 참조 복사됨
const nested = { x: { y: 1 } };
const copyNested = { ...nested };
copyNested.x.y = 2;
console.log(nested.x.y); // 2 (얕은 복사로 인한 영향)
  • 새로운 프로퍼티 추가하면서 복사 가능함
const user = { name: 'Alice', age: 20 };
const updated = { ...user, age: 21, city: 'Seoul' };
console.log(updated); // { name: 'Alice', age: 21, city: 'Seoul' }

 

디스트럭처링 할당(Destructuring Assignment)

1. 배열 디스트럭처링 할당

  • 배열의 요소를 개별 변수로 쉽게 할당하는 문법임
  • 인덱스 순서대로 변수에 값이 할당됨
  • 코드 간결화와 가독성 향상에 유용함
const arr = [1, 2, 3];
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3

기본값 할당

  • 배열 요소가 undefined인 경우 기본값 할당 가능함
const [x, y, z = 5] = [10, 20];
console.log(x, y, z); // 10 20 5

필요 없는 요소 무시

  • 쉼표로 요소를 건너뛸 수 있음
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3

나머지 요소 할당(Rest)

  • 나머지 요소를 배열로 모아 할당 가능함
const [head, ...rest] = [1, 2, 3, 4];
console.log(head); // 1
console.log(rest); // [2, 3, 4]

2. 객체 디스트럭처링 할당

  • 객체 프로퍼티를 변수로 쉽게 추출하는 문법임
  • 프로퍼티 이름과 변수 이름이 동일해야 자동 매칭됨
const user = { name: 'Alice', age: 25 };
const { name, age } = user;
console.log(name, age); // Alice 25

프로퍼티 이름 변경

  • 다른 변수명으로 할당 가능함
const { name: userName, age: userAge } = user;
console.log(userName, userAge); // Alice 25

기본값 할당

  • 프로퍼티가 undefined이면 기본값 사용함
const { city = 'Seoul' } = user;
console.log(city); // Seoul

중첩 객체 디스트럭처링

  • 중첩된 객체 프로퍼티 추출 가능함
const person = { info: { firstName: 'Bob', lastName: 'Smith' } };
const { info: { firstName, lastName } } = person;
console.log(firstName, lastName); // Bob Smith

나머지 프로퍼티 할당(Rest)

  • 나머지 프로퍼티를 객체로 모아 할당 가능함
const { name: n, ...others } = { name: 'Alice', age: 25, city: 'Seoul' };
console.log(n); // Alice
console.log(others); // { age: 25, city: 'Seoul' }

 

Set과 Map

1. Set

  • Set 객체는 중복되지 않는 유일한 값들의 집합임
  • 원시값과 객체 참조 모두 저장 가능함
  • 삽입 순서를 기억함
const set = new Set([1, 2, 3, 3]);
console.log(set); // Set(3) {1, 2, 3}

주요 메서드

  • set.add(value) : 값 추가
  • set.has(value) : 값 존재 여부 확인
  • set.delete(value) : 특정 값 삭제
  • set.clear() : 모든 요소 제거
  • set.size : 요소 개수 반환
const set = new Set();
set.add(1);
set.add(2);
console.log(set.has(1)); // true
set.delete(1);
console.log(set.size); // 1

Set 순회

  • for...of, forEach 모두 사용 가능함
  • keys(), values(), entries() 메서드 제공함
const set = new Set([1, 2, 3]);
for (const val of set) {
  console.log(val);
}

활용

  • 배열 중복 제거에 유용함
const numbers = [1, 2, 2, 3];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3]

2. Map

  • Map 객체는 키-값 쌍으로 이루어진 컬렉션임
  • 키에는 객체, 함수, 원시값 모두 가능함
  • 삽입 순서를 기억함
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
console.log(map.get('key1')); // value1

주요 메서드

  • map.set(key, value) : 키-값 쌍 추가
  • map.get(key) : 키에 해당하는 값 반환
  • map.has(key) : 키 존재 여부 확인
  • map.delete(key) : 키와 값 삭제
  • map.clear() : 모든 요소 제거
  • map.size : 요소 개수 반환
const map = new Map();
map.set('a', 1);
map.set('b', 2);
console.log(map.has('a')); // true
map.delete('a');
console.log(map.size); // 1

Map 순회

  • for...of문, forEach 메서드 사용 가능함
  • keys(), values(), entries() 메서드 제공함
const map = new Map([
  ['name', 'Alice'],
  ['age', 25]
]);
for (const [key, value] of map) {
  console.log(key, value);
}

객체와의 차이점

  • 객체는 문자열, 심벌만 키로 사용 가능함
  • Map은 키 타입 제한 없음
  • 요소 개수는 map.size 사용, 객체는 Object.keys(obj).length 사용
const obj = { a: 1 };
obj[1] = 'number key';
console.log(obj); // { '1': 'number key', a: 1 }

const map = new Map();
map.set(1, 'number key');
map.set({x: 10}, 'object key');
console.log(map);