Prototype [1] - JS의 객체지향

Marullo·2021년 4월 8일
0
post-thumbnail

Prototype이란?

prototype은 자바스크립트의 객체지향성을 지탱하는 개념이다. 또 일반적인 객체지향 언어와 구분하는 개념이다. prototype은 말 그대로 객체의 원형이 된다. JS에서는 prototype으로 상속을 구현할 수 있다. JS ES6부터 class 키워드가 사용되었지만, class도 이 prototype의 개념을 벗어나지 않는다.

JS에서 Prototype이란 명칭은 2가지를 각각 가리킨다. 이 두 가지를 구분할 수 있어야 한다.

  1. Prototype property
  2. Protytpe Link

1. JS에서 인스턴스 만들기

JS에서 Prototype을 이용하여, "인스턴스 생성"과 "Class 원형과 인스턴스의 연결"을 어떻게 이뤄내는지 살펴보자.


1.1 생성자 함수란?

function Person(name, first, second) {
  this.name = name;
  this.first = first;
  this.second = second;
}

function Person(){} 함수는 statement의 일종이지만, JS에서 함수는 독특한 "객체"다. 객체이기 때문에 new 키워드를 사용해서 함수를 표현할 수도 있다. var Person = new Function()
함수를 호출할 때 new 키워드를 사용하면, 그 함수는 단순한 함수가 아니라 생성자로서 호출된다.


생성자는 객체를 반환한다.

function Person() {}

var obj = new Person();
console.log(typeof Person); //=> function
console.log(typeof obj); //=> object

Person의 타입은 function이지만, obj의 타입은 object다.


new로 호출하면, this는 전역 객체가 아닌 생성된 객체 자체를 가리키게 된다.

var name = "default name";

function Person(name) {
  if (this.name) {
    console.log(this.name);
  } else {
    this.name = name;
    console.log(this.name);
  }
}

Person("cho"); //=> default name
var obj = new Person("cho"); //=> cho
  • Person()을 그냥 호출하면, this는 글로벌 오브젝트를 참조한다.
    • 따라서, default name을 출력하게 된다.
    • 내부 프로퍼티 [[Scope]]가 window가 되고 이것을 외부 렉시컬 참조에 삽입했기 때문이다.
  • new 키워드로 호출하면, this는 함수 그 자체에 바인딩된다.
    • 객체지향의 인스턴스처럼 사용이 가능해진다.

생성자는 원형에 부합하는 프로퍼티(변수나 메소드)를 가진 객체를 반환한다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

var kim = new Person("kim", 29);
  • 객체 kimnameage를 가진 객체가 된다.
  • 생성자에 의해 객체의 프로퍼티가 define 된다.

"this와 객체를 반환하는 형태를 통해, Javascript의 함수를 인스턴스를 생성하는 class로 사용할 수 있다."





1.2 Function Object & Function prototype Object

function Person(name, first, second) {
  this.name = name;
  this.first = first;
  this.second = second;
}

JS 함수는 객체다. 따라서 프로퍼티를 가질 수 있다.

  • 위와 같이 함수를 선언하면 Person이라는 Function Object가 생성된다.
    • Function Object내부에 prototype이라는 내부 프로퍼티가 있다.
  • 그런데 객체가 하나 더 생긴다. Person.prototype이라는 객체도 추가로 생성된다. 이것은 Prototype Object 라고 불린다. 이것이 클래스 원형의 역할을 하게 된다.
    • Prototype Object내부에 constructor라는 내부 프로퍼티가 생긴다.

그리고 위에서 말했던 프로퍼티들은 상대방을 참조하고 있다.

  • Penson (함수)객체와 Person.prototype 객체는 서로 상호참조하고 있다.
    • Person의 prototype "property"가 Person.prototype 객체를 참조한다.
    • Person.prototype의 constructor "property"가 Person 객체를 참조한다.

function object에는 프로퍼티 이름이 prototype인 프로퍼티가 있으며, prototype이라는 프로퍼티가 참조하는 것은 prototype obejct다. 이름이 겹쳐서 헷갈릴 수 있음





1.3 __proto__ 프로퍼티 & prototype 프로퍼티

함수는 생성자가 되어, 객체를 생성할 수 있다.

var kim = new Person("kim", 10, 20);
var cho = new Person("cho", 20, 30);
  • 생성자 함수를 호출과 new 키워드가 합쳐지면서, kim과 cho에 Person (함수)객체와 같은 새로운 객체가 생성된다.
  • 이때 Person 객체가 가지고 있는 프로퍼티들을 만들고 초기화한다.
  • 또한 생성된 인스턴스에는 __proto__라는 프로퍼티가 생긴다. 이 프로퍼티는 Person의 Prototype Object 를 참조한다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.name = "default name";
Person.prototype.age = "default age";

var kim = new Person("kim", 29);
console.log(Person.prototype === kim.__proto__); //=> true
  • console.log(Person.prototype === kim.__proto__);
    콘솔에서는 prototype property는 오브젝트 마다 다르다.
    인스턴스에서는 .__proto__ 프로퍼티로 Person의 Prototype Object를 참조하고,
    생성자 역할을 하는 Persone .prototype.이라는 Person의 Prototype Object를 참조

