Prototype - 1. Prototype 디자인 이론

 자바스크립트는 프로토타입 기반의 언어다. 클래스 문법이 생기고 (적어도 내가 아는)많은 개발자들이 프로토타입보다는 클래스로 객체 지향 프로그래밍을 하고 있지만, 이 프로토타입이라는 것에 대해 조금은 알 필요가 있다. 왜냐하면 자바스크립트의 클래스는 프로토타입으로 구현되어 있고 프로토타입을 어느 정도 알고 있어야, 자바스크립트의 난해했던 부분들(호이스팅, this ...)을 암기가 아닌 이해로 받아들일 수 있기 때문이다. 프로토타입과 관련된 블로그, 위키 등을 참고하여 나름대로 이해한 내용을 정리하고자 한다.

 순서는 다음과 같다.

  1. Prototype 디자인 이론
  • 비트겐슈타인 : 용도의미론, 가족유사성
  • 로쉬 : 프로토타입 이론(원형의미론)
  • prototype 이론으로 javascript 이해하기
    • prototype으로 객체 지향 프로그래밍 해보기
    • 호이스팅
    • this
  1. JavaScript에서의 Prototype
  • prototype property
  • prototype object
  • prototype link
  1. JavaScript에서의 Class
  • prototype으로 class 구현해보기

Prototype 디자인 이론

 해당 파트에서는 프로토타입 이론에 대해 간략하게 다룰 예정이다. 프로토타입 이론은 심리학자인 엘리노어 로쉬가 제시한 인지심리학 이론이다. 조금은 뜬금없을 수 있지만, 인지심리학은 프로그래밍에 있어서 나름 중요한 부분을 차지한다. 어휘(lexical)에 어떻게 의미가 부여되는지, 우리가 어떻게 객체를 범주화할 것인지에 대해 논할 수 있기 때문이다.

 전통적인 클래스 기반 객체 지향 프로그래밍에서는 만들고자 하는 객체들이 있다면 이들의 공통 속성(property)을 찾아 추상화하여 클래스로 정의한다. 즉, 공통된 속성으로 분류(classification)하여 범주화한다. 그렇다면, 공통 속성이 없으면 그것은 같은 범주로 정의할 수 없는 것일까. 애당초 이렇게 사전에 정의를 하는 것은 올바른 방향인가? 철학자 루트비히 비트겐슈타인은 이러한 고전적인 분류에 대해 정면으로 반박했다.

가족 유사성(Family Resemblance)

 비트겐슈타인은 인간이 어떤 대상을 분류할 때, 속성이 아닌 가족 유사성을 통해 분류한다고 주장한다.

 위 그림의 가족을 보면, 이들은 공통적인 속성이 없다. 그렇지만 우리는 이 구성원들을 하나의 가족으로 인지한다. 이 말인즉슨, 우리는 이들을 하나의 범주로 보고 있지만, 전통적인 클래스로는 정의할 수 없다는 것이다.

 그렇다면 가족 유사성에 의한 분류란 무엇일까? 위 그림으로 계속 얘기하자면, 이 가족은 공통적인 특징은 없지만, 전형적인 특징은 찾을 수 있다. 안경, 짙은 갈색머리, 콧수염 등이 그렇다. 즉, 공통된 속성이 아닌 공유되고 있는 속성(유사성)으로 우리는 이들을 하나의 범주로 인식한다는 것이고 이게 가족 유사성에 의한 분류다.

용도의미론(the use theory of meaning)

 우리는 공유된 속성들을 통해 구성원들을 하나의 범주로 인식한다. 근데, 이러한 범주화의 결과는 모두 동일할까? 누군가는 위 그림을 보고 가족이라고 생각하지 않고 비슷한 스타일의 집단으로 범주화할 수도 있다. 누군가는 토마토를 보고 과일로 생각하고 누군가는 채소라고 생각한다. 이처럼 사람마다 범주화의 결과가 각각 다른데, 사전에 분류하고 정의 내리는 것은 무슨 의미가 있을까. 그렇다. 큰 의미가 없다. 적어도 비트겐슈타인은 그렇게 생각했다.

 그렇다면 의미는 어디서 오는 것일까. 의미는 그 쓰임새(상황, 문맥)에 의해 결정된다.

  • (텃밭에서 식물을 보며)물 좀 줘 = 식물에 물을 뿌려.
  • (땡볕 아래, 손부채질을 하며)물 좀 줘 = 너무 더우니깐, 내가 마실 물을 줘.
  • (나쁜 이성 친구 앞에서)물 좀 줘 = 얼굴에 뿌리게, 컵에 물을 채워.

 다시 말해, 문맥(context)을 통해 크게는 범주화가 이뤄지고 작게는 변수의 의미가 정해진다. 자바스크립트의 실행 컨텍스트는 이 이론이 기저에 깔려있다.

