본문으로 건너뛰기

리액트 공식문서 스터디 1주차 - 2

· 약 4분
준열
프론트엔드 개발자

Using curly braces: A window into the JavaScript world

  • 동적으로 값을 지정하려면 중괄호({})를 사용해 변수・함수・연산 결과 등 다양한 JS 표현식 삽입 할 수 있다.
const name = "Gregorio Y. Zara";
return <h1>{name}'s To Do List</h1>;
// 변수를 JS 표현식으로 삽입
<h1>To Do List for {formatDate(today)}</h1>
// 함수호출을 JS 표현식으로 삽입

✨참고사항✨

  • JSX 중괄호 {}에서는 자바스크립트 표현식만 허용된다.

    • 변수, 숫자, 산술 연산, 함수 호출, 배열 map/filter, 삼항(?), 논리 (&&) 등 값을 반환하는 코드만 쓸 수 있다.

    • 자바스크립트 문(statement)는 직접 쓸 수 없다.

      • if (조건)
      • for (...)
      • 변수 선언/할당 const ~ let ~ var ~
      • 함수 정의 익명, 선언식
    • 이러한 제어/분기/반복 로직은 JSX 바깥(컴포넌트 내부)에서 처리해 결과를 중괄호로 넣어야 한다.


Where to use curly braces

  • 중괄호 사용 위치는 JSX 태그 내 텍스트와 속성 값 2곳뿐이다.

    • <h1>{name}'s To Do List</h1> (텍스트)

    • <img src={avatar} /> (속성값)

    • 문자열 전달과 구별되며, src="avatar"처럼 쓰면 문자열 그대로 넘어가서 동작이 다름.


Using "double curlies": CSS and other objects in JSX

  • 객체를 JSX에서 속성값으로 전달할 때는 중괄호를 두 번 겹침 ({{}}).
<ul style={{ backgroundColor: "black", color: "pink" }}>...</ul>
// 인라인 스타일 예시
// 속성값이 객체이므로 JSX용 {} 객체용 {}가 중첩되어야 한다.

More fun with JavaScript objects and curly braces

  • 자바스크립트 객체를 여러 데이터로 정리하고, JSX에서 필요한 부분만 뽑아 쓸 수 있다.
const person = { name: 'Gregorio Y. Zara', theme: { backgroundColor: 'black', color: 'pink' } };
...
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
...
</div>
// 객체 내부의 필요한 값들만 가져와서 사용할 수 있음.


함께 풀어보는 챌린지

  1. 아래 코드는 Objects are not valid as a React child를 표출한다.
const person = {
name: "Gregorio Y. Zara",
theme: {
backgroundColor: "black",
color: "pink",
},
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}

  1. Extract the image URL into the person object.
const person = {
name: "Gregorio Y. Zara",
imageUrl: "https://i.imgur.com/7vQD0fPs.jpg",
theme: {
backgroundColor: "black",
color: "pink",
},
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img className="avatar" src={person.imageUrl} alt="Gregorio Y. Zara" />
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}

  1. Write an expression inside JSX curly braces
const baseUrl = "https://i.imgur.com/";
const person = {
name: "Gregorio Y. Zara",
imageId: "7vQD0fP",
imageSize: "s",
theme: {
backgroundColor: "black",
color: "pink",
},
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src={`${baseUrl}${person.imageId}${person.imageSize}.jpg`}
alt={person.name}
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}


자바스크립트 표현식의 대표적인 예 map에 대하여

  • 반복 렌더링의 대표적 "자바스크립트 표현식"

  • map의 역할

    • 배열의 각 요소를 함수를 통해 변환해서 새로운 배열을 만들고, 그 결과를 JSX 중괄호 안에서 바로 사용할 수 있음.
  • 대표적 활용

    • UI 리스트 반복 렌더링 ( 배열 -> 여러 컴포넌트 생성 )
{
items.map((item) => <li key={item.id}>{item.name}</li>);
}


for문과의 차이점

기준mapfor
종류배열 메서드반복문(statement)
반환값새 배열(표현식)없음(문)
JSX 사용JSX() 내부 사용 가능JSX() 내부 사용 불가
함수형/불변성함수형 스타일, 불변성 유지명령형 스타일, 변수 선언/수정 필요
용도반복 렌더링, 결과 배열 필요할 때 사용절차적 반복, 다양한 로직 처리에 사용
코드예시{arr.map(x => <div>{x}</div>)}for(let i=0; i<arr.length; i++) {...}


Key 값의 중요성

1. Key값이 필요한 이유

  • React는 리스트를 렌더링할 때 각 요소가 "어떤 데이터에 대응되는지" 식별해야한다.

  • Key는 React가 아이템의 변화가 있을 때 최소한의 DOM 변경만 하도록 최적화하는 기준값이다.


2. Key값으로 사용 가능한 것 과 불가능 한 것

  • 가능한 것 : 데이터의 고유한 값 ( id, db pk ), 변하지 않는 uuid

  • 불가능한 것 : 배열의 인덱스 ( 순서값 ), 매번 새로 생성되는 랜덤값 ( uuid )

