Chapter 9

배열과 빌트인

  • 9.1 클래스는 프로토타입의 문법적 설탕인가?
  • 9.2 클래스 정의와 인스턴스 생성
  • 9.3 메서드와 프로퍼티
  • 9.4 상속에 의한 클래스 확장
  • 9.5 함수의 구분과 화살표 함수
  • 9.6 Rest 파라미터와 매개변수 기본값
  • 9.7 배열이란?
  • 9.8 배열 메서드와 고차 함수
  • 9.9 Number와 Math
  • 9.10 Date
  • 9.11 정규 표현식
  • 9.12 String
  • 9.13 Symbol

ES6 클래스부터 화살표 함수, 배열 고차 함수, 그리고 Number/Math/Date/RegExp/String/Symbol까지 -- JS의 핵심 빌트인 기능들을 한 번에 훑어볼게.

ES6에서 도입된 클래스는 단순한 문법적 설탕이 아니야. 생성자 함수보다 엄격하고 추가 기능을 제공하거든. new 없이 호출하면 에러가 나고, extends/super 키워드를 제공하고, 호이스팅이 발생하지만 TDZ에 빠지고, 자동 strict mode가 적용되고, 메서드가 열거 불가([[Enumerable]] = false)야.

class Person {
  constructor(name) { this.name = name; }        // 생성자
  sayHi() { console.log(`Hi! ${this.name}`); }   // 프로토타입 메서드
  static create(name) { return new Person(name); } // 정적 메서드
}

constructor는 인스턴스를 초기화하는 특수 메서드인데, 클래스당 1개만 가능해. 프로토타입 메서드는 인스턴스가 프로토타입 체인을 통해 호출하고, 정적 메서드는 클래스 자체로 호출해. 프로퍼티 쪽도 다양해. 클래스 필드constructor 밖에서 직접 선언할 수 있고, private 필드# 접두사로 선언하면 클래스 외부에서 접근 불가야.

class Person {
  #age;  // private 필드
  constructor(name, age) {
    this.name = name;
    this.#age = age;
  }
}

extends 키워드로 기존 클래스를 확장할 수 있어. super()는 부모 클래스의 constructor를 호출하는 건데, 서브클래스의 constructor에서 반드시 먼저 호출해야 해.

class Animal { constructor(name) { this.name = name; } }
class Dog extends Animal {
  constructor(name, sound) {
    super(name);       // 부모 constructor 호출
    this.sound = sound;
  }
}

클래스와 함께 ES6에서 함수의 종류도 명확히 구분됐어. 일반 함수는 constructor이면서 prototype을 가지고, 메서드(축약)는 non-constructor이면서 super를 쓸 수 있고, 화살표 함수는 non-constructor이면서 자체 this를 갖지 않아 상위 스코프의 this를 그대로 참조해(lexical this).

class Prefixer {
  constructor(prefix) { this.prefix = prefix; }
  add(arr) {
    return arr.map(item => this.prefix + item); // 화살표 함수라 this가 Prefixer 인스턴스
  }
}

화살표 함수는 arguments 객체도 없어서 Rest 파라미터(...rest)로 가변 인수를 진짜 배열로 받아야 하고, 매개변수 기본값(x = 0)도 ES6에서 함께 도입됐어.

이제 JS에서 가장 많이 쓰는 자료구조인 배열로 넘어가자. JS 배열은 해시 테이블로 구현된 특수한 객체야. 인덱스를 키로, 요소를 값으로 가지는 객체라서 타입이 섞여도 되고 길이가 동적이지. 배열 메서드는 **원본 변경(mutator)**과 **새 배열 반환(accessor)**으로 나뉘어. 원본 변경하는 건 push/pop, splice, sort, reverse이고, 새 배열 반환하는 건 concat, slice, flat이야.

실무에서 가장 많이 쓰는 배열 고차 함수들을 정리하면 이래. **map**은 각 요소를 변환해서 새 배열을 반환하고, **filter**는 조건을 만족하는 요소만 모아 새 배열을 반환해. **reduce**는 하나의 결과값으로 누적하는 가장 강력한 고차 함수야.

const sum = [1, 2, 3].reduce((acc, cur) => acc + cur, 0); // 6

**some**은 하나라도 조건 만족하면 true, **every**는 모두 만족해야 true, **find**는 조건 만족하는 첫 번째 요소 반환, **flatMap**은 map + flat(1단계)이야.

Number 빌트인 객체에서는 Number.isNaNNumber.isFinite가 전역 함수보다 암묵적 타입 변환 없이 엄격하게 검사하니 이쪽을 쓰자. toFixed(n)은 소수점 이하 n자리까지 반올림한 문자열을 반환하고, Number.EPSILON은 부동소수점 오차 비교에 쓰여.

Math는 생성자 함수가 아니라 정적 프로퍼티/메서드만 제공해. Math.random(), Math.floor(), Math.max()/Math.min() 정도가 실무에서 자주 쓰이고, Math.pow 대신 ES7의 ** 연산자가 더 깔끔해.

Math.max(...[1, 2, 3]); // 3
Math.floor(Math.random() * 10) + 1; // 1~10 랜덤 정수

Date 빌트인 객체는 날짜와 시간을 다루는데, 월이 0부터 시작하는 함정이 있어. Date.now()로 타임스탬프를 빠르게 구할 수 있고, 실무에서는 dayjsdate-fns 같은 라이브러리를 함께 쓰는 경우가 많지.

정규 표현식은 문자열 패턴 매칭을 위한 도구야. test로 검증하고 match로 추출하는 패턴을 기억하자. 플래그는 i(대소문자 무시), g(전역 검색), m(멀티라인)이 핵심이고, exec는 g 플래그여도 첫 번째만 반환하지만 match는 전부 반환하는 차이가 있어.

/^[A-Za-z0-9]{4,10}$/.test('abc123'); // true — 4~10자 영문/숫자

String 메서드는 원시값이 변경 불가능(immutable)하기 때문에 모든 메서드가 새 문자열을 반환해. includes, startsWith, trim, split, replace 정도가 실무에서 가장 많이 쓰이고, indexOf 대신 includes가 가독성이 좋지.

마지막으로 ES6에서 도입된 7번째 원시 타입 Symbol이야. 다른 값과 절대 중복되지 않는 유일무이한 값인데, 주로 프로퍼티 키 충돌을 방지하기 위해 사용해. Symbol.for(key)는 전역 심벌 레지스트리에서 공유 가능한 심벌을 만들고, Well-known SymbolSymbol.iterator는 이터러블 프로토콜을 구현할 때 사용하는데, 다음 장의 이터러블 기반이 되지.

const s1 = Symbol('description');
const s2 = Symbol('description');
s1 === s2; // false — 설명이 같아도 다른 값

정리

9장 읽고 기억할 거 세 가지:

  1. 배열 고차 함수(map, filter, reduce)는 함수형 프로그래밍의 기본이야. for문 대신 이들을 적극 활용하면 코드가 선언적이고 읽기 쉬워져. mutator 메서드와 accessor 메서드를 구분하는 것도 중요해.
  2. 화살표 함수는 자체 this가 없어서 콜백에 딱이지만, 메서드로 쓰면 this가 엉뚱한 곳을 가리키니 주의해야 해. 콜백에는 화살표 함수, 객체 메서드에는 축약 표현이 모범 답안이야.
  3. 클래스의 # private 필드와 extends/super로 상속과 정보 은닉이 깔끔해졌어. 프로토타입 기반 상속을 읽기 쉬운 문법으로 감싼 것이지만 더 엄격하고 기능도 많지.