자바스크립트 퀴즈북 리마인드 Day 13: 모듈은 파일 묶음이.
ESM live binding, default/named export, circular dependency, dynamic import, CJS와 ESM 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.
오늘의 질문: “class로 만들었으니 자바나 타입스크립트의 class처럼만 읽으면 될까?”
class UserCard {
constructor(user) {
this.user = user;
}
render() {
return `<article>${this.user.name}</article>`;
}
}
const card = new UserCard({ name: "Jin" });
겉으로는 익숙한 class입니다. 하지만 자바스크립트에서 render는 각 인스턴스 안에 복사되는 함수가 아니라 UserCard.prototype에 놓이는 메서드입니다. card.render()를 호출하면 엔진은 먼저 card 자신의 프로퍼티를 보고, 없으면 프로토타입 체인을 따라 올라가서 render를 찾습니다.
class 문법은 prototype을 없애지 않습니다. 더 읽기 쉬운 문법으로 감싼 것입니다.
class ButtonView {
click() {
return "clicked";
}
}
const view = new ButtonView();
Object.hasOwn(view, "click"); // false
Object.hasOwn(ButtonView.prototype, "click"); // true
리뷰할 때 이 차이가 중요한 이유는 메서드 위치가 동작과 성능에 영향을 주기 때문입니다. prototype 메서드는 인스턴스들이 공유합니다. 반대로 constructor 안에서 함수를 매번 만들면 인스턴스마다 새 함수가 생깁니다.
class ItemRow {
constructor(item) {
this.item = item;
this.render = () => `<li>${this.item.title}</li>`;
}
}
이런 코드가 항상 틀린 것은 아닙니다. arrow function 필드나 constructor 바인딩은 this 문제를 줄이는 데 쓰이기도 합니다. 다만 “왜 공유 메서드가 아니라 인스턴스별 함수여야 하는가?”를 설명할 수 있어야 합니다.
프로토타입 체인은 상속이라기보다 조회 경로로 읽는 편이 안전합니다.
const base = {
role: "guest",
canEdit() {
return false;
},
};
const user = Object.create(base);
user.name = "Jin";
user.role = "admin";
user.role; // "admin"
user.canEdit(); // false
role은 user 자신의 값이 있으므로 거기서 끝납니다. canEdit은 자기 자신에게 없으니 base에서 찾습니다. 같은 이름의 프로퍼티가 더 가까운 객체에 있으면 위쪽 값은 가려집니다.
프론트엔드에서는 이 감각이 테스트 mock, 라이브러리 객체 확장, class 상속 디버깅에서 필요합니다. 어떤 값이 “원래 객체에 있는 값”인지, “prototype에서 올라온 값”인지 구분하지 못하면 원인을 잘못 짚습니다.
자바스크립트 class 상속은 쓸 수 있지만, UI 코드에서 깊은 상속 구조는 리뷰 비용을 키웁니다.
class TrackingButton extends Button {
click() {
analytics.track("button_click");
return super.click();
}
}
이 코드를 보려면 TrackingButton.click, Button.click, 그리고 super 호출 순서를 함께 봐야 합니다. 간단한 확장은 괜찮지만, 상태·렌더링·네트워크·분석 이벤트가 상속 계층에 흩어지면 변경 영향이 흐려집니다.
많은 프론트엔드 코드에서는 합성이 더 선명합니다.
function createTrackingButton(button, analytics) {
return {
click() {
analytics.track("button_click");
return button.click();
},
};
}
합성이 항상 정답이라는 뜻은 아닙니다. 다만 리뷰 질문은 명확해집니다.
이 동작은 “is-a” 관계로 묶어야 하나, 아니면 기존 객체에 기능을 덧붙이는 편이 충분한가?
요즘 문법에서는 class field도 자주 보입니다.
class SearchBox {
query = "";
onChange = (event) => {
this.query = event.target.value;
};
reset() {
this.query = "";
}
}
query와 onChange는 인스턴스마다 생깁니다. reset은 prototype에 놓입니다. onChange를 arrow field로 만들면 callback으로 넘겨도 this가 덜 흔들립니다. 대신 인스턴스마다 함수가 만들어진다는 비용이 있습니다.
그래서 코드 리뷰에서는 이렇게 봅니다.
| 코드 모양 | 좋은 이유 | 확인할 점 |
|---|---|---|
| prototype method | 인스턴스들이 메서드를 공유함 | callback으로 빼도 this가 안전한가? |
| arrow field method | this가 lexical하게 고정됨 | 인스턴스마다 함수가 필요한가? |
| extends/super | 기존 구현을 재사용함 | 호출 순서와 부수 효과가 숨어 있지 않은가? |
| Object.create | 명시적 prototype 연결 | 소유 프로퍼티와 상속 프로퍼티를 구분하는가? |
obj.method가 자기 자신의 프로퍼티인지 prototype에서 온 것인지 확인했는가?super 호출 전후에 부수 효과 순서가 중요하지 않은가?class를 보더라도 핵심은 “객체가 어떤 설계 철학을 따르는가”가 아닙니다. 런타임에서 프로퍼티를 어디서 찾고, 메서드가 어디에 놓이며, 변경 영향이 어느 객체까지 퍼지는지를 읽는 것입니다.
class Button {
click() { return 'ok'; }
}
const b = new Button();Post Q&A
자바스크립트 퀴즈북 리마인드 Day 11: class를 봐도 프로토타입을 읽는다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
ESM live binding, default/named export, circular dependency, dynamic import, CJS와 ESM 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.
DOM 이벤트의 capture/bubble, delegation, default action, React synthetic event 경계를 프론트엔드 코드 리뷰 관점에서 정리합니다.
프로토타입 조회, class 문법, 인스턴스 필드와 prototype 메서드 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.
고차 함수, 순수 함수, 부수 효과, async 함수의 Promise 반환을 코드 리뷰에서 어떻게 읽을지 정리합니다.
this 바인딩을 정의 위치가 아니라 호출 형태, 메서드 분리, arrow function, bind 기준으로 읽는 방법을 코드 리뷰 관점에서 정리합니다.
콜 스택, 태스크 큐, 마이크로태스크 큐를 기준으로 Promise와 setTimeout 실행 순서를 코드 리뷰 관점에서 정리합니다.
클로저가 값 복사가 아니라 렉시컬 환경 참조라는 점을 stale closure, 루프 콜백, React 이벤트 리뷰 관점에서 정리합니다.
스코프 체인, 렉시컬 환경, var/let/const 호이스팅 차이를 콜백·상태 버그 리뷰 관점에서 다시 정리합니다.
객체 참조와 얕은 복사, Object와 Map/Set의 차이, WeakMap/WeakSet이 필요한 메모리 상황을 프론트엔드 코드 리뷰 관점에서 정리합니다.
객체 프로퍼티를 key/value가 아니라 descriptor로 읽는 법, getter/setter와 defineProperty, preventExtensions/seal/freeze의 경계를 코드 리뷰 관점에서 정리합니다.
객체가 원시값으로 바뀌는 ToPrimitive 흐름, + 연산자의 문자열 연결과 숫자 덧셈 분기, Symbol.toPrimitive가 코드 리뷰에서 왜 중요한지 정리합니다.
==, ===, Object.is, SameValueZero가 각각 어떤 비교 알고리즘을 쓰는지 정리하고, includes와 indexOf, Map/Set 키 비교에서 생기는 프론트엔드 리뷰 포인트를 잡습니다.
자바스크립트의 Number가 왜 정수처럼 보여도 부동소수점 모델 위에서 움직이는지, NaN과 -0, safe integer, BigInt를 프론트엔드 코드 리뷰 관점에서 다시 정리합니다.