리액트 공식문서 스터디 1주차 - 1

· 약 4분
준열
프론트엔드 개발자

Your First Component

  1. 컴포넌트는 React의 핵심 개념 중 하나입니다.

  2. 컴포넌트는 사용자 인터페이스 (UI)를 구축하는 토대입니다.


Components: UI building blocks

<article>
<h1>My First Component</h1>
<ol>
<li>Components: UI Building Blocks</li>
<li>Defining a Component</li>
<li>Using a Component</li>
</ol>
</article>
  • React는 MarkUP, CSS, JavaScript 커스텀 컴포넌트로 결합할 수 있게 해줍니다.

    여기로 컴포넌트란 앱에서 재사용 가능한 UI 요소이다.

  • 위의 목차코드 또한 <TableOfContents /> 컴포넌트로 만들어 모든 페이지에서 렌더링할 수 있습니다.


그렇다면 컴포넌트화를 왜 해야하는 걸까?

  • 프로젝트가 커질수록 기존에 작성한 컴포넌트를 재사용하여 더 빠르고 쉽게 디자인을 구현할 수 있습니다.
  • 또한 Chakra UI , Material UI 같은 React 오픈소스 라이브러리를 이용하여 더 빠르게 구현할 수도 있습니다.

Defining a component

  • 이전에는 웹 페이지를 만들 때 웹 개발자들은 콘텐프를 마크업으로 작성한 후, 자바스크립트로 상호작용을 추가했습니다.

  • 더 많은 상호작용을 위해서 React가 나타났고, React 컴포넌트는 마크업을 더할 수 있는 자바스크립트 함수입니다.


1. Export the component

  • export default 문법을 사용하여 파일내에 주된 함수로 지정할 수 있습니다.

  • 또한 이 함수를 다른 파일에서 import하여 사용할 수 있습니다.


2. Define the function

  • function 함수명() {} 구문을 사용해 함수명에 해당하는 JavaScript 함수를 정의합니다.

    React 컴포넌트는 일반 JavaScript 함수이지만, 이름을 반드시 `대문자`로 시작해야 정상적으로 작동합니다! (소문자로 시작하면 컴포넌트로 인식되지 않음)

3. Add markup

  • return 구뭄은 한 줄로 작성하거나, 같은 줄에 있지 않다면 반드시 소괄호로 감싸야합니다.

Using a component

function Profile() {
return <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />;
}

export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
  • Profile 컴포넌트를 정의해 다른 컴포넌트 안에서 중첩하여 사용할 수 있습니다.

  • 위 코드처럼 여러 개의 Profile 컴포넌트를 사용하는 Gallery 컴포넌트를 만들 수 있습니다.


1. What the browser sees

<section>
<h1>Amazing scientists</h1>
<img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
<img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
<img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
</section>
  • <section>은 소문자로 시작해 React가 HTML 태그로 인식합니다.

  • <Profile />는 대문자로 시작해 React가 사용자가 만든 Custom 컴포넌트로 인식합니다.

  • 그렇기에 브라우저에서는 컴포넌트 대신 HTML 태그만 표시하게 됩니다. <Proile> -> <img>


2. Nesting and organizing components

  • 컴포넌트는 일반 자바스크립트 함수이므로, 크기가 작거나 관련된 컴포넌트는 같은 파일에 함께 둘 수 있다.

  • 부모 컴포넌트가 자식 컴포넌트를 여러번 렌더링할 수 있고, 한 번 정의된 컴포넌트는 여러 곳에서 반복 사용이 가능하다.

  • 컴포넌트 정의를 다른 컴포넌트 내부에 중첩하면 안 되고, 항상 파일의 최상단에 선언해야 한다.



Importing and Exporting Components

  • 컴포넌트의 재사용성이 React의 진정한 장점이다.

  • 여러 컴포넌트를 만들어 서로 중첩해서 사용할 수 있지만, 컴포넌트가 많아질수록 파일을 분리하는 것이 효율적이다.

  • 파일을 나누면 코드를 더 쉽게 파악할 수 있고, 다양한 곳에서 컴포넌트를 재사용하는 것이 가능하다.


1. The root component file

function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>;
);
}

export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
  • 컴포넌트가 작거나 서로 연관되어 있으면, 한 파일에 함께 둘 수 있다.

  • 파일이 너무 커지면 Profile 같은 컴포넌트를 별도 파일로 이동할 수 있습니다.


2. Exporting and importing a component

import Gallery from "./Gallery.js";

export default function App() {
return <Gallery />;
}
  • 루트 컴포넌트 파일에서 분리해서 각각 export한 뒤 import하여 관리하는 것이 더 좋습니다.

3. Exporting and importing multiple components from one file

export function Profile() {
return <img src="https://i.imgur.com/QIrZWGIs.jpg" alt="Alan L. Hart" />;
}

export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
  • 한 파일에 default export는 하나만 가능하지만, 여러 개의 named export는 가능합니다.

4. Exporting and importing multiple components from the same file

  • 컴포넌트가 더 많아지면 Profile, Gallery 등 각각을 Profile.js, Gallery.js처럼 별도 파일로 분리하여 관리합니다.


