본문으둜 κ±΄λ„ˆλ›°κΈ°

🐀 Chapter 7. μ½”λ“œλ₯Ό μž‘μ„±ν•˜κ³  μ‹€ν–‰ν•˜κΈ°

πŸ₯• μ•„μ΄ν…œ 53. νƒ€μž…μŠ€ν¬λ¦½νŠΈ κΈ°λŠ₯λ³΄λ‹€λŠ” ECMAScript κΈ°λŠ₯을 μ‚¬μš©ν•˜κΈ°β€‹

νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ νƒœλ™ν•˜λ˜ 2010λ…„κ²½, μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” 결함이 많고 κ°œμ„ ν•΄μ•Ό ν•  뢀뢄이 λ§Žμ€ μ–Έμ–΄μ˜€μŠ΅λ‹ˆλ‹€. 그리고 클래슀, λ°μ½”λ ˆμ΄ν„°, λͺ¨λ“ˆ μ‹œμŠ€ν…œ 같은 κΈ°λŠ₯이 μ—†μ–΄μ„œ ν”„λ ˆμž„μ›Œν¬λ‚˜ 트랜슀파일러둜 λ³΄μ™„ν•˜λŠ” 것이 일반적인 λͺ¨μŠ΅μ΄μ—ˆμŠ΅λ‹ˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— νƒ€μž…μŠ€ν¬λ¦½νŠΈλ„ 초기 λ²„μ „μ—λŠ” λ…λ¦½μ μœΌλ‘œ κ°œλ°œν•œ 클래슀, μ—΄κ±°ν˜•(enum), λͺ¨λ“ˆ μ‹œμŠ€ν…œμ„ ν¬ν•¨μ‹œν‚¬ μˆ˜λ°–μ— μ—†μ—ˆμŠ΅λ‹ˆλ‹€.

μ‹œκ°„μ΄ 흐λ₯΄λ©° TC39(μžλ°”μŠ€ν¬λ¦½νŠΈλ₯Ό κ΄€μž₯ν•˜λŠ” ν‘œμ€€ 기ꡬ)λŠ” λΆ€μ‘±ν–ˆλ˜ 점듀을 λŒ€λΆ€λΆ„ λ‚΄μž₯ κΈ°λŠ₯으둜 μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ μžλ°”μŠ€ν¬λ¦½νŠΈμ— μƒˆλ‘œ μΆ”κ°€λœ κΈ°λŠ₯은 νƒ€μž…μŠ€ν¬λ¦½νŠΈ 초기 λ²„μ „μ—μ„œ λ…λ¦½μ μœΌλ‘œ κ°œλ°œν–ˆλ˜ κΈ°λŠ₯κ³Ό ν˜Έν™˜μ„± 문제λ₯Ό λ°œμƒμ‹œμΌ°μŠ΅λ‹ˆλ‹€. 그렇기에 νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ§„μ˜μ—μ„œλŠ” λ‹€μŒ μ „λž΅ 쀑 ν•˜λ‚˜λ₯Ό 선택해야 ν–ˆμŠ΅λ‹ˆλ‹€. ν•œ 가지 μ „λž΅μ€ νƒ€μž…μŠ€ν¬λ¦½νŠΈ 초기 λ²„μ „μ˜ ν˜•νƒœλ₯Ό μœ μ§€ν•˜κΈ° μœ„ν•΄ μžλ°”μŠ€ν¬λ¦½νŠΈ μ‹ κ·œ κΈ°λŠ₯을 λ³€ν˜•ν•΄μ„œ λΌμ›Œ λ§žμΆ”λŠ” κ²ƒμž…λ‹ˆλ‹€. 또 λ‹€λ₯Έ μ „λž΅μ€ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ μ‹ κ·œ κΈ°λŠ₯을 κ·ΈλŒ€λ‘œ μ±„νƒν•˜κ³  νƒ€μž…μŠ€ν¬λ¦½νŠΈ 초기 버전과 ν˜Έν™˜μ„±μ„ ν¬κΈ°ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

νƒ€μž…μŠ€ν¬λ¦½νŠΈ νŒ€μ€ λŒ€λΆ€λΆ„ 두 번째 μ „λž΅μ„ μ„ νƒν–ˆμŠ΅λ‹ˆλ‹€. κ²°κ΅­ TC39λŠ” λŸ°νƒ€μž„ κΈ°λŠ₯을 λ°œμ „μ‹œν‚€κ³ , νƒ€μž…μŠ€ν¬λ¦½νŠΈ νŒ€μ€ νƒ€μž… κΈ°λŠ₯만 λ°œμ „μ‹œν‚¨λ‹€λŠ” λͺ…ν™•ν•œ 원칙을 μ„Έμš°κ³  ν˜„μž¬κΉŒμ§€ μ§€μΌœμ˜€κ³  μžˆμŠ΅λ‹ˆλ‹€.