프로토타입 이론(Prototype Theory)

 위의 두 이론이 정리되며 나온 것이, 로쉬의 프로토타입 이론이다. 요지는, 인간이 사물을 분류할 때, 유사성이 높은 것(= 공유하는 속성이 많은 것)으로 순서대로 등급을 매기는데, 등급이 가장 높은 것이 바로 프로토타입(원형)이라는 것이다.

 위 그림은 서구에서 흔한 새인 로빈을 프로토타입으로 간주하고 그에 따라 새라는 그룹으로 범주화한 것이다. 먼저 프로토타입이라는 것은 개개인이 느끼는 어떤 전형적인 것이기 때문에 얼마든지 바뀔 수 있다. 한국에서는 로빈이 아닌, 참새가 프로토타입이 될 수 있다.

 비둘기나 참새는 로빈과 가까이에 있어(= 등급이 높아), 새라는 데에 의심할 여지가 없다. 그러나 타조나 펭귄은 로빈이라는 원형에 멀리 떨어진, 일종의 비전형적인 개체다. 그러므로 누군가에게는 새라고 인식되지 않을 수 있다.

 즉, 우리는 어떤 새로운 개체를 보았을 때, 머릿속에 떠오르는 (개개인의 전형적인)프로토타입과 비교 확인을 하며 범주화 시키면 되는 것이다. 만약 생각하는 프로토타입과 멀리 떨어져 있다면, 그 개체 자체가 또 하나의 새로운 프로토타입이 될 수도 있다.

 프로그래밍과 연결 지어 생각해보면, 프로토타입에서 객체는 클래스로 분류, 정의되어 생성되는 것이 아닌, 좋은 보기(프로토타입)로 점점 복사되어 하나의 범주를 이룬다고 보면 된다.

프로토타입 이론으로 자바스크립트 이해하기

프로토타입으로 객체 지향 프로그래밍 해보기

// 나 박해커, 상사 김코딩으로부터 참새 객체를 만들라는 작업을 받았다.
// 참새 특징이 뭐가 있더라...
function 참새(크기, 날개갯수, 날수있다) {
  this.크기 = 크기;
  this.날개갯수 = 날개갯수;
  this.날수있다 = 날수있다;
}

// 생각해낸 참새 생성자 함수로 인스턴스 찍어내는 중...
const 참새1 = new 참새('작다', 2, true);
const 참새2 = new 참새('작다', 3, true);
const 참새3 = new 참새('크다', 2, false);

console.log(참새1) // 참새 {크기: '작다', 날개갯수 : 2, 날수있다: true}

// 김코딩: 박해커씨, 이번에는 비둘기 객체를 만들어 주세요.
// 비둘기 특징이 뭐가 있더라...

function 비둘기(상징) {
  this.상징 = 상징;
  // 아 그러고보니... 전에 만들었던 참새1과 유사하네?
}

비둘기.prototype = 참새1; // 이미 만들어진 객체로부터 확장

const 비둘기1 = new 비둘기("평화");
const 비둘기2 = new 비둘기("똥");

console.log(비둘기1) // 비둘기 {상징: '평화'}
console.log(비둘기1.날수있다) // true;
// 해당 객체에 '날수있다' 속성은 없지만, 프로토타입 객체([[prototype]])에는 있기에 사용할 수 있다.
// 프로토타입 객체 및 링크는 다음 주제에서 다룬다.

const 닭둘기 = new 비둘기('돼지');

// 앗 닭둘기는 날 수 없는데...
닭둘기.날수있다 = false;