Writing Markup with JSX

  • JSX : JavaScript file 안에서 HTML-like markup을 사용할 수 있게 하는 extension

1. JSX: Putting markup into JavaScript

  • 이전에는 content와 logic을 HTML과 JavaScript 파일에 각각 분리해서 작성했다.

  • Web이 더 인터랙티브해지면서 자바스크립트(로직)가 content(HTML)까지 직접 다루는 일이 많아졌다.

  • React에서는 렌더링 로직과 마크업(markup)을 한 곳에서 모두 관리한다. 이 장소가 바로 "Component"(컴포넌트)이다.

  • Rendering logic과 markup을 같은 자리에 두면, 매번 수정할 때마다 둘이 항상 동기화된다. 반면 관련 없는 것들(예: 버튼과 사이드바)은 분리돼 있으므로 개별적으로 안전하게 수정할 수 있다.

  • React 컴포넌트 함수는 JSX 마크업을 포함한다.

  • JSX와 React는 서로 다른 개념이다. JSX는 문법 확장(syntax extension)이고, React는 자바스크립트 라이브러리이다. 두 개념은 독립적으로 사용할 수도 있다.


2. The Rules of JSX

  • JSX와 HTML의 차이점

    • 여러 요소를 반환하려면 가장 바깥에 하나의 태그로 감싸야 합니다.

    • 모든 태그를 닫아줘야 합니다.

    • 대부분의 속성은 camelCase로 작성해야 하며, class 대신 className을 씁니다.

중급 프로젝트 회고

· 약 4분
준열
프론트엔드 개발자

프로젝트 기간 : 2025-09-25 ~ 2025-10-22

🤔 프로젝트를 시작하며

3주가 넘는 기간동안 중급 프로젝트를 진행하였습니다. 이번 파트때도 팀장을 맡게되어 조금 힘들었지만 좋은 팀원들이 있었기에 성공적으로 프로젝트를 마칠 수 있었습니다.
개인적으로 많은 성장을 한 프로젝트이기에 시간이 조금 지났지만 회고글을 작성해보려고 합니다.

✨ 프로젝트 선정 및 소개

부트캠프 기간동안 총 3번의 프로젝트를 진행하게 됩니다. 그 중 2번째 프로젝트에 대한 얘기를 시작해보겠습니다.
저희 팀은 WHYNE 이라는 브랜드 네임을 가진 사용자의 리뷰와 평가를 바탕으로 나만의 와인창고를 만드는 웹사이트 프로젝트를 진행하게 되었습니다.

✨ 프로젝트 선정 이유

프로젝트 주제를 선정하기 위해서 가장 중요한게 생각했던 부분은 팀원들의 의견이 가장 컸습니다.
단순히 CRUD를 구현하는 것이 아닌, 우리만의 특별한 기능을 넣을 수 있을만한 주제를 선정하기 위해 팀원들과 상의하였고, 그 중에서도 깔끔한 디자인을 갖고있던 WHYNE으로 선정하게 되었습니다.

📚 프로젝트 과정

1. R&R 분배

초급 프로젝트에서의 아쉬움을 뒤로하고 중급 프로젝트에서는 많은 기능과 컴포넌트를 만들고 싶어 아래와 같은 R&R 분배를 했습니다.

  • 프로젝트 기초 세팅
  • 랜딩페이지
  • 와인 상세페이지
  • 리뷰 등록/수정 페이지
  • Taste 컴포넌트
  • 리뷰 등록/수정 모달
  • 무한스크롤
  • Toast

총 8가지의 R&R을 분배받아 프로젝트를 진행하였습니다.

2. 프로젝트 계획

프로젝트를 진행하기 전에 아래와 같은 목표를 세웠습니다.
생각보다 3주라는 시간은 매우 짧았기에, 다른팀들보다 먼저 시작하여 추가기능을 도입하고자 하는 목표를 세웠습니다. 그렇기에 1주차는 컴포넌트 개발, 2주차는 페이지 개발, 3주차는 추가기능 및 QA를 목표로 프로젝트를 시작하였습니다.

2-1. 협업 툴 선정

일단 기본적으로 Figma를 사용하여 요구사항에 대한 디자인을 확인하였고, Notion를 사용하여 전체적인 프로젝트 관리를 진행하였습니다. 그리고 github를 사용하여 코드를 관리하였습니다.

또한 실시간 소통 및 코드리뷰를 위해 Discord 사용하고 이슈 및 애로사항 공유를 위해 Zep이라는 플랫폼을 활용하여 코어타임과 팀회의를 매일 성실하게 참여하였습니다.

팀장의 역할을 성실하게 수행하기위해 Discord WebHookZep WorkSpace를 직접 설정하고 제작하여 팀원들의 편의성을 높혔습니다.

2-2. 협업 전략

GitHub Organization을 사용하여 팀 레포지토리를 생성한 후, fork 방식이 아닌 팀 레포지토리에 직접 Push하는 방식을 사용하였습니다.

