해당 포스트는 자바스크립트 완벽 가이드라는 교재로 스터디를 하면서 9장을 정리한 포스트입니다.
주관적으로 해석한 내용이 들어가 있어서 잘못된 내용이 포함될 수 있습니다.
또한 교재의 모든 내용을 정리하지 않고 주관적인 판단에 의해 필요한 내용만 작성했습니다.
🔨 객체 생성 방법
0️⃣ 팩토리 함수
팩토리 함수란 객체를 반환하는 함수를 의미합니다.
JavaScript
에서는 쉽게 객체를 만들 수 있기 때문에 그렇게 의미있는 함수는 아닌 것 같지만, 아래와 같이 특별한 객체를 만드는 함수라면 의미 있는 함수인 것 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function range(from, to) {
// 프로토 타입을 직접 정한 객체 생성
let r = Object.create(range.methods);
r.from = from;
r.to = to;
return r;
}
range.methods = {
includes(v) {
return this.from <= v && this.to > v;
},
*[Symbol.iterator]() {
for (let index = this.from; index < this.to; index++) yield index;
},
toString() {
return `(${this.from}...${this.to})`;
},
};
const r = range(1, 3);
console.log(r.toString());
console.log(...r);
1️⃣ 생성자 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Range(from, to) {
this.from = from;
this.to = to;
}
Range.prototype = {
includes(v) {
return this.from <= v && this.to > v;
},
*[Symbol.iterator]() {
for (let index = this.from; index < this.to; index++) yield index;
},
toString() {
return `(${this.from}...${this.to})`;
},
};
const r = new Range(1, 3);
console.log(r.toString());
console.log(...r);
2️⃣ 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Range {
constructor(from, to) {
this.from = from;
this.to = to;
}
includes(v) {
return this.from <= v && this.to > v;
}
*[Symbol.iterator]() {
for (let index = this.from; index < this.to; index++) yield index;
}
toString() {
return `(${this.from}...${this.to})`;
}
}
const r = new Range(1, 3);
console.log(r.toString());
console.log(...r);
🧾 객체 지향적 클래스 구현 예시
몇 가지 주요 키워드를
TypeScript
에서만 지원해주기 때문에TypeScript
를 이용한 예시입니다.
- 주요 키워드
- 가상 클래스/메서드 (
abstruct
) : 클래스는 인스턴스 생성 불가능 / 메서드는 자식에서 반드시 구현 - 정적 변수/메서드 (
static
) : 클래스 자체가 갖는 값 ( 즉, 속성을 갖는 값 ) - 상속 : 상위 요소에 선언된 변수/메서드를 그대로 사용
- 접근 제한자 (
private
,protected
,public
) : 접근의 범위 결정 super
: 상위 클래스getter
와setter
: 간접적으로 접근overriding
: 하위 요소에서 덮어씌우는 방식_
를 접두사로 붙인 변수는 직접적으로 호출하지 않는 게 관습
- 가상 클래스/메서드 (
0️⃣ Animal 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// (1) 가상 클래스
abstract class Animal {
// (4) 접근 제한자: protected
protected age: number;
constructor(age: number) {
this.age = age;
}
// (1) 가상 메서드
public abstract move(): void;
// (2) 정적 메서드: 동물이라면 누구나 갖는 동작이라 정적 메서드로 만듦
public static breathe() {
console.log("호흡");
}
}
1️⃣ Dog 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ( 상속 )
abstract class Dog extends Animal {
// (4) 접근 제한자: private ( with "_" )
private _color: string;
constructor(age: number, color: string) {
// (5) super: 부모 생성자(constructor) 호출
super(age);
this._color = color;
}
// 강아지라면 갖는 동작
public bark() {
console.log("멍멍");
}
public eat() {
console.log("먹음");
}
// (6) getter/setter
protected get color() {
return this._color;
}
protected set color(color: string) {
if (!(color === "red" || color === "green" || color === "blue")) {
throw new Error("red | green | blue만 사용할 수 있습니다.");
}
this._color = color;
}
}
2️⃣ Maltese 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Maltese extends Dog {
private name: string;
constructor(age: number, color: string, name: string) {
// (5) super: 부모 생성자(constructor) 호출
super(age, color);
this.name = name;
}
// (1) 가상 메서드는 자식이 반드시 구현해야 함
public move() {
console.log(`${this.name}의 움직임`);
}
// (7): overriding 말티즈 만의 동작
public eat() {
console.log("이름이 " + this.name + "인 말티즈가");
// (5) super: 상위 메서드 호출
super.eat();
}
public intro() {
console.log(`나이: ${this.age}
색상: ${this.color}
이름: ${this.name}`);
}
}
3️⃣ 사용 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// (2) "호흡"
Maltese.breathe();
const m = new Maltese(2, "red", "강아지");
// 'name' 속성은 private이며 'Maltese' 클래스 내에서만 액세스할 수 있습니다.
// console.log(m.name);
// "이름이 강아지인 말티즈가"
// "먹음"
m.eat();
// "멍멍"
m.bark();
// "강아지의 움직임"
m.move();
// 나이: 2
// 색상: red
// 이름: 강아지
m.intro();
/**
* ** 결과 **
*
* 호흡
* 이름이 강아지인 말티즈가
* 먹음
* 멍멍
* 강아지의 움직임
* 나이: 2
* 색상: red
* 이름: 강아지
*/
🃏 알아두면 쓸모있는 잡다한 클래스 지식
0️⃣ instanceof와 isPrototypeOf()
교재에서는 instanceof
가 확실하게 인스턴스인지 파악하지 못하는 예외의 경우를 보여주고 isPrototypeOf()
를 사용하는 방법에 대해서 알려줍니다.
근데 실제로 이런 경우가 발생하려면 prototype
을 직접적으로 수정하거나 팩토리 함수를 사용해서 객체를 만들어내야 합니다.
근데 두 가지 경우를 사용해본 경험이 없기도 하고, 사용하는 코드를 본적도 없기 때문에 “이렇게까지 해야하나?”라는 생각이 들어서 간단하게 instanceof
를 유용하게 사용해본 경우에 대해서만 정리해보겠습니다.
TypeScript
를 사용하다보면 변수의 타입을 좁혀줘야 하는 경우가 종종 발생합니다.
React
에서도 e.target
혹은 ref.current
같은 타입을 좁혀줘야 하는 경우도 많습니다.
그런 경우에 아래처럼 사용해서 타입 체커에게 타입을 확정시켜주는데 유용하게 사용합니다.
1
2
3
4
5
const $button = document.querySelector(".toggle-btn");
if($button instanceof HTMLButton) {
// 블록 내부에서는 "$button"이 버튼 타입임을 확정
}
1️⃣ 몽키패치
donggov - 몽키패치란를 참고했습니다.
몽키패치란 원래 소스코드를 변경하지 않고 실행 시 코드 기본 동작을 추가, 변경 또는 억제하는 기술을 의미합니다.
많이 오래된 브라우저에서는 String.prototype.startsWith()
를 지원하지 않을 수 있습니다.
하지만 저희는 많이 오래된 브라우저는 고려하지 않고 최신 문법을 이용해서 개발을 합니다.
그렇다면 startsWith()
를 지원하지 않는 브라우저가 제가 만든 웹사이트를 들어오면 에러가 발생할 것입니다.
그런 경우에 런타임에 코드의 동작을 추가하는 방법을 사용합니다.
이런 기술을 몽키패치라고 하고 babel
에서는 polyfill
을 통해 구형 브라우저의 접근을 도와줍니다.
1
2
3
4
5
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (s) {
return this.indexOf(s) === 0;
};
}
2️⃣ 클래스와 생성자 함수의 차이
- 접근 제한자 (
public
,protected
,private
) - 가상 키워드 사용 가능 여부 (
abstract
) - 정적 키워드 사용 가능 여부 (
static
) - 나열 여부
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CChampion {
constructor(name, speed) {
this.name = name;
this.speed = speed;
}
attack() {
console.log(this.name + ": 공격");
}
}
const FChampion = function (name, speed) {
this.name = name;
this.speed = speed;
};
FChampion.prototype.attack = function () {
console.log(this.name + ": 공격");
};
const c1 = new CChampion("Aatrox", 345);
const c2 = new FChampion("Puppy", 350);
for (const key in c1) {
console.log(key); // name speed
}
for (const key in c2) {
console.log(key); // name speed attack
}
3️⃣ 싱글톤 패턴
클래스의 인스턴스를 단 하나만 만드는 방법을 의미합니다.
( 자세한 내용은 싱글톤 참고 )
📮 레퍼런스
- « 자바스크립트 완벽 가이드 9장 » ( 데이비드 플래너건 지음, 한성용 옮김, 인사이트, 2022 )
- donggov - 몽키패치란
- 1-blue - polyfill
- 1-blue - 싱글톤