console.log(닭둘기) // 비둘기 {상징: '돼지', 날수있다: false}
console.log(닭둘기.날수있다) // false
// 프로토타입 객체에는 true지만, 해당 객체에 오버라이딩되어 false가 되었다.

// 김코딩: 박해커씨, 마지막으로 타조도 부탁해요.
// 타조 특징이 뭐가 있더라

function 타조(크기, 속도) {
  this.크기 = 크기;
  this.속도 = 속도;
  // 아... 그러고보니, 타조가 참새랑 비슷한 것 같기도 하고.. 아닌 것 같기도 하고
}

타조.prototype = 참새1;

const 타조1 = new 타조('크다', '빨라');
타조1.날수있다 = false;

console.log(타조1) // 타조 {크기: '크다', 속도: '빨라', 날수있다 : false};

// 그후 먼 훗날, 신입 황보쿼리는 박해커의 코드를 보는데...
// 황보쿼리: 아하! 맥락을 파악해 봤을 때, 해당 객체들의 프로토타입은 참새고 점점 넓혀 하나의 범주를 이루고 있구나.
// 어 근데, 타조의 생성자 함수가 좀 그렇다. 이러면 모든 객체마다 다 '날수있다 = false'를 입력해야 되잖아.

// 내가 수정해야지.
타조.prototype.날수있다 = false;

console.log(참새1.날수있다) // false;
console.log(비둘기1.날수있다) // false;

// 아... 망했다.

호이스팅(Hoisting)

 호이스팅은 실행 문맥(execution context) 생성 시, 어휘적 범위(lexical scope) 내 선언들을 상단으로 끌어올리는 것을 뜻한다. 한때 면접 단골 질문이었기에, 자바스크립트를 사용하고 있는 개발자는 대부분 알고 있을 것이다. 그럼 자바스크립트에서 이 호이스팅, 실행 문맥, 어휘적 범위는 왜 존재하는 것일까?

 자바스크립트에서 변수의 의미는 용도의미론에 따라 정의된다. 즉, 문맥 속에서 변수들의 의미가 정해지기에, 자바스크립트 엔진은 코드가 로드될 때 실행 문맥을 생성한다. 그리고 그 안에 선언된 변수, 함수들을 참고해(호이스팅), 단어의 의미가 사용되는 근처 환경인 어휘적 범위를 구성한다.

// 전역 실행 문맥 생성. '이름', '보여줘이름' 호이스팅
// 함수 선언식의 경우, 함수 내용 자체가 호이스팅이 되어 코드상, 선언 전에도 사용 가능하다.
// 변수의 경우, 선언과 함께 초기화, 할당 과정을 거친다.
// var, let, const는 이 부분에서 약간의 차이가 있는데 이는 다음에 다루도록 하겠다.
let 이름 = '나전역';

보여줘이름(); // 보여줘이름 실행 문맥 생성. 내부에 있는 '이름', '나온다이름' 호이스팅

function 보여줘이름() {
  let 이름 = "나내부";
  
  function 나온다이름() {
    // let 이름 = "나내내부";
    // 위 변수 '이름'이 주석이기에,
    // 스코프 체인을 통해, console.log(name)은 외부 어휘적 범위에 접근하여 '이름' 변수 탐색 밑 참조
    // 이렇게 함수와 함수가 선언된 어휘적 환경(범위)의 조합이 '클로저'다.
    console.log(name);
  }
  
  나온다이름(); // 나온다이름 실행 문맥 생성. 주석이 없다면 내부에 있는 '이름' 호이스팅
}

 정리하면, 자바스크립트 내에 중요하게 다루는 내용들(호이스팅, 실행 문맥, 어휘적 범위...)은 모두 프로토타입에서 다뤄지는 문맥을 표현하기 위해 사용된 것들이다.

this

 this 또한 용도의미론과 관련이 있다. 용도의미론에 따르면, 의미는 그 쓰임새에 따라 정해진다. 누가, 어떻게 쓰였느냐에 따라 의미가 달리 정해진다는 것이다. this는 this가 정의된 함수가 누구에 의해 혹은 어떻게 호출되었는지에 따라 그 값이 달라진다.

 일단 기본적으로 그 어떤 지정도 없이 this를 호출하면 this는 전역 객체를 가리킨다. 전역 객체는 런타임마다 다르므로 단순히 window라고 외우면 낭패를 볼 수 있다.