총 3개의 브랜치를 활용하여 깃 전략을 세웠고, main develop 개별 브랜치로 통일하여 기능별 혹은 디자인별로 브랜치를 생성하여 develop 브랜치로 PushPR을 진행한 후 main 브랜치로 Merge를 진행하였습니다.

2-3. 팀장의 역할

팀장의 역할 중 개인적으로 가장 중요하게 생각했던 부분은 팀의 화합이였습니다.

누구하나 뒤쳐지지않고 프로젝트에 잘 참여할수 있도록 팀미팅 분위기 및 수시로 소통을 하며 팀원들과 친해졌고, 어느 팀보다 좋은 분위기속에서 프로젝트를 마무리 할 수 있었습니다.


- 일단 팀장 역할 수행하시느라 고생많으셨습니다.
프로젝트 세팅부터 책임지고 꼼꼼하게 진행해주시는 모습을 보고 믿음이 간 것 같습니다.
또한 같이 프로젝트 진행하면서 여러가지 새로운 시도들을 많이 해본 것 같습니다.
팀 회의 때 마다 분위기도 밝게 이어나가주셔서 감사합니다.

- 항상 밝은 분위기와 긍정적인 에너지로 팀을 이끌어 주셔서 팀 전체가 좋은 분위기에서 프로젝트를 진행할 수 있었습니다.
팀장 역할로 바쁜 와중에도 팀원 개개인과 프로젝트 일정을 꼼꼼히 챙겨주시고, 전체적인 진행 상황을 균형감 있게 관리해 주신 점이 감사하고,
개인 역할도 성실히 수행하시면서 코드 리뷰도 꾸준히 세심하게 남겨주셔서 많이 배울 수 있었고 성장하는 데 큰 도움이 되었습니다!

- 팀장 역할을 잘 해주셔서, 소통 과정에서 많은 도움을 받을 수 있었습니다. 감사합니다.

파트를 마무리하고 위와 같은 피어리뷰를 받으면서 나의 행동들이 결코 틀리지 않았다는걸 깨닫게 되면서 앞으로도 소프트스킬을 더욱 키워나가야겠다고 생각하게 되었습니다.

2-4. 그라운드 룰

프로젝트를 본격적으로 진행하기에 앞서 팀 그라운드룰을 정해 항상 리마인드하여 팀원분들이 까먹지 않도록 진행하였습니다.

첫 번째로, 데일리 팀 스크럼은 PM 13:30에 진행되며, Zep을 활용하여 작업 및 애로사항들을 공유하였습니다.

두 번째로, 코어타임을 짧은 시간이 아닌 매일 AM 09:00 ~ PM 18:00으로 정하여 언제든 팀원들끼리 소통할 수 있도록 했습니다.

마지막으로, Notion을 이용하여 팀원들의 진행사항 및 QA를 작성하여 문서화하는 습관을 들였습니다.

🛠️ 기술스택 선정

저희 팀은 Next.js 15버전을 사용하여 App router을 기본 라우팅 방식으로 선정하였고, 팀원 모두 Tailwind 경험이 있어 스타일링 방식은 손쉽게 결정하였습니다.

또한 다른팀보다 시간적 여유가 있었기에 컨벤션을 지키기 위한 husky+lint-staged를 사용하여 컨벤션을 지키고 깨끗한 코드를 유지하였습니다.

그리고 공통컴포넌트 문서화를 위해 Storybook을 사용하여 공통 컴포넌트를 문서화하였습니다.

Axios + Tanstack Query를 사용하여 API를 호출하고, Vercel을 사용하여 배포하였습니다.

🐝 프로젝트 진행 과정

4. 첫 위기

프로젝트를 진행하면서, 팀원 한 분이 취업으로 인해 중도하차를 하게되어 미리 짜여져있던 개발 계획이 어그러져 시간적 낭비를 하게되었습니다.

하지만 팀원 모두 개인시간까지 투자하여 중도하차 팀원의 작업까지 서로 알맞게 분배하여 큰 트러블 없이 진행할 수 있었습니다.

이 과정에서 느꼈던 점은 계획은 계획일뿐 항상 원하는대로 이루어지지 않는다는 것을 알았고 다음에는 더 체계적으로 계획을 세워 팀원들의 혼란을 줄여야겠다고 생각하게 되었습니다.

5. 빌드 간소화

실시간 코드리뷰를 진행하면서 PRMerge를 할 때마다 Storybook 배포로 인한 파이프라인 낭비가 발생하여 개발 속도가 늦어지는 경우가 빈번했습니다.

그렇기에 이 문제점을 해결하기 위해 팀원들과 회의를 하였고 팀장인 제가 Storybook을 조건부적으로 배포할 수 있는 방법을 찾게 되었습니다.

방법을 찾아 내용을 팀원들과 공유하였고 시간이 오래 걸리지않겠냐는 팀원들의 우려속에서도 포기하지않고 책임감있게 진행하여 빠른시간내에 해결하였습니다.

이렇게 팀원들과 함께 노력하여 빌드 간소화를 성공적으로 수행하여 프로젝트가 끝날때까지 개발 속도를 높일 수 있었습니다.

