프로토타입 기반 언어프로토타입 이해하기생성자 속성프로토타입 수정하기메서드속성프로토타입 체인일반적인 방식 : 속성은 생성자에서, 메소드는 프로토타입에서 정의프로토타입 기반 작성 🆚 클래스 기반 작성
[ 참조 공식 문서 ]
프로토타입 기반 언어
- JavaScript는 흔히 프로토타입 기반 언어라 불림
- 모든 객체들이 메소드와 속성들을 상속받기 위한 템플릿으로써 프로토타입 객체를 가진다는 의미임
- 프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수도 있고, 그 상위 프로토타입 객체도 마찬가지 ⇒ 이것이 프로토타입 체인 ( 다른 객체에 정의된 메소드와 속성을 한 객체에서 사용할 수 있도록 하는 근간)
프로토타입 이해하기
- 상속되는 속성과 메소드들은 각 객체가 아닌, 객체의 생성자의
prototype
이라는 속성에 정의되어 있음
JavaScript에서는 객체 인스턴스와 프로토타입 간에 연결이 구성되며 이 연결을 따라 프로토타입 체인을 타고 올라가며 속성과 메소드를 탐색함
var Person = function (name) { this._name = name; }; Person.prototype.getName = function() { return this._name; }
자바스크립트는 함수에 자동으로 객체인 prototype 프로퍼티를 생성해 놓는데, 해당 함수를 생성자 함수로서 사용할 경우(
new
연산자와 함께 함수를 호출할 경우),
그로부터 생성된 인스턴스에는 숨겨진 프로퍼티인 __proto__
가 자동으로 생성되며, 이 프로퍼티는 생성자 함수의 prototype
프로퍼티를 참조함
__proto__
프로퍼티는 생략 가능하도록 구현돼 있기 때문에 생성자 함수의 prototype
에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있음[ String mdn 문서 ] - 여기서 정적 메서드, 인스턴스 속성, 인스턴스 메서드 확인하면 무슨 말인지 이해됨
Object에 정의되어 있는 메소드를 person1 에서 호출하면 일어나는 일은 아래 과정
function Person(first, last, age, gender, interests) { // 속성과 메소드 정의 this.first = first; this.last = last; //... } var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']); person1.valueOf()
- 브라우저는 우선
person1
객체가valueOf()
메소드를 가지고 있는지 체크함
- 없으므로
person1
의 프로토타입 객체(Person()
생성자의 프로토타입)에valueOf()
메소드가 있는지 체크
- 여전히 없으므로
Person()
생성자의 프로토타입 객체의 프로토타입 객체(Object()
생성자의 프로토타입)가valueOf()
메소드를 가지고 있는지 체크합니다. 여기에 있으니 호출하며 끝납니다!
특정 객체의 프로토타입 객체에 바로 접근하는 방법( __proto__ )
- instance.__proto__
- 뒤에 __proto__ 를 하나 더 붙이면 그 상위 프로토타입 객체를 반환함
- 그러나 __proto__ 는 생략가능하며, instance.method() 형태로 호출이 가능함
- Object.getPrototypeOf(instance);
생성자 속성
- 모든 생성자 함수는
constructor
속성을 지닌 객체를 프로토타입 객체로 가지고 있음
constructor
속성은 원본 생성자 함수 자신을 가리킴
var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
- 실행 도중 명시적인 생성자 함수를 예측할 수 없는 상황에서 인스턴스를 생성해야 하거나 하는 경우 유용하게 사용할 수 있는 방법임
프로토타입 수정하기
메서드
function Person(first, last, age, gender, interests) { // 속성과 메소드 정의 } var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']); Person.prototype.farewell = function() { alert(this.name.first + ' has left the building. Bye for now!'); };
- person1을 만들고나서
prototype
에 메소드를 업데이트 하더라도 바로 사용이 가능함 - 프로토타입 객체는 모든 인스턴스에서 공유하기 때문에
속성
prototype
에 속성을 정의하는 경우는 별로 좋지 않은 방법임
Person.prototype.fullName = this.name.first + ' ' + this.name.last;
- 이 경우
this
는 함수 범위가 아닌 전역 범위를 가리키므로 코드가 의도대로 동작하지 않습니다.
- 이대로 실행해도
undefined undefined
만 볼 수 있죠.
- 윗 절에서 프로토타입에 정의한 메소드 내에서는 정상적으로 동작하는데 이는 코드가 함수 범위 내에 있으며 객체의 멤버 함수로써 동작하기에 객체 범위로 전환되었기 때문입니다
- 따라서 프로토타입에 상수(한 번 할당하면 변하지 않는 값)를 정의하는 것은 가능하지만 일반적으로 생성자에서 정의하는 것이 낫습니다
프로토타입 체인
// o라는 객체가 있고, 속성 'a' 와 'b'를 갖고 있다고 하자. let f = function () { this.a = 1; this.b = 2; } let o = new f(); // {a: 1, b: 2} // f 함수의 prototype 속성 값들을 추가 하자. f.prototype.b = 3; f.prototype.c = 4; // f.prototype = {b: 3, c: 4}; 라고 하지 마라, 해당 코드는 prototype chain 을 망가뜨린다. // o.[[Prototype]]은 속성 'b'와 'c'를 가지고 있다. // o.[[Prototype]].[[Prototype]] 은 Object.prototype 이다. // 마지막으로 o.[[Prototype]].[[Prototype]].[[Prototype]]은 null이다. // null은 프로토타입의 종단을 말하며 정의에 의해서 추가 [[Prototype]]은 없다. // {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null
![Arraty의 내부를 살펴보면 [[Prototype]] 안에 또 [[Prototype]] 이 있는 것을 확인할 수 있음](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F5a80af97-7399-4b03-b5d0-12dbaa549106%2FUntitled.png?table=block&id=f528e82a-4ce3-40be-a70a-432109310de9&cache=v2)

- 어떤 데이터의 [[Prototype]] 내부에 다시 [[Prototype]]가 연쇄적으로 이어진 것을 프로토타입 체인이라 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라고 함
일반적인 방식 : 속성은 생성자에서, 메소드는 프로토타입에서 정의
프로토타입 기반 작성 🆚 클래스 기반 작성
function Logger(name) { this.name = name; } Logger.prototype.log = function(message) { console.log(`[${this.name}] ${message}`); } Logger.prototype.info = function(message) { this.log(`info: ${message}`); }; Logger.prototype.verbose = function(message) { this.log(`verbose: ${message}`); }; module.exports = Logger;
class Logger { constructor(name) { this.name = name; } log(message) { console.log(`[${this.name}] ${message}`); } info(message) { this.log(`info: ${message}`); } verbose(message) { this.log(`verbose: ${message}`); } } module.exports = Logger;