JS에는 두 가지 종류의 타입이 있다.
원시타입 (primitive type)
String, Number, Boolean 등등이 여기에 속한다.
value 하나만 가지고 있다.
객체타입 (Object type)
Object, Function, Array 등등이 여기에 속한다.
자바로 치면 Reference Type으로 볼 수 있다.
객체이기 때문에 프로퍼티와 메서드를 가진다.
객체는 서로 다른 타입의 값들을 프로퍼티의 이름: 값으로 맵핑해서 하나의 그룹으로 묶은 자료구조이다.
ex.
let obj = {
name : "David",
age: 24,
speak: () => {
console.log("hello! world!");
}
}
이 때 프로퍼티 중 함수를 값으로 가지고 있는 프로퍼티는 메서드라고 부른다.
Object, Function, Array 같은 타입을 객체 타입이라고 하는 이유는 참조 타입(reference type)으로 각 타입이 내부적으로 Object.toString(), Object.length 와 같은 프로퍼티들을 가질 수 있기 때문이다.
그러나, Object, Function, Array는 함수이다.
이 이유는 마지막에 설명하겠다.
let obj = {
name : "David",
age: 24,
speak: () => {
console.log("hello! world!");
}
}
해당 name, age, speak으로 구성된 값만 다른 다른 객체를 만들고 싶으면 어떻게 할까?
원래 JS는 클래스 개념이 없기에 각 프로퍼티의 값들을 받아서 위와 같은 객체 구조를 가진 객체를 반환하는 helper 함수를 통해서 생성한다.
ex.
function Rabbit() {
return {
speak() {
console.log('Rabbit speaks');
}
};
}
근데 여기서 OOP의 개념을 가져와서 새로운 객체 인스턴스를 만드는 거니까 new 키워드로 만들 수 있게 했다.
const r1 = new Rabbit();
또한 class 키워드를 통해서 자신만의 프로퍼티 + prototype에 들어갈 메서드들을 작성할 수 있게 하였다.
class Rabbit {
constructor() {
this.type = "rabbit";
}
speak() {
console.log('Rabbit speaks');
}
//여기서 선언하는 메서드들은 new로 만들어지는 인스턴스가 공유하기 때문에 prototype으로 들어가게 된다.
}
클래스 키워드를 사용함으로써 Rabbit 함수의 이름이 클래스로 Rabbit 함수 body가 하던 일을 constructor로 분리가 되었다.
new Rabbit()으로 호출하면 내부적으로는 constructor가 실행되는 것이고 따라서 constructor 내부에서 this로 해당 인스턴스만 가지는 값들에 대한 것을 추가할 수 있다.
동일한 객체 타입으로 선언한 인스턴스끼리는 전부 동일한 메서드를 가진다.
ex.
let obj = {};
let obj2 = {};
로 생성한 두 obj, obj2 인스턴스는 Object 타입으로 Object 타입이 가지고 있는 length, toString(), 등등의 메서드들을 둘 다 가지고 있을 것이다.
OOP에서는 인스턴스마다 각자의 메서드들을 복사본으로 가지고 있지만
JS에서는 인스턴스들마다 동일하게 가지고 있는 메서드, 프로퍼티들을 prototype이라는 공유매체를 통해서 공유하기로 한다.
즉, Object 타입은 Object.prototype이라는 프로퍼티의 자신의 메서드들을 저장하고 있다는 것
함수는 Function type으로 이는 Object type을 상속하고 있다.
따라서 함수도 프로퍼티와 메서드를 가질 수 있다.
function speak(){
console.log("hello world!");
}
speak.name = "kim"
speak.bye() = () => {
console.log("bye bye");
}
그렇다면 speak라는 Function type의 함수도 prototype을 통해서 자신만의 메서드를 또 저장해야 한다.
이때 __proto__ 라는 프로퍼티를 통해 자신이 상속받아서 사용하고 있는 Function의 prototype에 대한 getter/setter를 가지고 있다. (진짜 내부적으로 링크를 가지고 있는 참조 프로퍼티는 [[Property]] 로 따로 있다. __proto__ 는 이 참조값에 대한 getter/setter인 것)
이렇게 __proto__을 통해 상속받아서 사용하고 있는 메서드들을 알 수 있고 메서드가 호출되었을 때 prototype에 없으면 상위 메서드를 찾게 되는데 이 때 __proto__를 통해서 거슬러 올라가게 된다. 이를 prototype chaining이라고 한다.
function으로 선언한 함수는 자신만의 prototype을 가질 수 있다.
그렇지만
let obj = {};
로 선언한 경우에 obj는 자신만의 프로토타입이랑 메서드를 만들 수는 있지만 (객체 타입이니까) prototype은 존재하지 않는다.
그 이유는 obj 자체는 Object type의 인스턴스일 뿐 이 인스턴스 자체가 직접 타입이 될 수는 없기 때문. 즉, new obj()가 불가능하기 때문에 타입으로서 기능을 할 수 없기에 prototype이 없음
function keyword로 선언한 함수는 생성하자마자 자신의 prototype이 존재한다.
그 이유는 function 함수는 constructor 메서드가 이미 포함되어 있기에 생성과 동시에 constructor를 가지고 있는 prototype을 가지고 있다.