그런데 이 원칙이 μ„Έμ›Œμ§€κΈ° 전에, 이미 μ‚¬μš©λ˜κ³  있던 λͺ‡ 가지 κΈ°λŠ₯이 μžˆμŠ΅λ‹ˆλ‹€. 이 κΈ°λŠ₯듀은 νƒ€μž… 곡간(νƒ€μž…μŠ€ν¬λ¦½νŠΈ)κ³Ό κ°’ 곡간(μžλ°”μŠ€ν¬λ¦½νŠΈ)의 경계λ₯Ό ν˜Όλž€μŠ€λŸ½κ²Œ λ§Œλ“€κΈ° λ•Œλ¬Έμ— μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” ν”Όν•΄μ•Ό ν•˜λŠ” κΈ°λŠ₯을 λͺ‡ 가지 μ‚΄νŽ΄λ΄…λ‹ˆλ‹€. 그리고 λΆˆκ°€ν”Όν•˜κ²Œ 이 κΈ°λŠ₯을 μ‚¬μš©ν•˜κ²Œ λ˜λŠ” 경우 μ–΄λ–€ 점에 μœ μ˜ν•΄μ•Ό ν˜Έν™˜μ„± 문제λ₯Ό μΌμœΌν‚€μ§€ μ•ŠλŠ”μ§€ μ•Œμ•„λ΄…λ‹ˆλ‹€.

μ—΄κ±°ν˜•(enum)​

λ§Žμ€ μ–Έμ–΄μ—μ„œ λͺ‡λͺ‡ κ°’μ˜ λͺ¨μŒμ„ λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ μ—΄κ±°ν˜•μ„ μ‚¬μš©ν•©λ‹ˆλ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œλ„ μ—΄κ±°ν˜•μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

enum Flavor {
VANILLA = 0,
CHOCOLATE = 1,
STRAWBERRY = 2,
}

let flavor = Flavor.CHOCOLATE; // νƒ€μž…μ΄ Flavor

Flavor // μžλ™μ™„μ„± μΆ”μ²œ: VANILLA, CHOCOLATE, STRAWBERRY
Flavor[0] // 값이 "VANILLA"

λ‹¨μˆœνžˆ 값을 λ‚˜μ—΄ν•˜λŠ” 것보닀 μ‹€μˆ˜κ°€ 적고 λͺ…ν™•ν•˜κΈ° λ•Œλ¬Έμ— 일반적으둜 μ—΄κ±°ν˜•μ„ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ μ—΄κ±°ν˜•μ€ λͺ‡ 가지 λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ μ—΄κ±°ν˜•μ€ λ‹€μŒ λͺ©λ‘μ²˜λŸΌ 상황에 따라 λ‹€λ₯΄κ²Œ λ™μž‘ν•©λ‹ˆλ‹€.

  • 숫자 μ—΄κ±°ν˜•(μ•ž 예제의 Flavor)에 0, 1, 2 μ™Έμ˜ λ‹€λ₯Έ μˆ«μžκ°€ ν• λ‹Ήλ˜λ©΄ 맀우 μœ„ν—˜ν•©λ‹ˆλ‹€. (이 방법은 μ›λž˜ λΉ„νŠΈ ν”Œλž˜κ·Έ ꡬ쑰λ₯Ό ν‘œν˜„ν•˜κΈ° μœ„ν•΄ μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.)
  • μƒμˆ˜ μ—΄κ±°ν˜•μ€ λ³΄ν†΅μ˜ μ—΄κ±°ν˜•κ³Ό 달리 λŸ°νƒ€μž„μ— μ™„μ „νžˆ μ œκ±°λ©λ‹ˆλ‹€. μ•žμ˜ 예제λ₯Ό const enum Flavor둜 λ°”κΎΈλ©΄, μ»΄νŒŒμΌλŸ¬λŠ” Flavor.CHOCOLATE을 1으둜 λ°”κΏ” λ²„λ¦½λ‹ˆλ‹€. 이런 κ²°κ³ΌλŠ” κΈ°λŒ€ν•˜μ§€ μ•Šμ€ 것이며, λ¬Έμžμ—΄ μ—΄κ±°ν˜•κ³Ό 숫자 μ—΄κ±°ν˜•κ³Ό μ „ν˜€ λ‹€λ₯Έ λ™μž‘μž…λ‹ˆλ‹€.
  • preserveConstEnums ν”Œλž˜κ·Έλ₯Ό μ„€μ •ν•œ μƒνƒœμ˜ μƒμˆ˜ μ—΄κ±°ν˜•μ€ λ³΄ν†΅μ˜ μ—΄κ±°ν˜•μ²˜λŸΌ λŸ°νƒ€μž„ μ½”λ“œμ— μƒμˆ˜ μ—΄κ±°ν˜• 정보λ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.
  • λ¬Έμžμ—΄ μ—΄κ±°ν˜•μ€ λŸ°νƒ€μž„μ˜ νƒ€μž… μ•ˆμ „μ„±κ³Ό 투λͺ…성을 μ œκ³΅ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ λ‹€λ₯Έ νƒ€μž…κ³Ό 달리 ꡬ쑰적 타이핑이 μ•„λ‹Œ λͺ…λͺ©μ  타이핑을 μ‚¬μš©ν•©λ‹ˆλ‹€.

νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ 일반적인 νƒ€μž…λ“€μ΄ ν• λ‹Ή κ°€λŠ₯성을 μ²΄ν¬ν•˜κΈ° μœ„ν•΄μ„œ ꡬ쑰적 타이핑을 μ‚¬μš©ν•˜λŠ” 반면, λ¬Έμžμ—΄ μ—΄κ±°ν˜•μ€ λͺ…λͺ©μ  타이핑을 μ‚¬μš©ν•©λ‹ˆλ‹€.

