자바스크립트의 모든 객체는 최소한 하나 이상의 다른 객체로부터 상속을 받으며, 이때 상속되는 정보를 제공하는 객체를 프로토타입(prototype)이라고 합니다. -Tcp School
자바 스크립트는 Java나 C++처럼 클래스 기반의 언어가 아닌 프로토타입 기반의 언어인 만큼 프로토타입은 자바 스크립트를 공부하는 사람이라면 누구나 짚고 넘어가야하는 개념중 하나인 만큼 굉장히 중요한 개념이다.
자바스크립트의 모든 객체는 부모객체가 가진 프로퍼티나 메서드를 상속받아 호출하는것이 가능한데, 이러한 부모객체를 프로토타입(prototype) 객체라 한다.
const Person = function(name){
this.name = name;
this.hello = function(){
console.log(`Hello ${this.name}`);
}};
const james = new Person('james');
Person이라는 생성자 함수를 'new'연산자를 통해 호출했더니 Person에서 정의한 내용을 바탕으로 새로운 instance가 생성되었다. 그리고 이 instance에는
__proto__
라는 Property가 자동으로 부여되는데__proto__
은 자바 스크립트의 모든 객체가 자신의 부모인 프로토타입 객체를 가리키는 참조링크를 저장하는 객체이다.
❗️(ES5.1 명세에는 __proto__
가 아니라 [[prototype]]
이라는 명칭으로 기재되어있으며, __proto__
은 브라우저가 [[prototype]]
을 구현한 대상에 지나지않는다.)
먼저 프로토타입의 원리를 알기 위해서는 객체는 언제나 함수를 통해 생성된다는 사실을 기억해야한다.
객체 리터럴 방식으로 생성한 객체든, 생성자 함수로 생성한 객체든 결국 함수로 생성한 객체이다.
const newObj = new Object(); // 자바 스크립트 기본 API인 Object함수로 생성
const newObj = {}; // 객체 리터럴 방식으로 생성, (위와 같은 코드이다.)
함수를 통해 객체가 생성 된다면 객체가 생성된 후엔 무슨일이 일어나는지 알아보자.
객체 리터럴 방식으로 생성한 객체도 결국 Object()라는 자바스크립트 내장 api 함수를 통해 생성된 객체이다.
또한 모든 생성자 함수는 생성시 prototype객체가 함께 생성되는데, 이로인해 생성된 Object.prototype 객체에는 api 함수들이 내장되어있다.
이로인해 'myObject'객체는__proto__
링크를 통해hasOwnProperty()
같은 Object.prototype의 메서드를 사용할 수 있는것이다. 이처럼 부모의 프로토 타입 객체의 프로퍼티를 자신것 처럼 사용할수있고, 이를 프로토타입 체이닝이라고 한다.
(myObject 객체는
hasOwnProperty()
메서드를 갖고있지않지만, 부모의 프로토타입 객체가 ``hasOwnProperty()```메서드를 갖고있기때문에 사용할 수 있다.)
1. 생성자 함수 Person 생성시 Person.prototype 객체가 생성됨.
2. Person.prototype의 constructor는(생성자)는 생성자 함수 Person이다.
3. new 연산자를 통해 james라는 객체를 생성했더니 이 객체는__proto__
라는 암묵적인 링크를
통해 Person의 prototype객체에 접근이 가능해짐 (객체 리터럴방식에서 myObject가__proto__
링크를 통해 부모의 프로토타입 객체에 접근하는것과 같은 원리이다.)
4. Person.prototype 역시 자바스크립트 객체 이기에 Object.prototype을 prototype객체로 가진다. 따라서 프로토 타입 체이닝은 Object.prototype으로 이어진다.
Person 생성자 함수로 생성된 james와 생성자 함수인 Person의 프로퍼티를 보면 각각 james의 __proto__
와 Person 함수의prototype
이 같은 곳을 가리키고있는것을 볼수있다.
앞에서 살펴봤듯이 객체 리터럴 생성 방식이든 생성자 함수를 통한 객체 생성 방식이든 결국 모든 객체의 프로트 타입 체이닝의 끝은 Object.prototype이다. 그렇기에 모든 객체는 toString
, isPrototypeOf
, hasOwnProperty()
같은 내장 메서드를 사용할 수 있는것이다.
이를 통해 우린 객체지향 언어의 class를 비슷하게 흉내낼 수 있다. 위 예제에서 객체 리터럴로 생성한 myObject
는 Park라는 name 프로퍼티를 갖고있고, Person 생성자 함수로 생성한 james
객체는 james
라는 name 프로퍼티를 갖고있다. 만일 프로토타입을 몰랐다면 두 객체에 console.log(`hello ${this.name}!`)
를 실행하는 sayHello
라는 함수를 만들어주고 싶다면
const Person = function(name){
this.name = name;
this.sayHello = function(){
console.log(`hello ${this.name}!`);
}};
const james = new Person('james');
james.sayHello(); // hello james!
------------------------------------------------
const myObject = {
name: "Park",
sayHello: function(){
console.log(`hello ${this.name}!`)
}
}
myObject2.sayHello(); // // hello Park!
위와 같이 각 객체의 프로퍼티 함수로 만들어 줬겠지만, 이제 프로토타입에 대해 알게됬으므로 아래와 같이 모든 객체의 부모 프로토타입 객체인 Object.prototype에 sayHello 함수를 만들어줌으로서 모든 객체에서 sayHello객체에 접근할 수 있게 되었고, 이로인해 우린 불필요한 메모리 낭비를 줄일 수 있게되었다!
마지막으로 중요한 것은 자바스크립트의 프로토타입 작동방식과 일반적인 객체 지향언어 에서 쓰는 상속의 개념과는 큰 차이가 있는데, 자바스크립트에서 객체간의 관계는 복사가 아닌 연결이 맺어진 것임으로 상속보단 ‘위임’이 좀더 적절한 표현이라고 한다.
(제게 있어선) 상당히 복잡한 개념과 관련된 포스팅이라 잘못된 부분이 있을 수 있습니다! 언제든 잘못된 부분은 댓글로 남겨주시면 바로 수정하도록 하겠습니다! 😃
(약 4시간에 걸쳐 블로그를 쓰던 내 모습.)