constructor가 있는 이유는 new Function()으로 생성할 때 호출할 constructor가 필요하기 때문
그러나 arrow function은 new () => {} 이렇게가 불가능하기 때문에 constructor가 없어서 prototype이 존재하지 않는다.
객체랑 마찬가지로 새로운 타입을 만들어낼 수 없기 때문에 prototype이 없다.
const r = new Rabbit();
의 경우 chaining을 그려보면
r
|
| [[Prototype]] (__proto__)
↓
Rabbit.prototype
- jump()
- constructor → Rabbit
|
↓
Object.prototype
- toString()
- hasOwnProperty()
- ...
|
↓
null
이렇게 되고 Rabbit의 constructor의 chaining을 보면
Rabbit (함수 객체)
|
↓ __proto__ ( [[Prototype]] )
Function.prototype
- call()
- apply()
- bind()
- ...
|
↓ __proto__
Object.prototype
- toString()
- hasOwnProperty()
- ...
|
↓
null
Rabbit의 인스턴스의 chaining과 Rabbit.constructor의 chaining이 다르다.
Rabbit의 인스턴스 -> Rabbit 타입
Rabbit.constructor -> Rabbit 타입을 만들어내는 함수
이 객체 타입들은 사실을 함수이다!!!
console.log(typeof Object); // 'function'
console.log(typeof Function); // 'function'
console.log(typeof Array); // 'function'
즉 Object, Function, Array는 사실은 Object, Function, Array라는 이름의 새로운 타입을 만들어 낼 수 있는 함수이다!
새로운 타입을 만들어낼 수 있는 함수이기에 첫번째는 대문자로 작성한 것
| 용어 | 어디에 있음 | 역할 | 주요 사용 대상 |
|---|---|---|---|
| prototype | 생성자 함수(함수 객체)에 존재하는 property | new로 객체 생성 시 해당 객체의 [[Prototype]]을 설정 | 생성자 함수에서 인스턴스들이 공유할 메서드/속성을 정의할 때 사용 |
| __proto__ | 모든 객체에 존재하는 getter/setter (Object.prototype에서 상속됨) | 해당 객체의 [[Prototype]]에 접근하는 수단 (개발자 접근용) | 인스턴스에서 prototype chain을 확인 조작할 때 사용 |
| [[Prototype]] | 모든 객체가 내부적으로 가지는 엔진 내부 슬롯 | prototype chain에서 상속을 위한 실제 참조 링크 | 자바스크립트 엔진이 property lookup 시 사용하는 실제 참조 구조 (개발자가 직접 접근 불가) |
prototype은 생성자 함수가 제공하는 prototype 원본이고, [[Prototype]]은 객체 내부에서 실제 prototype chain을 구성하는 참조 링크이며, __proto__는 [[Prototype]]에 접근하는 개발자용 getter/setter이다.