ꡬ쑰적 타이핑은 ꡬ쑰가 κ°™μœΌλ©΄ 할당이 ν—ˆμš©λ˜λŠ” 반면, λͺ…λͺ©μ  타이핑은 νƒ€μž…μ˜ 이름이 κ°™μ•„μ•Ό 할당이 ν—ˆμš©λ©λ‹ˆλ‹€.

enum Flavor {
VANILLA = 'vanilla',
CHOCOLATE = 'chocolate',
STRAWBERRY = 'strawberry',
}

let flavor = Flavor.CHOCOLATE; // νƒ€μž…μ΄ Flavor
flavor = 'strawberry';
// ~~~~~ '"strawberry"' ν˜•μ‹μ€ 'Flavor' ν˜•μ‹μ— ν• λ‹Ήν•  수 μ—†μŠ΅λ‹ˆλ‹€.ts(2322)

λͺ…λͺ©μ  타이핑은 라이브러리λ₯Ό κ³΅κ°œν•  λ•Œ ν•„μš”ν•©λ‹ˆλ‹€. Flavorλ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›λŠ” ν•¨μˆ˜λ₯Ό κ°€μ •ν•΄ λ΄…μ‹œλ‹€.

function scoop(flavor: Flavor) { /* ... */ }

FlavorλŠ” λŸ°νƒ€μž„ μ‹œμ μ—λŠ” λ¬Έμžμ—΄μ΄κΈ° λ•Œλ¬Έμ—, μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ λ‹€μŒμ²˜λŸΌ ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€.

scoop('vanilla'); // μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” 정상

κ·ΈλŸ¬λ‚˜ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” μ—΄κ±°ν˜•μ„ μž„ν¬νŠΈν•˜κ³  λ¬Έμžμ—΄ λŒ€μ‹  μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

import { Flavor } from 'ice-cream';
scoop(Flavor.VANILLA);

이처럼 μžλ°”μŠ€ν¬λ¦½νŠΈμ™€ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ λ™μž‘μ΄ λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— λ¬Έμžμ—΄ μ—΄κ±°ν˜•μ€ μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ—΄κ±°ν˜• λŒ€μ‹  λ¦¬ν„°λŸ΄ νƒ€μž…μ˜ μœ λ‹ˆμ˜¨μ„ μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

type Flavor = 'vanilla' | 'chocolate' | 'strawberry';

let flavor: Flavor = 'chocolate'; // 정상

λ¦¬ν„°λŸ΄ νƒ€μž…μ˜ μœ λ‹ˆμ˜¨μ€ μ—΄κ±°ν˜•λ§ŒνΌ μ•ˆμ „ν•˜λ©° μžλ°”μŠ€ν¬λ¦½νŠΈμ™€ ν˜Έν™˜λ˜λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€. 그리고 νŽΈμ§‘κΈ°μ—μ„œ μ—΄κ±°ν˜•μ²˜λŸΌ μžλ™μ™„μ„± κΈ°λŠ₯을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ§€κ°œλ³€μˆ˜ 속성​

일반적으둜 클래슀λ₯Ό μ΄ˆκΈ°ν™”ν•  λ•Œ 속성을 ν• λ‹Ήν•˜κΈ° μœ„ν•΄ μƒμ„±μžμ˜ λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}

νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” 더 κ°„κ²°ν•œ 문법을 μ œκ³΅ν•©λ‹ˆλ‹€.

class Person {
constructor(public name: string) {}
}

κ·ΈλŸ¬λ‚˜ λ§€κ°œλ³€μˆ˜ 속성과 κ΄€λ ¨λœ λͺ‡ 가지 문제점이 μ‘΄μž¬ν•©λ‹ˆλ‹€.

  • 일반적으둜 νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ»΄νŒŒμΌμ€ νƒ€μž… μ œκ±°κ°€ μ΄λ£¨μ–΄μ§€λ―€λ‘œ μ½”λ“œκ°€ μ€„μ–΄λ“€μ§€λ§Œ, λ§€κ°œλ³€μˆ˜ 속성은 μ½”λ“œκ°€ λŠ˜μ–΄λ‚˜λŠ” λ¬Έλ²•μž…λ‹ˆλ‹€.
  • λ§€κ°œλ³€μˆ˜ 속성이 λŸ°νƒ€μž„μ—λŠ” μ‹€μ œλ‘œ μ‚¬μš©λ˜μ§€λ§Œ, νƒ€μž…μŠ€ν¬λ¦½νŠΈ κ΄€μ μ—μ„œλŠ” μ‚¬μš©λ˜μ§€ μ•ŠλŠ” κ²ƒμ²˜λŸΌ λ³΄μž…λ‹ˆλ‹€.
  • λ§€κ°œλ³€μˆ˜ 속성과 일반 속성을 μ„žμ–΄μ„œ μ‚¬μš©ν•˜λ©΄ 클래슀의 섀계가 ν˜Όλž€μŠ€λŸ¬μ›Œμ§‘λ‹ˆλ‹€.
class Person {
first: string;
last: string;
constructor(public name: string) {
[this.first, this.last] = name.split(' ');
}
}

Person ν΄λž˜μŠ€μ—λŠ” μ„Έ 가지 속성(first, last, name)이 μžˆμ§€λ§Œ, first와 last만 속성에 λ‚˜μ—΄λ˜μ–΄ 있고 name은 λ§€κ°œλ³€μˆ˜ 속성에 μžˆμ–΄μ„œ 일관성이 μ—†μŠ΅λ‹ˆλ‹€. ꡬ쑰적 타이핑 νŠΉμ„± λ•Œλ¬Έμ— λ‹€μŒ 예제처럼 ν• λ‹Ήν•  수 μžˆλ‹€λŠ” 것을 μ£Όμ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.