⭐️ 프로젝트 결과

🌱 KPT 회고

🤔 다음 프로젝트에서 개선해야할 나의 모습

클래스

· 약 5분
준열
프론트엔드 개발자

1. 클래스와 인스턴스의 개념 이해

클래스 는 붕어빵 틀, 인스턴스 는 붕어빵이라고 생각하면 편하다.

  • 상위클래스 - superclass
  • 하위클래스 - subclass -> 부모의 특징을 물려받음

2. 자바스크립트의 클래스

프로토타입 기반 상속

자바스크립트에서는 생성자 함수와 프로토타입을 이용해 클래스를 구현한다.

  • 생성자 함수 Array를 new 연산자와 호출하면 인스턴스가 생성된다.
  • 이때 Array를 일종의 클래스라고 하면, Array의 prototype 객체 내부 요소들이 인스턴스에 상속된다고 볼 수 있다.
  • 인스턴스에 상속되는지(인스턴스가 참조하는지) 여부에 따라 스태틱 멤버와 인스턴스 멤버로 나뉜다.
// 생성자 함수
var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};

// 프로토타입 메서드 정의 (모든 인스턴스가 공유)
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};

// 스태틱 메서드 정의 (클래스 자체에 속함)
Rectangle.isRectangle = function (instance) {
return (
instance instanceof Rectangle && instance.width > 0 && instance.height > 0
);
};

var rect1 = new Rectangle(3, 4);
console.log(rect1.getArea()); // 정상 작동, 출력: 12

// 잘못된 호출: rect1은 스태틱 메서드를 호출할 수 없다.
console.log(rect1.isRectangle(rect1)); // TypeError: rect1.isRectangle is not a function

// 올바른 호출: 스태틱 메서드는 클래스 이름을 통해 호출해야 한다.
console.log(Rectangle.isRectangle(rect1)); // true


3. 클래스 상속

  • ES5까지의 자바스크립트에는 클래스가 없다. ES6에서 클래스가 도입됐지만 역시나 prototype을 기반으로 한 것이다.
  • 자바스크립트에서 클래스 상속을 구현했다는 것은 결국 프로토타입 체이닝을 잘 연결한 것이다.

정보

단순히 SubClass.prototype = new SuperClass() 를 하면, 부모의 인스턴스 프로퍼티까지 상속되는 문제가 발생한다.


하위클래스의 프로토타입이 상위클래스의 인스턴스 프로퍼티를 가지지 않게 하는 방법


방법 1: 인스턴스 생성 후 프로퍼티 제거

상속을 통해 자식 클래스가 부모 클래스의 메서드를 활용하면서도 자식 클래스의 고유한 메서드를 가질 수 있도록 설정할 수 있음

var extendClass1 = function (SuperClass, SubClass, subMethods) {
// SubClass의 프로토타입을 SuperClass의 인스턴스로 설정하여 상속 구조를 만든다.
SubClass.prototype = new SuperClass();

// 상속된 프로토타입에서 자신의 프로퍼티(인스턴스 프로퍼티)를 제거
for (var prop in SubClass.prototype) {
if (SubClass.prototype.hasOwnProperty(prop)) {
delete SubClass.prototype[prop];
}
}

// subMethods가 제공된 경우, 이를 SubClass의 프로토타입에 추가한다.
if (subMethods) {
for (var method in subMethods) {
SubClass.prototype[method] = subMethods[method];
}
}

// SubClass의 프로토타입을 동결하여 변경 불가능하게 만든다.
Object.freeze(SubClass.prototype);
return SubClass;
};

// Rectangle 클래스 정의
var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};

// Rectangle 클래스의 프로토타입 메서드 정의
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};

// Square 클래스 정의 및 Rectangle 클래스 상속
var Square = extendClass1(Rectangle, function (width) {
// Rectangle 생성자를 호출하여 너비와 높이를 동일하게 설정
Rectangle.call(this, width, width);
});

// Square 인스턴스 생성
var sq = new Square(5);

// Square 인스턴스에서 Rectangle 클래스의 메서드 사용
console.log(sq.getArea()); // 25

방법 2: 빈 함수를 활용

빈 함수를 브리지(Bridge)로 사용해 프로토타입 체인을 설정한다. 이를 통해 자식 클래스가 부모 클래스의 프로토타입을 상속받으면서도 새로운 메서드를 추가할 수 있게 된다.

var extendClass2 = (function () {
// 빈 함수를 브리지로 사용
var Bridge = function () {};

// 클로저를 사용하여 상속 함수 반환
return function (SuperClass, SubClass, subMethods) {
// Bridge의 프로토타입을 SuperClass의 프로토타입으로 설정
Bridge.prototype = SuperClass.prototype;

// SubClass의 프로토타입을 Bridge의 인스턴스로 설정
SubClass.prototype = new Bridge();

// subMethods가 제공된 경우, 이를 SubClass의 프로토타입에 추가
if (subMethods) {
for (var method in subMethods) {
SubClass.prototype[method] = subMethods[method];
}
}

// SubClass의 프로토타입을 동결하여 변경 불가능하게 설정
Object.freeze(SubClass.prototype);
return SubClass;
};
})();

