0%

201216_TIL(클래스, 화살표 함수)

오늘 배운 것

클래스

클래스를 new 연산자 없이 호출하면 에러가 발행한다. → [[Call]]이 없으며 [[Constructor]]만 존재한다.

클래스 내부에는 메서드 축약 표현만 들어갈 수 있다.

클래스 정의

1
2
3
4
5
// 익명 클래스 표현식
const Person = class {};

// 기명 클래스 표현식
const Person = class MyClass {};

일반적이지는 않지만 함수와 마찬가지로 표현식으로 클래스를 정의할 수도 있다.

클래스 몸체 안에는 constructor, 프로토타입 메서드, 정적 메서드만 올 수 있다.

클래스 몸체의 constructor는 prototype의 constructor와는 다르다.

클래스 몸체의 constructor는 함수 객체의 몸체로 들어가며 없어진다.

클래스 필드 정의 제안

클래스 필드는 아래와 같다.

1
2
3
4
5
6
7
8
class Foo {
a = 1;
// constructor() {
// this.a = 1;
// }
}

console.log(new Foo()); // Foo { a: 1 }

클래스 필드는 아래와 같이 인수 전달이 필요할 때 불가능하다.

1
2
3
4
5
6
7
8
class Foo {
a = n; //??
// constructor(n) {
// this.a = n;
// }
}

console.log(new Foo());

클래스 필드에 함수를 할당하는 경우, 이 함수는 프로토타입 메서드가 아닌 인스턴스 메서드가 된며 상위 스코프는 constructor 내부이다.

1
2
3
4
5
6
7
8
9
class Foo {
f = function () {};

constructor(n) {
this.a = n;
}
}

console.log(new Foo(100)); // Foo { a: 100 }

클래스 필드는 고정값을 가지고 있을 때는 의미가 있지만 외부에서 인수를 받아 할당할 때는 constructor() 사용을 해야한다.

private 필드 정의 제안

생김새가 못 생겼지만 private 가능하다. 또한, 아직까지 유일한 private 정의 방법이다.

1
2
3
4
5
6
7
8
9
class Person {
#name = "";
constructor(name) {
this.#name = name;
}
}

const me = new Person("Lee");
console.log(me.name); // undefined

private 키워드를 안 쓴 것에는 분명 이유가 있겠지만 생김새가 아쉽다.

super 키워드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 수퍼클래스
class Base {
constructor(a, b) {
this.a = a;
this.b = b;
}
}

// 서브클래스
class Derived extends Base {
// 다음과 같이 암묵적으로 constructor가 정의된다.
// constructor(...args) { super(...args); }
}

const derived = new Derived(1, 2);
console.log(derived); // Derived {a: 1, b: 2}

수퍼클래스에서 추가한 프로퍼티와 서브클래스에서 추가한 프로퍼티를 갖는 인스턴스를 생성한다면 서브클래스의 constructor를 생략할 수 없다. 이때 new 연산자와 함께 서브클래스를 호출하면서 전달한 인수 중에서 수퍼클래스의 constructor에 전달할 필요가 있는 인수는 서브클래스의 constructor에서 호출하는 super를 통해 전달한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 수퍼클래스
class Base {
constructor(a, b) {
// ④
this.a = a;
this.b = b;
}
}

// 서브클래스
class Derived extends Base {
constructor(a, b, c) {
// ②
super(a, b); // ③
this.c = c;
}
}

const derived = new Derived(1, 2, 3); // ①
console.log(derived); // Derived {a: 1, b: 2, c: 3}
  1. 서브클래스에서 constructor를 생략하지 않는 경우 서브클래스의 constructor에서는 반드시 super를 호출해야 한다.

  2. 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없다.

  3. super는 반드시 서브클래스의 constructor에서만 호출한다. 서브클래스가 아닌 클래스의 constructor나 함수에서 super를 호출하면 에러가 발생한다. (객체 리터럴과 클래스에서만 가능)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Base {
    constructor() {
    super(); // SyntaxError: 'super' keyword unexpected here
    }
    }

    function Foo() {
    super(); // SyntaxError: 'super' keyword unexpected here
    }

상속 클래스의 인스턴스 생성 과정

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 수퍼클래스
class Rectangle {
constructor(width, height) {
// 4. 여기서 this의 빈객체 생성 및 바인딩, (2, 4)할당, 암묵적 return
// this = {};
// console.log(this); // ColorRectangle {} -> new 키워드로 호출한 객체 이름으로 찍힘.
this.width = width;
this.height = height;
// console.log(this); // ColorRectangle {width: 2, height: 4}
// return this;
}

getArea() {
return this.width * this.height;
}

toString() {
return `width = ${this.width}, height = ${this.height}`;
}
}

// 서브클래스
class ColorRectangle extends Rectangle {
// 2. 호출 뒤 이 위치부터 시작
constructor(width, height, color) {
// 3. super 호출
super(width, height);
// 5. super가 종료되면서 반환 객체를 아래 this에 할당 후 color 값 할당
// this = super(width, height); 같은 느낌, 그렇기 때문에 super가 서브클래스의 this보다 앞에 와야한다.
// console.log(this); // ColorRectangle {width: 2, height: 4}
this.color = color;
// console.log(this); // ColorRectangle {width: 2, height: 4, color: "red"}

// 6. 암묵적 this 반환
}

// 메서드 오버라이딩
toString() {
return super.toString() + `, color = ${this.color}`;
}
}

// 1. 인수를 가지고 ColorRectangle의 constructor 호출
const colorRectangle = new ColorRectangle(2, 4, "red");
console.log(colorRectangle); // ColorRectangle {width: 2, height: 4, color: "red"}

// 상속을 통해 getArea 메서드를 호출
console.log(colorRectangle.getArea()); // 8
// 오버라이딩된 toString 메서드를 호출
console.log(colorRectangle.toString()); // width = 2, height = 4, color = red

Q. 클래스 상속이 깊어질 수록 매개변수가 많아지는게 필연적일 것 같은데 그럴 경우 대안 대책이 있는가?

A. 매개변수 많아지는게 필연적은 아니지만 만약에 그렇다면 객체 리터럴로 지정하면 될 것 같다.

ES6 함수의 추가 기능

화살표 함수

1
2
3
4
5
6
7
// ES5
[1, 2, 3].map(function (v) {
return v * 2;
});

// ES6
[1, 2, 3].map((v) => v * 2); // -> [ 2, 4, 6 ]

화살표 함수는 일반 함수 리터럴과 다르게 프로토타입 등을 만들지 않기 때문에 가볍고 가독성 또한 좋다.

화살표 함수와 일반 함수의 차이

  1. 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor다.

  2. 중복된 매개변수 이름을 선언할 수 없다.

  3. 화살표 함수는 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않는다.

    위의 것을 갖지 않는다는 것은 렉시컬 환경에 위의 것이 없다는 것이다. → 상위 스코프에서 찾아야한다. → 생성자 함수, 메서드로 상용하지말고 일반적인 함수로 사용하자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Prefixer {
    constructor(prefix) {
    this.prefix = prefix;
    }

    add(arr) {
    console.log(this); // Prefixer { prefix: '-webkit-' }

    return arr.map((item) => this.prefix + item); //여기서 this는 화살표 함수의 상위 스코프(add 메서드)의 this
    }
    }

    const prefixer = new Prefixer("-webkit-");
    console.log(prefixer.add(["transition", "user-select"]));
    // ['-webkit-transition', '-webkit-user-select']

Nyong’s GitHub