class Person {
constructor(public name: string) {}
}

const p: Person = { name: 'Jed Bartlet' }; // 정상

λ§€κ°œλ³€μˆ˜ 속성은 νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ λ‹€λ₯Έ νŒ¨ν„΄λ“€κ³Ό 이질적이고, μ΄ˆκΈ‰μžμ—κ²Œ μƒμ†Œν•œ λ¬Έλ²•μ΄λΌλŠ” 것을 κΈ°μ–΅ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ˜ν•œ λ§€κ°œλ³€μˆ˜ 속성과 일반 속성을 같이 μ‚¬μš©ν•˜λ©΄ 섀계가 ν˜Όλž€μŠ€λŸ¬μ›Œμ§€κΈ° λ•Œλ¬Έμ— ν•œ κ°€μ§€λ§Œ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

λ„€μž„μŠ€νŽ˜μ΄μŠ€μ™€ νŠΈλ¦¬ν”Œ μŠ¬λž˜μ‹œ μž„ν¬νŠΈβ€‹

ECMAScript 2015 μ΄μ „μ—λŠ” μžλ°”μŠ€ν¬λ¦½νŠΈμ— 곡식적인 λͺ¨λ“ˆ μ‹œμŠ€ν…œμ΄ μ—†μ—ˆμŠ΅λ‹ˆλ‹€. Node.jsλŠ” require와 module.exportsλ₯Ό μ‚¬μš©ν•˜λŠ” 반면, AMDλŠ” define ν•¨μˆ˜μ™€ μ½œλ°±μ„ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.
νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ—­μ‹œ module ν‚€μ›Œλ“œμ™€ 'νŠΈλ¦¬ν”Œ μŠ¬λž˜μ‹œ' μž„ν¬νŠΈλ₯Ό μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. ECMAScript 2015κ°€ κ³΅μ‹μ μœΌλ‘œ λͺ¨λ“ˆ μ‹œμŠ€ν…œμ„ λ„μž…ν•œ 이후, νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” μΆ©λŒμ„ ν”Όν•˜κΈ° μœ„ν•΄ moduleκ³Ό 같은 κΈ°λŠ₯을 ν•˜λŠ” namespace ν‚€μ›Œλ“œλ₯Ό μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.

namespace foo {
function bar() {}
}

/// <reference path="other.ts" />
foo.bar();

νŠΈλ¦¬ν”Œ μŠ¬λž˜μ‹œ μž„ν¬νŠΈμ™€ module ν‚€μ›Œλ“œλŠ” ν˜Έν™˜μ„±μ„ μœ„ν•΄ 남아 μžˆμ„ 뿐이며, μ΄μ œλŠ” ECMAScript 2015 μŠ€νƒ€μΌμ„ λͺ¨λ“ˆ(import와 export)을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

λ°μ½”λ ˆμ΄ν„°β€‹

λ°μ½”λ ˆμ΄ν„°λŠ” 클래슀, λ©”μ„œλ“œ, 속성에 μ• λ„ˆν…Œμ΄μ…˜μ„ λΆ™μ΄κ±°λ‚˜ κΈ°λŠ₯을 μΆ”κ°€ν•˜λŠ” 데 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, 클래슀의 λ©”μ„œλ“œκ°€ 호좜될 λ•Œλ§ˆλ‹€ 둜그λ₯Ό 남기렀면 logged μ• λ„ˆν…Œμ΄μ…˜μ„ μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@logged
greet() {
return "Hello, " + this.greeting;
}
}

λ°μ½”λ ˆμ΄ν„°λŠ” μ²˜μŒμ— μ•΅κ·€λŸ¬ ν”„λ ˆμž„μ›Œν¬λ₯Ό μ§€μ›ν•˜κΈ° μœ„ν•΄ μΆ”κ°€λ˜μ—ˆμœΌλ©° tsconfig.json에 experimentalDecorators 속성을 μ„€μ •ν•˜κ³  μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. ν˜„μž¬κΉŒμ§€λ„ ν‘œμ€€ν™”κ°€ μ™„λ£Œλ˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ—, μ‚¬μš© 쀑인 λ°μ½”λ ˆμ΄ν„°κ°€ λΉ„ν‘œμ€€μœΌλ‘œ λ°”λ€Œκ±°λ‚˜ ν˜Έν™˜μ„±μ΄ 깨질 κ°€λŠ₯성이 μžˆμŠ΅λ‹ˆλ‹€. μ•΅κ·€λŸ¬λ₯Ό μ‚¬μš©ν•˜κ±°λ‚˜ μ• λ„ˆν…Œμ΄μ…˜μ΄ ν•„μš”ν•œ ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•˜κ³  μžˆλŠ” 게 μ•„λ‹ˆλΌλ©΄, λ°μ½”λ ˆμ΄ν„°κ°€ ν‘œμ€€μ΄ 되기 μ „μ—λŠ” νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ λ°μ½”λ ˆμ΄ν„°λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 게 μ’‹μŠ΅λ‹ˆλ‹€.

πŸ₯• μ•„μ΄ν…œ 54. 객체λ₯Ό μˆœνšŒν•˜λŠ” λ…Έν•˜μš°β€‹