// Rectangle 클래스 정의
var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};

// Rectangle 클래스의 프로토타입 메서드 정의
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};

// Square 클래스 정의 및 Rectangle 클래스 상속
var Square = extendClass2(Rectangle, function (width) {
Rectangle.call(this, width, width); // Rectangle 생성자를 호출하여 너비와 높이를 동일하게 설정
});

// Square 인스턴스 생성
var sq = new Square(5);

// Square 인스턴스에서 Rectangle 클래스의 메서드 사용
console.log(sq.getArea()); // 출력: 25

방법 3: Object.create 활용

부모 클래스의 프로토타입을 자식 클래스의 프로토타입으로 설정해 상속구조를 만든다.

var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};

// Rectangle 클래스의 프로토타입 메서드 정의
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};

// Square 클래스 정의
var Square = function (width) {
Rectangle.call(this, width, width); // Rectangle 생성자를 호출하여 너비와 높이를 동일하게 설정
};

// Square 클래스의 프로토타입을 Rectangle 클래스의 프로토타입을 상속받도록 설정
Square.prototype = Object.create(Rectangle.prototype);

// Square 클래스의 프로토타입을 동결하여 변경되지 않도록 설정
Object.freeze(Square.prototype);

// Square 인스턴스 생성
var sq = new Square(5);

// Square 인스턴스에서 Rectangle 클래스의 메서드 사용
console.log(sq.getArea()); // 출력: 25

constructor 복구하기

위의 방법들은 기본적인 상속에는 성공했지만,
constructor 참조가 여전히 상위클래스를 가리키고 있다. 그렇기에 명시적으로 아래처럼 복구해야 한다.

SubClass.prototype.constructor = SubClass;

상위 클래스에서 접근 수단 제공

super 흉내내보기

  • superclass의 생성자 함수에 직접 접근하고자 할 때는 this.super(),
  • superclass의 프로토타입 메서드에 접근하고자 할 때는 this.super(propName)와 같이 사용하면 된다.
var extendClass = function (SuperClass, SubClass, subMethods) {
SubClass.prototype = Object.create(SuperClass.prototype);
SubClass.prototype.constructor = SubClass;

SubClass.prototype.super = function (propName) {
// 추가된 부분 시작
var self = this;
if (!propName)
return function () {
SuperClass.apply(self, arguments);
};
var prop = SuperClass.prototype[propName];
if (typeof prop !== "function") return prop;
return function () {
return prop.apply(self, arguments);
};
}; // 추가된 부분 끝

if (subMethods) {
for (var method in subMethods) {
SubClass.prototype[method] = subMethods[method];
}
}

Object.freeze(SubClass.prototype);
return SubClass;
};

var Rectangle = function (width, height) {
this.width = width;
this.height = height;
};

Rectangle.prototype.getArea = function () {
return this.width * this.height;
};

var Square = extendClass(
Rectangle,
function (width) {
this.super()(width, width); // super 사용 (1)
},
{
getArea: function () {
console.log("size is :", this.super("getArea")()); // super 사용 (2)
},
}
);

var sq = new Square(10);
sq.getArea(); // size is : 100
console.log(sq.super("getArea")()); // 100


4. ES6의 클래스 및 클래스 상속

ES5와 ES6의 클래스 문법 비교

// ES5 클래스 정의
var ES5 = function (name) {
this.name = name;
};

// 정적 메서드 추가
ES5.staticMethod = function () {
return this.name + " staticMethod";
};

// 프로토타입 메서드 추가
ES5.prototype.method = function () {
return this.name + " method";
};

// ES5 인스턴스 생성과 메서드 호출
var es5Instance = new ES5("es5");
console.log(ES5.staticMethod()); // 출력: es5 staticMethod
console.log(es5Instance.method()); // 출력: es5 method

// ES6 클래스 정의
var ES6 = class {
constructor(name) {
this.name = name;
}

// 정적 메서드 정의
static staticMethod() {
return this.name + " staticMethod";
}

// 프로토타입 메서드 정의
method() {
return this.name + " method";
}
};

// ES6 인스턴스 생성과 메서드 호출
var es6Instance = new ES6("es6");
console.log(ES6.staticMethod()); // 출력: es6 staticMethod
console.log(es6Instance.method()); // 출력: es6 method

주요 차이점

  • ES5에서는 생성자 함수와 프로토타입을 사용하여 클래스를 정의하고 메서드를 추가한다. 정적 메서드는 직접 클래스 함수에 추가된다.
  • ES6에서는 class 문법을 사용하여 클래스를 정의하고 constructor, 정적 메서드, 인스턴스 메서드를 명시적으로 정의한다. 코드가 더 간결하고 이해하기 쉽다.
  • ES6은 클래스 상속을 보다 직관적으로 지원하며, super 키워드를 사용하여 부모 클래스의 생성자와 메서드를 호출할 수 있다.

프로토타입

· 약 5분
준열
프론트엔드 개발자
정의