let= '헬로'

function 바깥함수 () {
  console.log(this.);
  this.안에함수();
}

const 나객체 = {: '월드',
  바깥함수,
  안에함수 : function() {
    console.log(this);
  }
}

// 첫번째 경우
나객체.바깥함수();
// 나객체가 바깥함수를 호출했기에, 바깥함수 안의 this는 나객체다.
// console.log로 찍히는 것은 나객체의 값인 '월드'다.
// this가 나객체이기 떄문에 안에함수도 잘 호출된다.
// 마찬가지로 안에함수를 호출한 것은 this이고 this는 나객체이기 때문에
// 안에함수의 console.log에 찍히는 것은 나객체다.

// 두번째 경우
바깥함수();
// 지정한 호출자가 없기에, 바깥함수 안의 this는 전역 객체다.
// console.log로 찍히는 것은 전역 객체 선언한 값인 '헬로'다.
// 전역 객체 안에 안에함수가 없기에 바로 에러가 뜬다.

 DOM 조작과 관련해서도 위 예시와 같다.

function 나클릭핸들 () {
  console.log(this);
}

const 나버튼태그 = document.getElementById('나버튼');

나버튼태그.addEventListener('click', 나클릭핸들)
// addEventListener 함수는 해당 엘리먼트에 콜백을 등록히는 함수다.
// 즉, 해당 html 객체에 지정 함수를 메소드로 등록시킨다.

// 나버튼태그를 클릭시, 해당 html 태그에서 나클릭핸들을 호출한다.
// 즉, this는 나클릭핸들을 호출한 html 태그다.

 call, apply는 함수 호출 메소드다. 첫번째 인자로 this를 지정해줄 객체가 들어가고 다음 인자로는 해당 함수의 매개변수가 들어간다. call하고 apply의 차이는 그 매개변수를 각각 입력하냐, 리스트 형식으로 입력하냐 차이다.

 bind는 인자에 this를 지정해줄 객체를 넣어 새로운 함수를 생성한다. 호출하지는 않는다. this값을 고정할 때 사용한다. 그 외 생성자함수에 new 키워드를 쓰면은 해당 함수 내 this는 새로 생긴 인스턴스 객체를 가리킨다.

let 나객체 = {};

function 객체에추가함수(나이, 좋아하는음식) {
  this.나이 = 나이;
  this.좋아하는음식 = 좋아하는음식;
};

객체에추가함수.call(나객체, 30, "국수");
// 혹은 객체에추가함수.apply(나객체, [30, "국수"])

console.log(나객체) // {나이: 30, 좋아하는음식: 국수}

 정리하면 this의 경우 호출자를 지정해준 케이스가 아니라면 호출되었을 때를 보고 그 의미를 찾으면 된다.

나가는 말

 지금까지의 내용을 간략하게 정리하면 다음과 같다.

  • 자바스크립트는 프로토타입 기반의 언어다.
  • 프로토타입에서는 공유하는 속성이 많은 객체(프로토타입)를 통해 점점 범주화를 이룬다.
  • 호이스팅, this 등 자바스크립트의 난해했던 부분들은 대다수 프로토타입 이론을 표현하기 위해 설계된 것이다.

 소제목에서 거창하게 디자인이라고 적어둔 이유는, 자바스크립트에 이러한 이론들이 바탕에 있다는 것을 명시하고 싶었기 때문이지 별다른 이유는 없다.

 아쉬운 점으로는 mdn 문서도 여러모로 살펴보긴 했지만, 블로그 위주의 정보를 정리하다 보니 중구난방이고 내용이 정확하지 않다. 그리고 하단 출처에 넣겠지만, 한 블로그에 너무 의존했다. 이 게시글은 그 블로그를 프로토타입 삼아 나온 하나의 인스턴스로 봐도 무방하다. 그리고 호이스팅이나 this 같은 경우, 필자 스스로 완벽히 정리되지 않았는지, 명확하게 서술하지 못한 것 같다. 계속 수정 및 추가해야 할 부분이다.

참고한 사이트

사이트 1
사이트 2

좋은 웹페이지 즐겨찾기