였λ₯˜μ˜ 원인은 λ¬΄μ—‡μΌκΉŒμš”?

const obj = {
one: 'uno',
two: 'dos',
three: 'tres',
};

for (const k in obj) {
const v = obj[k];
// ~~~~~~ obj에 인덱슀 μ‹œκ·Έλ‹ˆμ²˜κ°€ μ—†κΈ° λ•Œλ¬Έμ— μ—˜λ¦¬λ¨ΌνŠΈλŠ” μ•”μ‹œμ μœΌλ‘œ any νƒ€μž…μž…λ‹ˆλ‹€.
}

k의 νƒ€μž…μ΄ string인 반면, obj κ°μ²΄λŠ” one, two, three μ„Έ 개의 ν‚€λ§Œ μ‘΄μž¬ν•©λ‹ˆλ‹€. k와 obj 객체의 ν‚€ νƒ€μž…μ΄ μ„œλ‘œ λ‹€λ₯΄κ²Œ μΆ”λ‘ λ˜μ–΄ 였λ₯˜κ°€ λ°œμƒν•œ κ²ƒμž…λ‹ˆλ‹€.

interface ABC {
a: string;
b: string;
c: number;
}

function foo(abc: ABC) {
for (const k in abc) { // const k: string
const v = abc[k];
// ~~~~~ 'ABC' νƒ€μž…μ— 인덱슀 μ‹œκ·Έλ‹ˆμ²˜κ°€ μ—†κΈ° λ•Œλ¬Έμ— μ—˜λ¦¬λ¨ΌνŠΈλŠ” μ•”μ‹œμ μœΌλ‘œ anyκ°€ λ©λ‹ˆλ‹€.
}
}

첫 번째 μ˜ˆμ œμ™€ λ™μΌν•œ 였λ₯˜μž…λ‹ˆλ‹€. κ·ΈλŸ¬λ―€λ‘œ (let k: keyof ABC) 같은 μ„ μ–ΈμœΌλ‘œ 였λ₯˜λ₯Ό μ œκ±°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

const x = {a: 'a', b: 'b', c: 2, d: new Date()};
foo(x); // 정상

foo ν•¨μˆ˜λŠ” ABC νƒ€μž…μ— ν• λ‹Ή κ°€λŠ₯ν•œ μ–΄λ– ν•œ 값이든 λ§€κ°œλ³€μˆ˜λ‘œ ν—ˆμš©ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.
λ˜ν•œ keyof ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•œ 방법은 또 λ‹€λ₯Έ λ¬Έμ œμ μ„ λ‚΄ν¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

function foo(abc: ABC) {
let k: keyof ABC;
for (k in abc) {
const v = abc[k]; // string | number νƒ€μž…
}
}

kκ°€ "a" | "b" | "c" νƒ€μž…μœΌλ‘œ ν•œμ •λ˜μ–΄ λ¬Έμ œκ°€ 된 κ²ƒμ²˜λŸΌ, v도 string | number νƒ€μž…μœΌλ‘œ ν•œμ •λ˜μ–΄ λ²”μœ„κ°€ λ„ˆλ¬΄ 쒁아 λ¬Έμ œκ°€ λ©λ‹ˆλ‹€.
골치 μ•„ν”ˆ νƒ€μž… λ¬Έμ œμ—†μ΄, 단지 객체의 킀와 값을 μˆœνšŒν•˜κ³  μ‹Άλ‹€λ©΄ Object.entriesλ₯Ό μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

function foo(abc: ABC) {
for (const [k, v] of Object.entries(abc)) {
k // string νƒ€μž…
v // any νƒ€μž…
}
}

객체λ₯Ό λ‹€λ£° λ•ŒλŠ” 항상 "ν”„λ‘œν† νƒ€μž… μ˜€μ—Ό"의 κ°€λŠ₯성을 염두에 두어야 ν•©λ‹ˆλ‹€. μ‹€μ œ μž‘μ—…μ—μ„œλŠ” Object.prototype에 순회 κ°€λŠ₯ν•œ 속성을 μ ˆλŒ€λ‘œ μΆ”κ°€ν•˜λ©΄ μ•ˆ λ©λ‹ˆλ‹€.

πŸ₯• μ•„μ΄ν…œ 55. DOM 계측 ꡬ쑰 μ΄ν•΄ν•˜κΈ°β€‹

νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ DOM μ—˜λ¦¬λ¨ΌνŠΈμ˜ 계측 ꡬ쑰λ₯Ό νŒŒμ•…ν•˜κΈ° μš©μ΄ν•©λ‹ˆλ‹€.
μ•„λž˜ μ½”λ“œλŠ” νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ μˆ˜λ§Žμ€ 였λ₯˜κ°€ ν‘œμ‹œλ©λ‹ˆλ‹€.

function handleDrag(eDown: Event) {
const targetEl = eDown.currentTarget;
targetEl.classList.add('dragging');

const dragStart = [eDown.clientX, eDown.clientY];

const handleUp = (eUp: Event) => {
targetEl.classList.remove('dragging');
targetEl.removeEventListener('mouseup', handleUp);

const dragEnd = [eUp.clientX, eUp.clientY];
console.lg('dx, dy = ', [0, 1].map(i => dragEnd[i] - dragStart[i]));
}

targetEl.addEventListener('mouseup', handleUp);
}

const div = document.getElementById('surface');
div.addEventListener('mousedown', handleDrag);