자바스크립트는 프로토타입 기반 언어입니다. 클래스 기반 언어에서 '상속'을 사용한다면, 프로토타입 기반 언어는 어떤 객체를 원형으로 삼아 이를 복제(참조)하는 방식으로 상속과 비슷한 효과를 만들어냅니다.

프로토타입의 개념 이해

constructor, prototype, instance

새로운 인스턴스를 생성할 때, __proto__라는 프로퍼티가 자동으로 부여됩니다.

let instance = new Constructor();

prototype__proto__는 모두 객체입니다. prototype 객체 내부에는 인스턴스가 사용할 메서드를 저장합니다.

let Person = function (name) {
this.name = name;
};

// Person.prototype에 getName 메서드 추가
Person.prototype.getName = function () {
return this.name;
};

let suzi = new Person("Suzi");
suzi.__proto__.getName(); // undefined
Person.prototype === suzi.__proto__; // true

위 코드에서 undefined가 출력되는 이유는 메서드 호출 시 바로 앞 객체가 this가 되기 때문입니다. __proto__ 객체에는 name 프로퍼티가 없으므로 undefined가 반환됩니다.

__proto__는 생략 가능합니다. 이 덕분에 인스턴스에서 prototype의 메서드를 자신의 것처럼 사용할 수 있습니다.

// 아래 세 표현은 동일합니다
suzi.__proto__.getName
suzi(.__proto__).getName
suzi.getName // __proto__ 생략!

prototype 내부에 없는 메서드는 인스턴스가 직접 호출할 수 없습니다. 생성자 함수에서 직접 접근해야 합니다.

let arr = [1, 2];
arr.forEach(function () {}); // (O) Array.prototype에 있음
Array.isArray(arr); // (O) true - Array 생성자 함수의 스태틱 메서드
arr.isArray(); // (X) TypeError - prototype에 없음

constructor 프로퍼티

prototype 내부에는 constructor 프로퍼티가 있습니다. 이는 생성자 함수 자신을 참조하며, 인스턴스가 자신의 원형을 알 수 있는 수단이 됩니다.

let arr = [1, 2];
Array.prototype.constructor === Array; // true
arr.__proto__.constructor === Array; // true
arr.constructor === Array; // true

// constructor로 새 인스턴스 생성 가능
let arr2 = new arr.constructor(3, 4);
console.log(arr2); // [3, 4]
constructor 변경 시 주의사항

constructor는 변경 가능하지만, 변경해도 인스턴스의 실제 타입이 바뀌지는 않습니다.

let NewConstructor = function () {
console.log("this is new constructor!");
};

let obj = {};
obj.constructor = NewConstructor;
console.log(obj.constructor.name); // 'NewConstructor'
console.log(obj instanceof NewConstructor); // false!

위 예시에서 constructor를 변경해도 instanceof는 여전히 false를 반환합니다. 참조 대상만 변경되었을 뿐 실제 원형이나 데이터 타입은 변하지 않습니다.

동일한 대상을 가리키는 표현:

[Constructor][instance].__proto__.constructor[instance].constructor;
Object.getPrototypeOf([instance]).constructor[Constructor].prototype
.constructor;

동일한 객체에 접근하는 방법:

[Constructor].prototype[instance].__proto__[instance];
Object.getPrototypeOf([instance]);

프로토타입 체인

메서드 오버라이드

인스턴스가 prototype과 동일한 이름의 메서드를 가지면 메서드 오버라이드가 발생합니다.

let Person = function (name) {
this.name = name;
};

Person.prototype.getName = function () {
return this.name;
};

let iu = new Person("지금");

// 인스턴스에 같은 이름의 메서드 추가
iu.getName = function () {
return "바로 " + this.name;
};

console.log(iu.getName()); // '바로 지금'

원본 메서드가 제거되는 것이 아니라 그 위에 덮어씌워지는 구조입니다. 자바스크립트 엔진은 다음 순서로 메서드를 찾습니다:

  1. 인스턴스 자신의 프로퍼티 검색
  2. 없으면 __proto__ 검색

원본 prototype 메서드에 접근하려면 명시적으로 호출해야 합니다:

// call로 this를 iu로 바인딩
console.log(iu.__proto__.getName.call(iu)); // '지금'

프로토타입 체인

배열의 내부 구조를 보면 __proto__ 내부에 다시 __proto__가 있습니다. prototype 객체도 객체이기 때문입니다.

let arr = [1, 2];

// __proto__ 생략 가능
arr.push(3); // arr.__proto__.push(3)와 동일

// 체인을 따라 올라감
arr.__proto__.__proto__.hasOwnProperty(2); // true
// arr → Array.prototype → Object.prototype
용어 정리
  • 프로토타입 체인: __proto__가 연쇄적으로 이어진 구조
  • 프로토타입 체이닝: 체인을 따라가며 검색하는 과정

메서드 오버라이드와 프로토타입 체이닝 예시:

let arr = [1, 2];

// Array.prototype의 toString
Array.prototype.toString.call(arr); // '1,2'

// Object.prototype의 toString
Object.prototype.toString.call(arr); // '[object Array]'

// 기본적으로는 Array.prototype의 toString 사용
arr.toString(); // '1,2'

