Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[박승훈] 챕터 5: 최신 자바스크립트 문법과 기능 #23

Merged
merged 2 commits into from
Oct 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 356 additions & 0 deletions 챕터_5/박승훈.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
## 모듈형 자바스크립트

### 모듈의 특징

- 모듈은 다른 모듈을 가져올 수 있다.
- 따라서 애플리케이션이 여러 개의 중첩된 모듈로 구성된다.


### 모듈의 장점

- 모듈은 잘게 분리되어 느슨하게 결합된다.
- 느슨한 결합은 의존성을 낮춰 유지보수에 용이하다.
- 한 모듈의 변경이 다른 부분에 어떤 영향을 미치는지 예측하기 쉽다.


### 자바스크립트와 모듈의 역사

- ES5 이전 : 모듈 지원 없음
- ES5 ~ ES6 : AMD, CommonJS(.cjs)
- ES6 이후 : moduleJS(.mjs)


## 모듈의 가져오기와 내보내기

> 가져오기는 import, 내보내기는 export

### 일반 구조

```js
// staff.mjs (내보내는 모듈)
export const baker = {
bake(item): { ... }
}


// cakeFactory.mjs (가져오는 모듈)
import baker from "/modules/staff.mjs";

export const oven = {
makeCupCake(toppings) {
baker.bake(...); // 모듈 사용
}
}
```


### 내보낼 모듈을 객체로 묶어 내보내기

```js
// staff.mjs (내보내는 모듈)
const baker = { ... };
const pastryChef = { ... };
const barista = { ... };

export { baker, pastryChef, barista };
```

> 나의 생각

react의 custom hook의 일반적인 모양과 비슷한 패턴이라고 느껴졌어요. 다만, JS의 import/export를 사용하는 것이 아니라 return으로 함수의 반환값을 사용한다는 것이 다른 것 같네요.


### `<script>` 태그에서 모듈 명시하기

```html
<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.mjs"></script>
```

- nomodule
- 브라우저에 모듈이 아님을 알려주는 속성
- 모듈 문법을 사용하지 않는 대체 스크립트에 유용
- 모듈을 지원하지 않는 구버전 브라우저에서도 제대로 기능하도록 한다.


### 폴리필과 트랜스파일

> script 코드의 브라우저 호환성에서 언급된 폴리필과 트랜스파일에 대해 더 알아보았어요.

|특징|폴리필(polyfill)|트랜스파일(transpile)|
|:---:|---|---|
|의미|기능을 지원하지 않는 브라우저에 기능을 제공하는 코드|최신 문법을 구버전 문법으로 변환하는 코드|
|특징|기능을 제공하기 위해 추가적으로 주입|문법 자체를 변환|