일반적으둜 νƒ€μž… 단언문은 지양해야 ν•˜μ§€λ§Œ, DOM κ΄€λ ¨ν•΄μ„œλŠ” νƒ€μž…μŠ€ν¬λ¦½νŠΈλ³΄λ‹€ μš°λ¦¬κ°€ 더 μ •ν™•νžˆ μ•Œκ³  μžˆλŠ” κ²½μš°μ΄λ―€λ‘œ 단언문을 μ‚¬μš©ν•΄λ„ μ’‹μŠ΅λ‹ˆλ‹€.

document.getElementById('my-div') as HTMLDivElement;

strictNullChecksκ°€ μ„€μ •λœ μƒνƒœλΌλ©΄, document.getElementByIdκ°€ null인 경우λ₯Ό 체크해야 ν•©λ‹ˆλ‹€. μ‹€μ œ μ½”λ“œμ—μ„œ document.getElementByIdκ°€ null일 κ°€λŠ₯성이 μžˆλ‹€λ©΄ if 뢄기문을 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μ•„λž˜ μ˜ˆμ œλŠ” νƒ€μž…μŠ€ν¬λ¦½νŠΈ 였λ₯˜λ₯Ό ν•΄κ²°ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€.

function addDragHandler(el: HTMLElement) {
el.addEventListener('mosuedown', eDown => {
const dragStart = [eDown.clientX, eDown.clientY];
const handleUp = (eUp: MouseEvent) => {
el.classList.remove('dragging');
el.removeEventListener('mouseup', handleUp);
const dragEnd = [eUp.clientX, eUp.clientY];
console.lg('dx, dy = ', [0, 1].map(i => dragEnd[i] - dragStart[i]));
}
el.addEventListener('mouseup', handleUp);
});
}

const div = document.getElementById('surface');
if (div) {
addDragHandler(div);
}

μžλ°”μŠ€ν¬λ¦½νŠΈλ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” μ‹ κ²½ 쓰지 μ•Šμ•˜κ² μ§€λ§Œ, DOMμ—λŠ” νƒ€μž… 계측 ꡬ쑰가 μžˆμŠ΅λ‹ˆλ‹€. DOM νƒ€μž…μ€ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ μ€‘μš”ν•œ 정보이며, λΈŒλΌμš°μ € κ΄€λ ¨ ν”„λ‘œμ νŠΈμ—μ„œ νƒ€μž…μŠ€ν¬λ¦½νŠΈλ₯Ό μ‚¬μš©ν•  λ•Œ μœ μš©ν•©λ‹ˆλ‹€.

πŸ₯• μ•„μ΄ν…œ 56. 정보λ₯Ό κ°μΆ”λŠ” λͺ©μ μœΌλ‘œ private μ‚¬μš©ν•˜μ§€ μ•ŠκΈ°β€‹

class Diary {
private secret = 'cheated on my English test';
}

const diary = new Diary();
diary.secret
// ~~~~~~ 'secret' 속성은 private이며 'Diary' 클래슀 λ‚΄μ—μ„œλ§Œ μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€.

νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—λŠ” public, protected, private 같은 μ ‘κ·Ό μ œμ–΄μžλŠ” νƒ€μž…μŠ€ν¬λ¦½νŠΈ ν‚€μ›Œλ“œμ΄κΈ° λ•Œλ¬Έμ— 컴파일 ν›„μ—λŠ” μ œκ±°λ©λ‹ˆλ‹€. 이 νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ½”λ“œλ₯Ό μ»΄νŒŒμΌν•˜κ²Œ 되면 λ‹€μŒ 예제의 μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œλ‘œ λ³€ν™˜λ©λ‹ˆλ‹€.(target=ES2017이 μ„€μ •λœ μƒνƒœ)

class Diary {
constructor() {
this.secret = 'cheated on my English test';
}
}

const diary = new Diary();
diary.secret;

private ν‚€μ›Œλ“œλŠ” μ‚¬λΌμ‘Œκ³  secret은 일반적인 μ†μ„±μ΄λ―€λ‘œ μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ μ ‘κ·Ό μ œμ–΄μžλ“€μ€ 단지 컴파일 μ‹œμ μ—λ§Œ 였λ₯˜λ₯Ό ν‘œμ‹œν•΄ 쀄 뿐이며, μ–Έλ”μŠ€μ½”μ–΄ 관둀와 λ§ˆμ°¬κ°€μ§€λ‘œ λŸ°νƒ€μž„μ—λŠ” μ•„λ¬΄λŸ° 효λ ₯이 μ—†μŠ΅λ‹ˆλ‹€. 심지어 단언문을 μ‚¬μš©ν•˜λ©΄ νƒ€μž…μŠ€ν¬λ¦½νŠΈ μƒνƒœμ—μ„œλ„ private 속성에 μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class Diary {
private secret = 'cheated on my English test';
}

const diary = new Diary();
(diary as any).secret // 정상

즉, 정보λ₯Ό 감좔기 μœ„ν•΄ private을 μ‚¬μš©ν•˜λ©΄ μ•ˆ λ©λ‹ˆλ‹€.
μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ 정보λ₯Ό 숨기기 μœ„ν•΄ κ°€μž₯ 효과적인 방법은 ν΄λ‘œμ €λ₯Ό μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. λ‹€μŒ μ½”λ“œμ²˜λŸΌ μƒμ„±μžμ—μ„œ ν΄λ‘œμ €λ₯Ό λ§Œλ“€μ–΄ λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