이제 함수 원형(Constructor), 생성된 객체(Instance), Prototype Object(Class)가 서로 연결되었다.

JS에서는 함수를 생성자로 쓰게되면, Prototype Object라는 것이 생긴다. Prototype Object가 클래스 객체 역할을 하게된다.
즉, 객체지향에서는 클래스안에 생성자 함수를 두지만, JS에서는 생성자와 클래스를 분리했다고 생각하면 된다.





2. JS에서 데이터, 메소드 공유하기

객체지향에서 인스턴스들은 클래스의 메소드를 공유하거나, 클래스 원형의 데이터를 인스턴스에서 사용하기도 한다.

  • new를 통해 생성되는 객체는 __proto__ 프로퍼티로, 생성자는 prototype 프로퍼티로 prototype Object(Person.prototype) 을 참조한다.
  • Prototype Object는 constructor라는 프로퍼티로 생성자 (function Person) 를 참조한다.

Prototype link란

prototype link란 prototype chain이라고도 불린다. 스코프 체인의 원리와 비슷하다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.getName = function () {
  console.log(this);
  return this.name;
};

var cho = new Person("조병건", 28);
var kim = new Person("김병건", 18);

console.log(cho); //=> {name : 조병건, age: 28}
console.log(kim); //=> {name : 김병건, age :18}

console.log(cho.getName()); //=> 조병건
console.log(kim.getName()); //=> 김병건
  • Person.prototype.getName = function(){...}
    Person (함수)객체가 아니라 Person.prototype 객체에 getName이라는 메소드를 추가했다.
    • 따라서, cho,kim 객체에는 getName이라는 메소드가 없다.

식별자 해결

  • 그러나 cho.getNamekim.getName은 정상적으로 동작한다.
  • chokim 객체에는 getName이라는 메소드가 없는데 어떻게 식별자 해결이 동작할까?
  1. 엔진은 처음 각자의 객체에서 식별자 해결을 한다.
  2. 만약 현재 위치에서 식별자 해결이 되지 않는다면, __proto__(prototype property) 가 참조하는 객체로 올라가서 식별자 해결을 하게 된다.
  3. __proto__가 참조하는 것은 현재 Person.prototype Object이므로, getName에 대한 식별자 해결이 가능하다.

이렇게, prototype이 참조하는 대상으로 올라가는 것은, 객체와 프로토타입이 연결되어 있기 때문인데, 이것을 prototype link라고 한다.

이렇게 Prototype Object를 클래스로 삼아, 프로퍼티를 추가하면서, prototype link로 연결된 객체들이 Prototype Object에 위치한 변수나 메소드를 사용할 수 있다. 즉, prototype object는 관련 객체들끼리의 공유 장소가 될 수 있다.


this 바인딩

  1. 자바스크립트 엔진은 cho.getName이 호출되면, getName을 위한 실행 컨텍스트를 만든다. 이때, getName 호출문 앞에 위치한 cho라는 객체를 this 바인딩 컴포넌트로 참조한다.
  2. getName의 실행시점에서, this로 참조하는 cho 오브젝트로 가서 name이라는 식별자에 대한 식별자 해결을 하게되고 값을 리턴하게 된다.

#### 이렇게, JS에서는 서로의 참조를 통해, 하나의 클래스의 메소드와 변수를 인스턴스들이 공유할 수 있다.


3. JS에서 상속은 어떻게?

객체지향에서는 상속이란 개념이 있다. JS는 Prototype을 이용해서 어떻게 상속을 구현했을까

3.1 Prototype link를 이용한 상속

function First(v) {
  this.first = "first";
}
function Second() {
  this.second = "second";
}
function Third() {
  this.third = "third";
}

First.prototype.get = function () {
  console.log(this.first, this.second, this.third);
};

Second.prototype = new First();
Third.prototype = new Second();

var obj = new Third();
  • Second.prototype 의 참조 대상을 First형 객체로 바꿔치기 했다.
  • Third.prototype 의 참조 대상을 Second형 객체로 바꿔치기 했다.
  • obj가 실제적으로 들고 있는 프로퍼티는 third뿐이지만, 마치 부모 클래스가 있는 것 처럼, secondfirst 속성을 갖게 된다.(스코프 체인에 의하여) 이런 방식으로 상속을 구현한다.

Class 상속과 다른 점은, 상속에서 부모 클래스의 프로퍼티들을 "복사하지 않는 것이다." 프로토타입 체인만을 이용하여, 내 것처럼 사용할 수 있게 되는 것이다. 스코프 체인과 동작 원리가 같다.

식별자 해결에 있어서 프로토타입과 스코프체인이 같이 동작한다고 한다. 이 동작 원리에 대해서 공부가 더 필요하다.

profile
한국외대 중국어&컴공 복수전공 - 세미 전공자의 기술 블로그

0개의 댓글