JavaScript는 클래스라는 개념이 없다.
ES6에서 class가 도입되었지만, 이는 사실 함수이며 클래스 기반에 익숙한 프로그래머들의 편의를 위해 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 도입된 것이다.
그러니, 프로토타입 기반 모델을 클래스처럼 쓸 수 있도록 한 것이므로 JavaScript의 클래스는 syntatic sugar(문법적 설탕)이다.
즉, Javascript는 기존의 객체를 복사하여 (cloning) 새로운 객체를 생성하는 프로토타입 기반의 언어이다.
프로토타입 기반 언어는 객체 원형인 프로토타입을 이용하여 새로운 객체를 만들어낸다.
이렇게 생성된 객체 역시 또 다른 객체의 원형이 될 수 있다.
프로토타입은 크게 두 가지로 해석된다.
자바스크립트에서 함수는 객체이다.
그러므로, 생성자로 사용될 함수도 객체이다.
객체는 프로퍼티를 가질 수 있는데, prototype
이라는 프로퍼티는 그 용도가 약속되어 있는 특수한 프로퍼티로서 prototype
에 저장된 속성들은 생성자를 통해서 객체가 만들어질 때 그 객체에 연결된다.
생성자 함수와 달리, 클래스에서 메소드는 자동으로 prototype
의 메소드가 된다.
즉, 인스턴스끼리 같은 메소드를 공유한다.
클래스는 생성자 함수와 매우 유사하게 동작하지만, 클래스가 생성자 함수보다 엄격하며 더 많은 기능을 제공한다.
자바스크립트에 클래스는 없었지만, 함수와 new
를 통해 클래스를 비슷하게 흉내낼 수 있었다.
function Person() {
this.eyes = 2;
this.nose = 1;
}
var kim = new Person();
var park = new Person();
console.log(kim.eyes); // 2
console.log(kim.nose); // 1
console.log(park.eyes); // 2
console.log(park.nose); // 1
kim과 park는 eyes와 nose를 공통으로 가지고 있지만, 메모리에는 eyes와 nose가 두 개씩 총 4개가 할당된다.
생성자 함수는 객체를 여러 개 생성할 때 유용하게 쓸 수 있지만, 객체지향 원칙 중 하나인 상속
을 구현할 수 없다.
그 이유는 생성자 함수에 의해 생성되는 모든 인스턴스가 메소드를 중복 생성하고 중복 소유하기 때문이다.
즉, 객체를 100개 만들면 200개의 변수가 메모리에 할당될 것이다.
이런 문제를 프로토타입 기반 상속으로 해결할 수 있다.
function Person() {
...
}
Person.prototype.eyes = 2;
Person.prototype.nose = 1;
var kim = new Person();
var park = new Person();
console.log(kim.eyes); // 2
console.log(kim.nose); // 1
Person.prototype
이라는 빈 Object가 어딘가에 존재하고, Person 함수로부터 생성된 객체인 kim, park은 어딘가에 존재하는 Object에 들어있는 값을 모두 쓸 수 있다.
즉, eyes와 nose를 어딘가에 있는 빈 공간에 넣어놓고 kim과 park가 공유해서 사용하는 것으로, Person.prototype
으로부터 eyes와 nose를 상속받았음을 의미한다.
자바스크립트에는 prototype link
와 prototype object
라는 것이 존재한다.
이 둘을 통들어 prototype이라고 부른다.
객체는 언제나 함수로 생성된다.
function Person() {} // 함수
const personObject = new Person(); // 함수로 객체를 생성
personObject
객체는 Person
이라는 함수로 생성된 객체이다.
이렇듯 언제나 객체는 함수에서 시작된다.
일반적으로 쓰는 객체 생성도 예외는 아니다.
const obj = {};
const obj.= new Object();
위의 두 코드는 동일하다.
함수가 정의될 때는 2가지 일이 동시에 이루어진다.
constructor
자격이 부여되면 new
를 통해 객체를 만들 수 있게 되는데, 이것이 함수만 new 키워드를 사용할 수 있는 이유이다.
constructor
가 아니면 new
를 사용할 수 없다.
함수를 정의하면 함수만 생성되는 것이 아니라, Prototype Object도 같이 생성된다.
함수를 정의하여 생성된 함수는 prototype
이라는 속성을 통해 prototype object
에 접근할 수 있다.
prototype object
는 일반적인 객체와 같으며, 기본적인 속성으로 constructor
와 __proto__
를 가지고 있다.
prototype 속성으로 prototype object에 접근하면, constructor는 prototype object와 같이 생성되었던 함수를 가리키고 있다.
여기서 __proto__
는 prototype link
이다.
위의 예시였던 kim과 park은 Person
함수를 통해 생성되었으니 Person.prototype
을 참조할 수 있게 되는 것이다.
kim 객체에는 eyes가 없는데 어떻게 사용이 가능한걸까?
kim에는 eyes라는 속성이 없는데도 kim.eyes를 실행하면 2라는 값을 참조하는 것을 볼 수 있다.
Prototype Object에 존재하는 eyes 속성을 참조하는 것인데, 어떻게 가능한걸까?
바로 kim이 가지고 있는 딱 하나의 속성 __proto__
가 그것을 가능하게 해주는 열쇠다.
prototype 속성은 함수만 가지고 있던 것과는 달리, __proto__
속성은 모든 객체가 빠짐없이 가지고 있는 속성이다.
__proto__
는 객체가 생성될 때 조상이었던 함수의 Prototype Object
를 가리킨다.
kim 객체는 Person 함수로부터 생성되었으니 Person
함수의 Prototype Object
를 가리키고 있는 것이다.
kim 객체가 eyes를 직접 들고있지 않기 때문에 eyes 속성을 찾을 때까지 상위 프로토타입을 탐색한다.
최상위인 Object
의 Prototype Object
까지 도달했는데도 못 찾았을 경우, undefined
를 리턴한다.
이렇게 __proto__
속성을 통해 상위 프로토타입과 연결되어 있는 형태를 프로토타입 체인
이라고 한다.
이런 프로토타입 체인 구조 때문에, 모든 객체는 Object의 자식이라고 불리고 Object Prototype Object
에 있는 모든 속성을 사용할 수 있다.
한 가지 예로는 toString()
함수가 있다.
Object가 가지고 있는 모든 속성을 자식이 사용할 수 있다.
프로토타입 체인
이라고 한다.