declare function hash(text: string): number;

class PasswordChecker {
checkPassword: (password: string) => boolean;
constructor(passwordHash: number) {
this.checkPassword = (password: string) => {
return hash(password) === passwordHash;
}
}
}

const checker = new PasswordChecker(hash('s3cret'));
checker.checkPassword('s3cret'); // κ²°κ³ΌλŠ” true

λͺ‡ 가지 μ£Όμ˜μ‚¬ν•­μ΄ μžˆμŠ΅λ‹ˆλ‹€. passwordHashλ₯Ό μƒμ„±μž μ™ΈλΆ€μ—μ„œ μ ‘κ·Όν•  수 μ—†κΈ° λ•Œλ¬Έμ—, passwordHash에 μ ‘κ·Όν•΄μ•Ό ν•˜λŠ” λ©”μ„œλ“œ μ—­μ‹œ μƒμ„±μž 내뢀에 μ •μ˜λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. 그리고 λ©”μ„œλ“œ μ •μ˜κ°€ μƒμ„±μž 내뢀에 μ‘΄μž¬ν•˜λ©΄, μΈμŠ€ν„΄μŠ€λ₯Ό 생성할 λ•Œλ§ˆλ‹€ 각 λ©”μ„œλ“œμ˜ 볡사본이 μƒμ„±λ˜κΈ° λ•Œλ¬Έμ— λ©”λͺ¨λ¦¬λ₯Ό λ‚­λΉ„ν•˜κ²Œ λœλ‹€λŠ” 것을 κΈ°μ–΅ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ˜ν•œ λ™μΌν•œ ν΄λž˜μŠ€λ‘œλΆ€ν„° μƒμ„±λœ μΈμŠ€ν„΄μŠ€λΌκ³  ν•˜λ”λΌλ„ μ„œλ‘œ λΉ„κ³΅κ°œ 데이터에 μ ‘κ·Όν•˜λŠ” 것이 λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— μ² μ €ν•˜κ²Œ λΉ„κ³΅κ°œμ΄λ©΄μ„œ λ™μ‹œμ— λΆˆνŽΈν•¨μ΄ λ”°λ¦…λ‹ˆλ‹€.

또 ν•˜λ‚˜μ˜ μ„ νƒμ§€λ‘œ, ν˜„μž¬ ν‘œμ€€ν™”κ°€ 진행 쀑인 λΉ„κ³΅κ°œ ν•„λ“œ κΈ°λŠ₯을 μ‚¬μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. (책은 진행 쀑이라고 λ˜μ–΄μžˆμ§€λ§Œ ν˜„μž¬λŠ” ν‘œμ€€ν™” μ™„λ£Œ) λΉ„κ³΅κ°œ ν•„λ“œ κΈ°λŠ₯은 μ ‘λ‘μ‚¬λ‘œ #λ₯Ό λΆ™μ—¬μ„œ νƒ€μž… 체크와 λŸ°νƒ€μž„ λͺ¨λ‘μ—μ„œ λΉ„κ³΅κ°œλ‘œ λ§Œλ“œλŠ” 역할을 ν•©λ‹ˆλ‹€.

class PasswordChecker {
#passwordHash: number;

constructor(passwordHash: number) {
this.#passwordHash = passwordHash;
}

checkPassword(password: string) {
return hash(password) === this.#passwordHash;
}
}

const checker = new PasswordChecker(hash('s3cret'));
checker.checkPassword('secret'); // κ²°κ³ΌλŠ” false
checker.checkPassword('s3cret'); // κ²°κ³ΌλŠ” true

#passwordHash 속성은 클래슀 μ™ΈλΆ€μ—μ„œ μ ‘κ·Όν•  수 μ—†μŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ ν΄λ‘œμ € 기법과 λ‹€λ₯΄κ²Œ 클래슀 λ©”μ„œλ“œλ‚˜ λ™μΌν•œ 클래슀의 κ°œλ³„ μΈμŠ€ν„΄μŠ€λΌλ¦¬λŠ” 접근이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

πŸ₯• μ•„μ΄ν…œ 57. μ†ŒμŠ€λ§΅μ„ μ‚¬μš©ν•˜μ—¬ νƒ€μž…μŠ€ν¬λ¦½νŠΈ λ””λ²„κΉ…ν•˜κΈ°β€‹

νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ½”λ“œλ₯Ό μ‹€ν–‰ν•œλ‹€λŠ” 것은, μ—„λ°€νžˆ λ§ν•˜μžλ©΄ νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ»΄νŒŒμΌλŸ¬κ°€ μƒμ„±ν•œ μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œλ₯Ό μ‹€ν–‰ν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

디버깅이 ν•„μš”ν•œ μ‹œμ μ— λΉ„λ‘œμ†Œ νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ 직접 μ‹€ν–‰λ˜λŠ” 것이 μ•„λ‹ˆλΌλŠ” 사싀을 κΉ¨λ‹«κ²Œ 될 κ²λ‹ˆλ‹€. λ””λ²„κ±°λŠ” λŸ°νƒ€μž…μ— λ™μž‘ν•˜λ©°, ν˜„μž¬ λ™μž‘ν•˜λŠ” μ½”λ“œκ°€ μ–΄λ–€ κ³Όμ •μœΌ γ„Ήκ±°μ³μ„œ λ§Œλ“€μ–΄μ§„ 것인지 μ•Œμ§€ λͺ»ν•©λ‹ˆλ‹€. 디버깅을 ν•˜λ©΄ 보게 λ˜λŠ” μ½”λ“œλŠ” μ „μ²˜λ¦¬κΈ°, 컴파일러, μ••μΆ•κΈ°λ₯Ό 거친 μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œμΌ κ²λ‹ˆλ‹€. μ΄λ ‡κ²Œ λ³€ν™˜λœ μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œλŠ” λ³΅μž‘ν•΄ λ””λ²„κΉ…ν•˜κΈ° 맀우 μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

