728x90

본 강의는 자바스크립트의 기초를 대충 안다고 가정하고 시작하는 조금 심도 깊은 포스팅이다.

완전 처음부터 배우고 싶다면 다른 블로그나 책의 글을 참조하기를 바란다.

특별한 추가 설명이 없다면 nodejs가 아닌 브라우저에서 사용하는 js를 의미한다.


참고:

javascript docs

ecmascript specification


객체를 만들 때는 이 때까지 prototype을 사용해서 만들었다.

사실 이 방식자체는 큰 문제는 없는데 기존방법과 너무 동떨어져있다보니 좋은 소리는 못들었다.

그래서 기존의 class 방식을 모방한 js만의 class가 ECMAScript6에서 추가되었다.

이번에는 그 방식을 한번 논해보도록 하자.


가령 기존의 방식을 보도록하자.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Book(owner) {
this.owner = owner;
}

Book.prototype.page = 150;
Book.prototype.info = function () {
console.log(`book title is ${this.owner}`);
};

var book = new Book('kukaro');

console.log(Book.prototype);
console.log(book);
book.info();
</script>
</body>

이 방식은 전 포스팅에서 사용하던 방식이다.

이러한 방식의 가장 큰 문제점은 이게 클래스인지 함수인지 도통 그냥봐선 모른다는 것이다.

그래서 추가된 방식이 바로 아래와 같은 class방식이다.

위의 코드와 아래의 코드는 동일하다고 보면 된다.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
class Book {
constructor(owner) {
this.owner = owner;
}

info() {
console.log(`book title is ${this.owner}`);
}
}

Book.prototype.page = 150;

var book = new Book('kukaro');

console.log(Book.prototype);
console.log(book);
book.info();
</script>
</body>
</html>

훨씬 코드가 간결해졌다.

둘의 결과는 동일하다.

그래도 두방식은 차이점이 있다. 그럼 변환 방식과 차이점에대해서 보도록하자.


function Book(owner) {
this.owner = owner;
}

기존의 이 함수가 함수이면서 생성자라고 했다.


constructor(owner) {
this.owner = owner;
}

그래서 클래스에선 아예 생성자속에 쏙 들어가버렸다.

생성자는 js에서 오버로딩이 되지 않으며 생성자는 단 하나이다.


Book.prototype.info = function () {
console.log(`book title is ${this.owner}`);
};
info() {
console.log(`book title is ${this.owner}`);
}

메소드는 둘의 차이점이 있다.

사실 기존에서 prototype에서는 메소드도 this에 붙혀서 prototype방식이 아닌 메소드를 만드는게 가능했다.

근데 굳이 그렇게 만들 이유가 없었기에 그렇게 쓰는사람은 없었다. 메모리 낭비이기도 하고.

그래서 class에서는 그 방식은 아예 불가능하다.

모든 메소드는 prototype에만 붙히는게 가능하다.

그리고 class방식에서는 function키워드를 굳이 사용하지 않는다. 없어도 클래스인걸 아니까.


Book.prototype.page = 150;

문제는 prototype에 붙히는 변수이다.

이는 js진영에서도 말이 많은데 prototype의 변수 선언은 기존에서는 static과 비슷한 의미로 사용했으므로 자주 쓰던 방식이였다.

그런데 과거 이 방식을 class에서는 거부한다. 그래서 class에서도 기존처럼 사용하고 싶다면 명시적으로 붙혀줘야한다.

불편하기 짝이 없다. 근데 대신 다른 해결책을 내놨다. 그 방식은 여러분이 기존에 자바에서 사용하는 방식과 동일하다.


<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
class Book {
constructor(owner) {
this.owner = owner;
}

info() {
console.log(`book title is ${this.owner}`);
}

static test() {
console.log('함수 테스트');
}
}

var book = new Book('kukaro');

Book.page = 150;

console.log(Book.page);
Book.test();
</script>
</body>
</html>

자바에서는 static메소드나 변수를 접근할 때 위와 같이 사용한다.

하지만 자바의 방식과 가장큰 차이점은 this로 접근할 수 없다는것이다. 물론 자바에서도 static변수나 메소드를 this로 접근하는걸 지양하지만.

그래서 쓸 때 조심히 사용해야한다.

그 외의 몇가지 특성은 아래와 같다.


1. class는 function과는 다르게 호이스팅되지 않는다.

2. new로만 생성자를 호출할 수 있다. 다른 방식은 모두 불가.

3. constructor메소드 없이 new호출 불가


그 외에도 여러가지 차이점이 있지만 아직까지는 여기까지만 논하도록하자.


상속


js에서는 객체를 만드는게 class나 prototype으로 만든다고 하였고 내부적으로 보았을 때 class는 prototype을 래핑한거에 불과하다고 하였다.

그러면 이제 우리는 상속에 대해서 알아볼 때 class관점과 prototype관점 모두 보는게 중요하다.

여기서는 역순으로 먼저 class에서 상속을 어떻게 구현하는지 보도록하자.


class fooc {
constructor(a) {
this.b = 10;
this.a = a;
}
}

class bazc extends fooc {
constructor() {
super(2);
this.c = 20;
}
}

다른 언어들과 똑같다.

extends를 사용해서 상속을 하고 부모의 생성자를 super로 호출해주면된다.


console.dir(bazc);
console.dir(bazc.constructor);

로그를 한번 찍어보자.


bazc의 prototype객체는 fooc인걸 확인할 수 있으며 해당 constructor는 함수였다는걸 알 수 있다.


이를 바탕으로 해서 거꾸로 prototype방식으로 상속을 class형식과 똑같이 구현하려면 아래처럼 해야한다.


function foo(a) {
this.b = 10;
this.a = a;
}

function baz() {
foo.call(this, 2);
this.c = 20;
}

baz.prototype = Object.create(foo.prototype);
baz.prototype.constructor = baz;

this바인드의 개념에 대해서는 이 포스팅을 참조하자.

foo함수를 먼저 만들고  baz는 foo를 this바인드를 써서 호출해준다. 이 결과로 baz에서 직접 호출한 c는 물론이거니와

a,b모두 생성이 완료된다.


문제는 이렇게 하면 baz는 foo는 아니라는 것이다.

즉 is관계가 성립하지 않는다. 성립하려면 foo를 prototype체이닝을 해줘야한다.

baz.prototype = new foo(100);

과거에는 이런방식으로 프로토타입체이닝을 해줬는데 이방식은 좀 문제가 있다.

큰 문제는 아닐수도 있지만 이 경우 baz의 경우 100이라는 섀도잉된 값이 하나 존재하게된다.


console.log(a.a);
delete a.a;
console.log(a.a);

이러한 코드가 존재할 경우 결과를 보자.


원래라면 값이 지워줘야하지만 실제로는 프로토타입의 값이 튀어나오기에 지워지지 않는다.

이는 솔직히 의도해서 할 때도 있지만 class기반방식의 결과와는 좀 달라진다.


baz.prototype = Object.create(foo.prototype);
baz.prototype.constructor = baz;

그래서 위처럼해준다.

Object.create로 생성하면 이름만 foo인 빈객체가 생성된다.

이는 우리가 바라는 바이다.

마지막으로 baz의 prototype의 constructor를 baz에 연결시켜주면된다.



+ Recent posts