티스토리 뷰
자바스크립트는 프로토타입 기반의 객체지향 언어다. 자바와는 다르게 클래스 기반이 아니다. 그런데 es6에서 클래스가 나오고 프로토타입의 동작원리를 잘 모르는 사람이 많아진 것 같다.(내 얘긴가??) 자바스크립트를 조금 깊게 물어보면 프로토타입과 실행 컨텍스트는 꼭 나오는 면접 질문중에 하나이기도 하다. 이 글을 보고 어디가서 '저 프로토타입 설명할 수 있어요' 라고 말하면 정말 좋겠다.
프로토타입이 뭔데??
프로토타입(prototype)은 원래의 형태 또는 전형적인 예, 기초 또는 표준이다. - 위키백과
이게 무슨말이지?? 어려운 말은 아니지만 프로토타입을 대강 알고있는 사람들도 위의 설명을 보고 프로토타입을 떠올리기는 쉽지 않다. 코딩에 대한 얘기는 아니지만 어느정도는 배경지식을 알 필요가 있다. 정말 자세히 설명하는 블로그가 있지만 간단하게 정리해보겠다. 이 글을 읽고 궁금해졌다면 아래에 참고 블로그를 정독하는 것을 추천한다.
클래스와 프로토타입의 배경
클래스
서양 철학에서는 실체가 있고 그 실체의 이데아에 존재하는 추상적인 것이 있다고 얘기한다. 의자로 예를 든다면 대학교 강의실에 있는 의자, 집에 있는 의자, 접이식 의자 등이 있고 본질적인 의자가 있는 것이다. 이 추상적인 의자(개체)의 속성이 동일하기 때문에 여러가지 의자가 chair의 그룹에 속한다고 얘기한다. 이 이론은 분류(classification)로 확장되었고 클래스 기반 객체지향언어의 본질이다. 이 언어에서는 유사한 객체들을 동일한 속성이 있는 클래스로 추상화한다.
간단하게 클래스로 표현하면 아래와 같을 것이다. (상속을 할 수도 있지만 일단은 넘어가자)
class Chari{}
const classChair = new Chair();
프로토타입
비트겐슈타인의 의미사용이론과 가족 유사성
비트겐슈타인은 분류이론을 반박한다.
공유 속성의 관점에서 정의하기 어려운 개념이 있다.
세계에 미리 내재되어서 대상과 언어를 완전히 규정하는 어떤 언어란 존재하지 않는다.
표현은 삶의 흐름 속에서만 의미를 갖는다
그리고 두 가지의 예시를 든다.
1. 의미사용이론
(벽돌이 필요할 때) : 벽돌을 달라
(벽돌로 보수해야 할 때) : 벽돌을 채우라
(벽돌이 떨어질 때) : 벽돌을 피해라
이를 바탕으로 자바스크립트의 실행 컨텍스트도 설명이 가능하다.
2. 가족 유사성
인간이 현실에서 실제로 대상을 분류할 때 속성(전통적인 분류에서의 기준)이 아닌 가족 유사성을 통해 분류하게 된다
위의 그림에서 얼굴을 보면 공통적인 속성이 존재하지 않는다. 하지만 이 그림을 보고 가족이라고 유추할 수 있다. 전통적인 분류와는 조금 다르다.
로시의 프로토타입
비르켄슈타인의 이론은 로시에 의해 프로토타입으로 정리된다. 로시는 한가지 실험을 했는데 여러 과일을 주고 속성을 적어보라고 했다. 그리고 공유하는 속성의 개수가 높을수록 가족 유사도가 높다고 판단했다.
새로 예를 들어보자. 참새는 가장 전형적인 새의 종류이다. 그리고 참새를 원형(prototype)으로 봤을 때 점차 떨어질 수록 일반적인 새와 거리가 멀어진다. 예를들면 펭귄이나 타조같이 말이다. 그리고 상황에 따라 펭귄은 새가 될수도 아닐수도 있다. 조류학자의 입장과 아이의 입장은 다르다. 조류학자가 펭귄을 새라고 칭했지만 사전을 찾아보지 않은 사람들은 펭귄이 새가 아니라고 생각할지도 모른다.(위의 그림에서는 참새대신 울새를 사용)
- 현실에 존재하는 것 중 가장 공통적이고 일반적인 것을 원형(prototype)으로 선택한다.
- 문맥(컨텍스트)에 따라 의미가 달라진다.
프로토타입 코드 예시
전통적인 방법의 프로토타입 체이닝
function Robin() {
this.legs = 2;
this.isFly = true;
}
Robin.prototype.sayFly = function () {
if (this.isFly) console.log('I can fly');
else console.log("I can't fly");
};
function Penguin() {
Robin.call(this);
this.isFly = false;
this.isSwim = true;
}
Penguin.prototype = Object.create(Robin.prototype);
Penguin.prototype.constructor = Penguin
const robin = new Robin();
robin.sayFly();
const penguin = new Penguin();
penguin.sayFly();
클래스의 상속과는 조금 다른게 객체 생성을 통해 확장한다. 그리고 인스턴스에서 생성자 함수의 프로토타입을 상속받아 사용할 수 있다.
프로토타입과 생성자
robin.constructor===Robin; // true
penguin.constructor===Penguin; // true
Robin.constructor===Function; // true
Penguin.constructor===Function; // true
robin.__proto__ === Robin.prototype; // true
Robin.prototype.__proto__===Function.prototype.__proto__ // true
Robin.prototype.__proto__===Object.prototype; // true
Function.prototype.__proto__===Object.prototype; // true
추가적으로 생성자와 프로토타입의 관계를 살펴보자.
모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다. 이 연결을 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이뤄진다.
아래의 그림을 보면서 같이 이해하면 더 잘될지도?? (출처 : 자바스크립트 딥 다이브)
새로운 방법
function Robin() {
this.legs = 2;
this.isFly = true;
}
Robin.prototype.sayFly = function () {
if (this.isFly) console.log('I can fly');
else console.log("I can't fly");
};
function Penguin() {
this.isSwim = true;
}
const robin = new Robin();
Penguin.prototype = robin;
Penguin.prototype.constructor = Penguin;
const penguin = new Penguin();
penguin.sayFly();
penguin.isFly = false;
penguin.sayFly();
Penguin.prototype.saySwim = function () {
if (this.isSwim) console.log('I can swim');
else console.log("I can't swim");
};
Penguin.prototype.sayFly = function () {
console.log('펭귄은 날 수 없어요');
};
penguin.saySwim();
penguin.sayFly();
penguin.sayFly = function () {
console.log('펭귄은 날 수 없어요...ㅠㅠ');
};
penguin.sayFly();
새의 프로토타입(원형)은 울새이고 다리가 두개고 날수있다. 여기서 로빈의 프로토타입에 sayFly라는 메서드를 추가한다. 그리고 펭귄이라는 생성자 함수를 만들고 펭귄의 프로토타입에 울새 인스턴스를 할당한다.(울새 함수가 아니라 인스턴스다. 프로토타입이론은 이미 존재하는 사물을 통해 범주화한다는 점에서 일치한다.)
펭귄 생성자함수에는 sayFly라는 메서드가 존재하지 않는다. 하지만 프로토타입 체인에 의해서 울새 인스턴스의 속성에 접근할 수 있다.
추가적으로 펭귄의 프로토타입에 이미 울새에 존재하는 속성을 추가하면 그 내용이 덮어씌워진다. 더 정확히 말한다면 프로토타입 체인이 일어나는 과정에서 속성을 찾는다면 더 상위 프로토타입으로 체이닝이 일어나지 않는다.
인스턴스의 메서드에 속성을 추가해도 마찬가지다.
lexical scope
위에서 얘기한 것으로 자바스크립트는 lexical scope라는 것을 알 수 있다.
생성자함수로 만든 인스턴스에서 속성에 접근하는 경우를 생각해보자. 인스턴스에 속성이 존재한다면 접근하고 존재하지 않는다면 프로토타입 체이닝을 통해 속성에 접근한다. 그곳에도 존재하지 않는다면 다시 프로토타입 체이닝으로 속성에 접근하게 되고 마지막에는 프로토타입이 null이 되고 이곳이 프로토타입의 종점이다.
그리고 다시 위로가서 의미사용이론에서의 벽돌 예시를 읽어보자.
프로토타입 뿐만 아니라 실행 컨텍스트에서도 마찬가지로 lexical scope다. 그리고 스코프 체이닝도 프로토타입 체이닝과 거의 동일하다. 지금까지 내용을 이해했다면 왜 자바스크립트가 렉시컬 스코프를 따르는지 이해할 수 있다.
실행 컨텍스트에 관한 내용은 다음 글에 적도록 하겠다.
참고 이미지
https://www.slideshare.net/vcmlab/cog5-lecppt-chapter08
참고 블로그
'자바스크립트 > 바닐라 자바스크립트' 카테고리의 다른 글
자바스크립트로 스크롤 이동하기 (0) | 2021.10.30 |
---|---|
바닐라 자바스크립트에서 무한 스크롤 구현하기 (0) | 2021.10.23 |
바닐라 자바스크립트로 프로젝트 만들기(옵저버패턴) (2) | 2021.10.20 |
바닐라 자바스크립트로 프로젝트 만들기(라우터) (2) | 2021.10.20 |
바닐라 자바스크립트로 프로젝트 만들기(컴포넌트) (0) | 2021.10.10 |