참고자료: [Polyfills and transpilers](https://javascript.info/polyfills)


## 정적으로 모듈 가져오기

> 정적 가져오기(static import)는 모듈을 가져오는 시점에 모듈을 로드한다.

- 정적 가져오기는 **메인 코드를 실행하기 전에 먼저 모듈을 다운로드하고 실행**해야 한다.
- 초기 페이지 로드 시 많은 코드를 미리 로드해야 하므로 성능에 문제가 생길 수도 있다.

```js
import { cakeFactory } from "/modules/cakeFactory.mjs";
// 미리 로드된 정적 가져오기

cakeFactory.oven.makeCupCake("chocolate");
cakeFactory.oven.makeMuffin("large");
```


## 동적으로 모듈 가져오기

> 동적 가져오기(dynamic import)는 지연 로딩(lazy loading)과 관련된다.

- 동적 가져오기는 **함수와 비슷한 새로운 형태의 가져오기**
- `import(url)`는 요청된 모듈의 네임스페이스 객체에 대한 **프로미스(Promise) 객체를 반환**한다.
- 이 Promise 객체는 모듈 자체와 모든 모듈 의존성을 가져온 후, 인스턴스화하고 평가한 뒤 만들어진다.
- **지연 로딩** : 모듈이 사용될 때만 다운로드되고 실행된다. (code splitting, 성능 최적화)

```js
const modulePath = "/modules/cakeFactory.mjs";

import(modulePath)
.then((module) => {
// 동적으로 가져온 모듈 사용
module.oven.makeCupCake("chocolate");
module.oven.makeMuffin("large");
})
.catch((err) => {
console.error(err);
});
```

동적 가져오기는 await와 함께 사용할 수 있다.

```js
const modulePath = "/modules/cakeFactory.mjs";

try {
const module = await import(modulePath);
module.oven.makeCupCake("chocolate");
module.oven.makeMuffin("large");
} catch (err) {
console.error(err);
}
```


### 화면에 보이면 가져오기

- 화면에 보여질 때 가져오면 성능 최적화에 도움이 된다.
- **Intersection Observer API**를 사용하여 화면에 보이면 가져오기를 구현할 수 있다.

```js
const modulePath = "/modules/cakeFactory.mjs";

const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
import(modulePath)
.then((module) => {
module.oven.makeCupCake("chocolate");
module.oven.makeMuffin("large");
})
.catch((err) => {
console.error(err);
});
}
});
});
```

> 나의 경험

성능 최적화를 위해 dynamic import를 사용해본 경험이 있어요. 그런데 신규 프로젝트 개발 초기여서 path가 계속 변하는데 dynamic import path는 자동으로 변경되지 않아서 수동으로 path를 변경해주어야 했어요. 이 부분이 조금 불편했던 것 같아요. 하지만 code splitting 학습 할 때 초기 화면에서 필요한 모듈만을 분리해서 가져오는 것의 이점이 드라마틱하다는 것을 알고 있었기 때문에 최대한 사용을 유지하려고 했어요. 모든 파일을 그렇게 할 필요는 없지만 유지보수에 유리한 선을 잘 정해서 적용해보면 좋을 것 같아요.


## 서버에서 모듈 사용하기

- Node는 type이 moduledlfkaus `.mjs`와 `.js`로 끝나는 파일을 JS 모듈로 취급한다.

```json
{
"name": "js-modules",
"version": "1.0.0",
...
"type": "module",
...
}
```


### 모듈 사용의 이점 총정리

- **한 번만 실행**
- 기존 스크립트 : DOM에 추가될 때마다 실행
- 모듈 스크립트 : 한 번만 실행
- 의존성 트리의 가장 내부에 위치한 모듈이 먼저 평가/실행

- **자동으로 지연 로드**
- 일반 script : 즉시 로드되지 않기 위해 defer나 async 속성 사용
- 모듈 스크립트 : 자동으로 지연 로드

- **사용하지 않는 코드 제거(tree shaking)**
- 일반 : 사용하지 않는 코드 수동 제거
- 모듈 : webpack, rollup 등의 번들러가 사용하지 않는 코드 제거

<br />

> 나의 생각

생각보다 모듈 사용의 이점이 대단함을 알게 되었어요. 자동 lazy loading이나 한 번만 실행된다는 점은 새로운 것 같아요. 다만 vanilla js를 사용하는 경우보다는 react 기반 프로젝트 + 번들러 위에서 더 많은 개발이 이어지는만큼 차이를 알 수 있으면 좋겠네요.


## 클래스

> 자바스크립트 클래스는 ES2015+에서 추가된 새로운 객체 생성 방식


### constructor, get, set

> 객체를 생성하고, 속성을 읽고 쓸 수 있다.

```js
class Cake {
// 생성자 안에서 변수를 정의한다.
constructor( name, toppings, price, cakeSize) {
this.name = name;
this.toppings = toppings;
this.price = price;
this.cakeSize = cakeSize;
}

addToppings(topping) {
this.toppings.push(topping);
}

// ES2015+ 부터는 모든 것을 함수로 만드는 걸 피하고자 한다.
// 새로운 식별자인 get과 set을 사용한다.

// getter : 속성을 읽을 때 사용
get allToppings() {
return this.toppings;
}

get qualifiesForDiscount() {
return this.price > 5;
}

// setter : 속성을 쓸 때 사용
set size(size) {
if (size < 0) {
throw new Error("Size must be greater than 0");
}
this.cakeSize = size;
}
}

// 사용
const cake = new Cake("chocolate", ["chocolate chips"], 5, "large");
```

### extends

> **extends** 키워드를 사용하여 상속을 구현한다.

```js
class BirthdayCake extends Cake {
surprise() {
console.log("Happy Birthday!");
}
}

const birthdayCake = new BirthdayCake("chocolate", ["chocolate chips"], 5, "large");
birthdayCake.surprise();
```


### super

> **super** 키워드는 부모 클래스의 생성자를 호출한다.

자기 상속(self-inheritance) 패턴 사용에 유용

```js
class Cookie {
constructor(flavor) {
this.flavor = flavor;
}

showTitle() {
console.log(`The flavor of this cookie is ${this.flavor}.`);
}
}

class FavoriteCookie extends Cookie {
showTitle() {
super.showTitle();
console.log(`${this.flavor} is amazing.`);
}
}

const myCookie = new FavoriteCookie('chocolate');
myCookie.showTitle();
// The flavor of this cookie is chocolate.
// chocolate is amazing.
```

### private(#)

> **#** 을 사용하여 비공개 클래스 멤버를 정의할 수 있다.

- 클래스 멤버(변수, 메서드)는 기본적으로 공개 상태
- 공개 클래스 멤버는 다른 클래스도 사용 가능
- #(해시)를 앞에 붙여 비공개 멤버로 변경 가능
- **비공개 멤버는 선언된 클래스 내부에서만 사용 가능**

```js
class CookieWithPrivateField {
// 비공개 필드Z
#privateField = "I'm private";
}

class CookieWithPrivateMethod {
// 비공개 메서드
#privateMethod() {
console.log("I'm private");
}
}
```


### static

> **static** 키워드를 사용하여 정적 메서드를 정의할 수 있다.

- 정적 메서드는 클래스를 초기화하지 않고도 사용 가능
- 주로 어떠한 설정이나 캐시 데이터를 보관하기 위해 사용

```js
class Cookie {
constructor(flavor) {
this.flavor = flavor;
}

static brandName = "Best Bakes";
static discountPercentage = 5;

static getDiscountedPrice(price) {
return price - (price * this.discountPercentage) / 100;
}
}

console.log(Cookie.brandName); // Best Bakes
console.log(Cookie.getDiscountedPrice(100)); // 95
```


### 나의 생각

저는 react에서 클래스형 컴포넌트가 아닌 함수형 컴포넌트부터 개발을 시작해서 객체지향 방식에 대해 잘 모르는데, 이번 기회에 작게나마 학습할 수 있어서 좋았어요. 아무리 요즘에는 함수형 패더라임을 사용한다고 하더라도 클래스 구조를 사용하는 경우가 종종 있었어요. 예를 들면 에러 바운더리를 라이브러리를 사용하지 않고 코드로 직접 작성하는 레퍼런스를 참고해 구현할 때가 있었네요. 이럴 경우를 대비해 클래스 문법을 알아두면 좋을 것 같아요.

저는 원래 기술 도서를 볼 때 예문을 그대로 옮겨적지 않으려 하는데, 클래스 문법은 예시까지 같이 있어야 나중에도 이해가 될 것 같아 자세히 정리하게 되었어요. 정리하고 나니 각 기능의 역할이 무엇인지 조금이나마 알 수 있어 좋았네요.
Loading