자바스크립트를 공부하기전 저는 "프로토타입" 기반의 언어라는 말이 무척 낯설게 느껴졌어요.
보통 많은 언어들은 클래스를 기반으로 하지만
자바스크립트는 다른 방식을 택한 것이죠.
"왜 (WHY)" 자바스크립트는 다른언어와 다른 방식의 길을 택한것일까?
그 히스토리를 알게 된다면,
우리는 FE 프론트엔드의 대표적인 악명 높은 면접질문을 더 가깝게 이해할 수 있게 될거예요!
(렉시컬 환경, 호이스팅, this ...)
그전에 우선 클래스 방식의 객체지향 언어의 기원을 먼저 살펴볼 필요가 있어요.
일반적으로 영어권의 사고방식에는 이분법적 세계관이 기본적으로 존재해요!
예를들자면 대표적으로 "관사" 라는 표현이에요!
한국어에서는 찾기 어려운 표현으로, 관사를 이해한다면 우리는 서양의 철학을 조금 더 쉽게 이해할 수 있게 돼요.
영어에서는 이데아의 세계에 있는것을 지칭할때 "추상적인 존재" 를 표현하는 방법이 있는데,
이데아 : 현상 세계 밖의 세상
예를 들어 볼게요,
만약 내가 추상적인 컴퓨터를 지칭 한다 하면 "computer" 라고 얘기하는 방법이 있어요,
하지만 현실세계에 존재하는 바로 "그" 컴퓨터를 지칭한다면 반드시 관사가 붙게돼요.
"a computer" 혹은 "the computer" , "computers"...
자 이제 위에서 설명하는 세계관을 코드로 옮겨볼게요.
class Computer{
...
}
Chair myComputer = new Computer();
레퍼런스 타입이라고 불리는 Computer
은 이데아에 존재하는 추장적인 개념이에요.
즉, 코드에는 존재하지만 실제 메모리상에는 존재 하지 않는 것이죠.
그렇다면 이 Computer
이라는 추상적인 무언가를 현실에서 존재하게 하려면 어떻게 해야할까요?
바로 인스턴스 선언을 통해 추상적으로 존재하던 컴퓨터를 현실세계에서 구체화 해 줄 수 있게 돼요.
이러한 클래스 방식의 OOP의 기원은 서양철학의 자연스러운 흐름이라고 볼 수 있는 것이죠!
분류란, 개체의 속성이 동일한 경우 개체그룹이 같은 범주에 속한다.
또한, 범주는 정의와 구별의 합이다.
이 개념은 전통적인 클래스 기반 객체지향 프로그래밍의 아이디어-일반화와 정확히 일치해요.
속성은 클래스의 프로퍼티가 되고,
프로퍼티가 유사한 객체가 있다면 일반화 과정을 통해 클래스로 추상화 되는 것이지요.
지금까지 우리는 프로토타입을 이해하기 위해 서양철학을 시작으로 Class의 어원까지 많은 개념을 훑고 지나왔어요!
이제 프로토타입의 입장을 들어볼 차례 👨🏻🎤
가장 대표적으로 프로토타입은 분류(Classification)이론을 정면으로 반박하기 위하여 나온 이론으로,
이 개념의 근거로 들고 온 것은 바로 게임
이에요.
일반적으로 승,패가 나뉘는 게임이 아닌 ‘승리', ‘패배'가 없는
ring around a rosy
라는 게임을 가지고 오는데,
이 게임과 일반적인 게임 사이에서 공유 속성이 없다는 사실을 증명해요.
승리/패배 ? 없는 게임도 있음
숙련도 여부 ? 주사위 게임(행운 게임)은 없음
플레이어 존재여부 ? 플레이어가 전혀 없는 게임도 있음
이처럼 클래스 기반 언어 Java로 개발을 하다보면 이러한 본질적인 한계
를 자주 느낄 수 있어요.
속성으로 분류한다 한들 여러가지 예외사항들과 확장성이 고려되지 못해 뒤엎어지는 일도 많이 발생돼요.
즉, Class를 사용하여 완벽한 디자인이 나오는건 불가능이 아닐까 하는 생각을 들게 해요.
결국 클래스도 완벽하지 않다는 것을 의미하죠.
의미 사용 이론 & 가족 유사성은 사용에 의해 의지가 결정된다는 이론으로
단어의 쓰임새가 곧 의미가 된다는 것을 의미해요.
쓰이는 곳을 봐야 그 의미를 알 수 있다.
예를들어 누군가가 "벽돌" 이라 외쳤을때 나올 수 있는 의미는 여러가지가 있어요.
벽돌을 줘!
(여기에) 벽돌을 채워
벽돌을 피해!
이처럼 같은 말이지만 여러가지 의미로 해석 될 수 있는데요,
여기서 우리는 맥락(Context)의 중요도를 다시 한번 깨달을 수 있어요.
📌 프로토타입 기반 언어의 실행 컨텍스트 기본 개념
의미사용이론과 유사한 또 하나의 이론으로 가족 유사성이 있어요.
인간이 현실에서 실제로 대상을 분류할 때 속성이 아닌 가족유사성을 통해 분류하게 된다는 개념인데요,
가족은 모두 공유하는 공통 속성이 없고, 모든 가족 구성원에게 적용되는 공통된 특징(속성)도 있을 수 없어요.
하지만 우리는 전형적인 어떠한 특징들을 통해 '가족'으로 분류하는데요,
이 이론 또한 프로토타입 이론의 근거가 돼요.
1975년 Rosch는 한가지 실험을 해요.
점수가 높을 수록 '가족 유사성' 이 높다고 평가되는 것인데요,
전통적인 분류에서는 이를 모두 "과일" 이라고 분류하겠지만
프로토타입 이론에서는 사과와 오렌지가 가장 전형적인 무언가
라고 정의하게 되지요.
반면 코코넛은 그 중에서 가장 비전형적인 것으로 볼 수 있어요.
이를 통해 우리는 "인간은 등급이 매겨진 구조"를 가진다. 라고 주장해요.
우리는 사물을 분류할 때 자연스럽게 가장 유사성 높은 것부터 순서대로 등급을 매기게 돼요.
이렇게 분류했을때 가장 높은 등급의 녀석이 나오는데,
이것이 바로 "원형(Prototype)" 이다 라는 주장이에요.
즉, 객체는 '정의'로부터 분류되는것이 아닌 가장 좋은 보기(prototype exemplar)로부터
범주화 된다고 볼 수 있어요.
가장 좋은 본보기를 원형(prototype)
으로 선택한다.이러한 많은 이론을 바탕으로 "프로토타입 기반 객체지향 프로그래밍 언어" 가 구현될 수 있었어요.
대표적으로 Javascript의 모태 언어인 Self언어와 가족유사성을 완벽하게 구현한 Kevo가 있어요.
일반적으로 프로토타입 기반 OOP 언어의 특징은 다음과 같아요.
1. 개별 객체 수중에서 메소드와 변수
를 추가
2. 객체 생성은 일반적으로 복사를 통해 이루어짐
3. 확장 클래스가 아니라 위임 > 현재 객체가 메시지에 반응하지 못할 때 다른 객체로 메시지를 전달할 수 있게 하여 상속의 본질을 지원
4. 개별 객체 수중에서 객체를 수정하고 발전시키는 능력은 선험적 분류의 필요성을 줄이고 반복적인 프로그래밍 및 디자인 스타일을 장려
5. 프로토타입 프로그래밍은 일반적으로 분류하지 않고 유사성을 활용하도록 선택
6. 결과적으로 설계는 맥락에 의해 평가
지금까지 한 이야기가 완벽하게 이해 되셨다면 여러분은 이제 자바스크립트의 동작원리에 대해 암기하는 것이 아닌 이해를 하실 수 있게 될 거예요
분류
를 우선하지 않고, 생성된 객체 위주로 유사성을 정의해요
- 실행 컨텍스트, 스코프 체인이 여기서 파생
- 클로져, this, 호이스팅 등등. 이 모든 헬(?) 이 프로토타입의 ‘맥락’을 표현하기 위한 것
function 참새(){
this.날개갯수 = 2;
this.날수있나 = true;
}
const 참새1 = new 참새();
console.log("참새의 날개 갯수 : ", 참새1.날개갯수); // 2
function 닭(){
this.벼슬 = true;
}
닭.prototype = 참새1; // reference(오른쪽이 인스턴스인 점 주목)
const 닭1 = new 닭();
console.log("닭1 날개 : ", 닭1.날개갯수, ", 날수있나? ", 닭1.날수있나); // 2, true
닭1.날수있나 = false;
console.log("다시 물어본다. 닭1은 날 수 있나? :", 닭1.날수있나); // false
// 아래는 고전적인 방식의 프로토타입 연결
function 펭귄(){
참새.call(this); // copy properties
}
펭귄.prototype = Object.create(참새.prototype); // 프로토타입 연결
const 펭귄1 = new 펭귄();
console.log("펭귄1 날개 : ", 펭귄1.날개갯수, ", 날수있나? ", 펭귄1.날수있나); // 2, true
펭귄1.날수있나 = false;
console.log("다시 물어본다. 펭귄1은 날 수 있나? :", 펭귄1.날수있나); // false
위 코드를 요약하면 참새를 프로토타입으로 참조하는 닭이 있고
해당 닭은 프로토타입 체인에 의해 참새1의 속성을 갖지만 객체 확장을 통해
속성을 변경할 수 있게 돼요.
자바스크립트는 단어가 사용되는 근처 환경 "근처"를 어휘적 범위(Lexical Scope)로 정의해요.
그렇다면 도대체 왜!?
프로토타입 언어에는 실행 문맥
, 렉시컬 스코프
, 호이스팅
이 존재하는걸까요?
그 전에 자바스크립트의 this에 대하여 살펴볼 필요가 있어요.
자바스크립트에서의 this는 클래스 기반 객체지향 언어에서의 this와 완전히 다른 동작을 하지만 어느 유명한 서적이나 인터넷을 찾아봐도 정확하게 this의 의미를 설명하는 문서를 찾기 힘들어요.
하지만 '프로토타입'에 대한 이해를 한 뒤에는 명확하게 그 의미를 파악 할 수 있게 돼요!
아래는 잘못 알려져있는 this에 대한 오해들입니다.
- this 는 기본적으로 window 다 ( X )
- 이벤트 리스너에서 등록한 콜백의 this 는 내부에서 bind 등을 통해 바뀌기때문에 무엇인지 알 수 없다. ( X )
- this 는 외워야한다 ( X )
프로토타입과 클래스의 대표적 차이로
단어를 보는 방식이 전혀 다르다는점이 있어요.
미리 분류하고 정의한 클래스를 중요하게 여기는 방식과
받아들이는 주체와 문맥이 중요한 프로토타입의 방식
즉, 실행하는 '객체'가 중요하다는걸 의미해요.
때문에 클래스의 this 와 프로토타입의 this는 다르게 동작하게 되는것이지요.
프로토타입 언어에서는 this가 정의된 함수가 어떻게 Invoke 되었는가에 초첨을 맞춰요, 정확히는 받아들이는 대상의 컨텍스트
를 가르키게 되는것이죠.
var someValue = 'hello';
function outerFunc() {
console.log(this.someValue);
// 첫번째 : world, 두번째 : hello
this.innerFunc();
}
const obj = {
someValue : 'world',
outerFunc,
innerFunc : function() {
console.log("innerFunc's this : ", this); // 첫번째 : obj, 두번째 : ERROR
}
}
obj.outerFunc(); // 첫번째
outerFunc(); // 두번째
위 예시를 이해해볼게요.
// 첫번째
라는 주석이 달린 obj.outFunc()을 만나면
해당 함수는 obj 내부에 있는 outerFunc을 실행할 거예요.
여기서 실행문맥상 this는 Obj를 가리키겠죠!
실행중 outerFunc내부에서 this.innerFunc() 을 만나게 되고
여기서 this는 위에서 정의된 것 처럼 obj가 될테니
Obj의 innerFunc 이 실행되면서 this
인 obj가 출력됨을 확인 할 수 있어요!
// 두번째
라는 주석이 달린 outerFunc() 을 실행시켜 볼게요!
outerFunc자체는 global에 선언 되어있기 때문에 함수까지는 잘 도달 할 수 있어요.
여기서 this
는 global을 가르켜요!
하지만 내부에 있는 this.InnerFunc() 을 만나면 글로벌에 선언된 InnerFunc함수를 찾을 수 없어 에러가 나게 돼요!
이처럼 자바스크립트는 정해진 룰에 맞춰 실행되는것이 아닌
각각의 문맥에 맞게 "발화되는 객체" 중심으로 흘러가기 때문에
다른 언어에서는 낯선 개념들이 많이 등장하게 되는것이랍니다!
레퍼런스 글을 잘 정리해주셨네요! 잘 읽고 갑니다 :)