// 메서드 오버라이드
arr.toString = function () {
return this.join("_");
};
arr.toString(); // '1_2'

어떤 생성자 함수든 prototype은 객체이므로, Object.prototype이 언제나 프로토타입 체인의 최상단에 존재합니다.

객체 전용 메서드의 예외사항

객체에서만 사용할 메서드를 Object.prototype에 정의하면 모든 데이터 타입에서 접근할 수 있게 되는 문제가 발생합니다.

// 잘못된 예시: Object.prototype에 메서드 추가
Object.prototype.getEntries = function () {
let res = [];
for (let prop in this) {
if (this.hasOwnProperty(prop)) {
res.push([prop, this[prop]]);
}
}
return res;
};

let data = [
["object", { a: 1, b: 2, c: 3 }], // [["a",1], ["b",2], ["c",3]]
["number", 345], // []
["string", "abc"], // [["0","a"], ["1","b"], ["2","c"]]
["boolean", false], // []
["array", [1, 2, 3]], // [["0",1], ["1",2], ["2",3]]
];

data.forEach(function (datum) {
console.log(datum[1].getEntries());
});

의도는 객체에만 사용하는 것이었지만, 모든 데이터 타입이 프로토타입 체이닝을 통해 getEntries에 접근할 수 있습니다.

이러한 이유로 객체 전용 메서드는 Object스태틱 메서드로 부여됩니다.

// 올바른 방식: 스태틱 메서드
Object.freeze(instance); // O
instance.freeze(); // X

// 인스턴스를 인자로 직접 전달
Object.keys(obj);
Object.values(obj);
Object.entries(obj);

생성자 함수 Object와 인스턴스 사이에는 this 연결이 불가능하므로, 대상 인스턴스를 인자로 받는 방식으로 구현됩니다.

Object.prototype에는 모든 데이터 타입에서 사용 가능한 범용 메서드만 존재합니다:

  • toString
  • hasOwnProperty
  • valueOf
  • isPrototypeOf

Object.create(null)

예외적으로 Object.create(null)을 사용하면 Object.prototype에 접근할 수 없는 객체를 만들 수 있습니다.

// __proto__가 없는 객체 생성
let _proto = Object.create(null);
_proto.getValue = function (key) {
return this[key];
};

// _proto를 __proto__로 하는 객체 생성
let obj = Object.create(_proto);
obj.a = 1;
console.log(obj.getValue("a")); // 1
console.dir(obj);

obj를 출력하면 __proto__에 오직 getValue 메서드만 존재하며, 일반적인 내장 메서드나 프로퍼티는 보이지 않습니다.

장단점:

  • 장점: 내장 메서드가 제거되어 객체가 가벼워지고 성능 향상
  • 단점: 기본 기능 제약 (toString, hasOwnProperty 등 사용 불가)

다중 프로토타입 체인

자바스크립트 기본 데이터 타입의 프로토타입 체인은 1-2단계로 끝나지만, 사용자가 더 긴 체인을 만들 수 있습니다.

방법: 생성자 함수의 prototype이 다른 생성자 함수의 인스턴스를 바라보게 합니다.

// 유사배열객체를 반환하는 생성자 함수
let Grade = function () {
let args = Array.prototype.slice.call(arguments);
for (let i = 0; i < args.length; i++) {
this[i] = args[i];
}
this.length = args.length;
};

let g = new Grade(100, 80);

g는 배열 형태(인덱스와 length 프로퍼티)를 가졌지만, 배열 메서드는 사용할 수 없는 유사배열객체입니다.

배열 메서드를 사용 가능하게 만들기:

// Grade.prototype을 배열 인스턴스로 교체
Grade.prototype = [];

console.log(g); // Grade(2) [100, 80]

// 이제 배열 메서드 사용 가능
g.pop();
console.log(g); // Grade(1) [100]

g.push(90);
console.log(g); // Grade(2) [100, 90]

이제 g의 프로토타입 체인은 다음과 같습니다:

g 객체
→ Grade.prototype (배열 인스턴스)
→ Array.prototype
→ Object.prototype

g는 자신의 멤버, Grade.prototype, Array.prototype, Object.prototype의 모든 멤버에 접근할 수 있습니다.

정리

  • 생성자 함수를 new 연산자와 함께 호출하면 인스턴스에 __proto__ 프로퍼티가 자동 부여되며, 이는 생성자 함수의 prototype을 참조합니다.

  • __proto__는 생략 가능하여, 인스턴스는 prototype의 메서드를 자신의 것처럼 호출할 수 있습니다.

  • prototype의 constructor 프로퍼티는 생성자 함수 자신을 가리키며, 인스턴스가 자신의 생성자 함수를 알 수 있게 합니다.

  • 프로토타입 체인: __proto__가 연쇄적으로 이어진 것
    프로토타입 체이닝: 체인을 따라 검색하는 것

  • 객체 전용 메서드는 Object.prototype이 아닌 Object에 스태틱 메서드로 부여됩니다. 모든 데이터 타입이 Object.prototype을 상속받기 때문입니다.