디버깅 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λΈŒλΌμš°μ € μ œμ‘°μ‚¬λ“€μ€ μ„œλ‘œ ν˜‘λ ₯ν•΄ μ†ŒμŠ€λ§΅μ΄λΌλŠ” 해결책을 λ‚΄λ†“μ•˜μŠ΅λ‹ˆλ‹€. μ†ŒμŠ€λ§΅μ€ λ³€ν™˜λœ μ½”λ“œμ˜ μœ„μΉ˜μ™€ μ‹¬λ²Œλ“€μ„ 원본 μ½”λ“œμ˜ μ›λž˜ μœ„μΉ˜μ™€ μ‹¬λ²Œλ“€λ‘œ λ§€ν•‘ν•©λ‹ˆλ‹€. λŒ€λΆ€λΆ„μ˜ λΈŒλΌμš°μ €μ™€ λ§Žμ€ IDEκ°€ μ†ŒμŠ€λ§΅μ„ μ§€μ›ν•©λ‹ˆλ‹€.

μ½”λ“œκ°€ λ³΅μž‘ν•˜κ²Œ λ³€ν™˜λœλ‹€λ©΄ μ†ŒμŠ€λ§΅μ΄ ν•„μš”ν•©λ‹ˆλ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ μ†ŒμŠ€λ§΅μ„ 생성할 수 μžˆλ„λ‘ tsconfig.jsonμ—μ„œ sourceMap μ˜΅μ…˜μ„ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

{
"compilerOptions": {
"sourceMap": true,
}
}

이제 μ»΄νŒŒμΌμ„ μ‹€ν–‰ν•˜λ©΄ 각 .ts νŒŒμΌμ— λŒ€ν•΄μ„œ .js와 .js.map 두 개의 νŒŒμΌμ„ μƒμ„±ν•©λ‹ˆλ‹€. .js.map 파일이 λ°”λ‘œ μ†ŒμŠ€λ§΅μž…λ‹ˆλ‹€.
μ†ŒμŠ€λ§΅μ— λŒ€ν•΄ μ•Œμ•„μ•Ό ν•  λͺ‡ 가지 사항이 μžˆμŠ΅λ‹ˆλ‹€.

  • 이상적인 디버깅 ν™˜κ²½μ΄ 되렀면 μƒμ„±λœ μžλ°”μŠ€ν¬λ¦½νŠΈκ°€ μ•„λ‹Œ 원본 νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ†ŒμŠ€λ‘œ λ§€ν•‘λ˜λ„λ‘ ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ²ˆλ“€λŸ¬κ°€ 기본적으둜 νƒ€μž…μŠ€ν¬λ¦½νŠΈλ₯Ό μ§€μ›ν•œλ‹€λ©΄ 별도 μ„€μ • 없이 잘 λ™μž‘ν•΄μ•Ό ν•©λ‹ˆλ‹€. 그렇지 μ•Šλ‹€λ©΄ λ²ˆλ“€λŸ¬κ°€ μ†ŒμŠ€λ§΅μ„ 인식할 수 μžˆλ„λ‘ 좔가적인 섀정이 ν•„μš”ν•©λ‹ˆλ‹€.
  • μƒμš© ν™˜κ²½μ— μ†ŒμŠ€λ§΅μ΄ 유좜되고 μžˆλŠ”μ§€ 확인해야 ν•©λ‹ˆλ‹€. 디버거λ₯Ό 열지 μ•ŠλŠ” 이상은 μ†ŒμŠ€λ§΅μ΄ λ‘œλ“œλ˜μ§€ μ•ŠμœΌλ―€λ‘œ, μ‹€μ œ μ‚¬μš©μžμ—κ²Œ μ„±λŠ₯ μ €ν•˜λŠ” λ°œμƒν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ μ†ŒμŠ€λ§΅μ— 원본 μ½”λ“œμ˜ 인라인 볡사본이 ν¬ν•¨λ˜μ–΄ μžˆλ‹€λ©΄ κ³΅κ°œν•΄μ„œλŠ” μ•ˆ 될 λ‚΄μš©μ΄ λ“€μ–΄ μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.

νƒ€μž… 체컀가 μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜κΈ° 전에 λ§Žμ€ 였λ₯˜λ₯Ό μž‘μ„ 수 μžˆμ§€λ§Œ, 디버거λ₯Ό λŒ€μ²΄ν•  μˆ˜λŠ” μ—†μŠ΅λ‹ˆλ‹€. μ†ŒμŠ€λ§΅μ„ μ‚¬μš©ν•΄μ„œ μ œλŒ€λ‘œ 된 νƒ€μž…μŠ€ν¬λ¦½νŠΈ 디버깅 ν™˜κ²½μ„ κ΅¬μΆ•ν•˜κΈΈ λ°”λžλ‹ˆλ‹€.