4.5 프로토타입 체이닝
4.5.1 프로토타입의 두 가지 의미
자바스크립트는 기존 C++이나 자바 같은 객체지향 프로그래밍 언어와는 다른 프로토타입 기반의 객체지향 프로그래밍을 지원한다. —> 자바스크립트의 동작 과정을 제대로 이해하려면 프로토타입의 개념도 잘 이해하고 있어야 한다.
(6장에서 자바스크립트가 어떻게 OOP 기능을 제공하는지에 대해서 자세히 살펴봄)
이번 절에서는 자바스크립트에서 OOP 상속에 근간이 되는 프로토타입과 프로토타입 체이닝의 기본 개념 설명.
자바와 같은 객체지향 프로그래밍에서는 클래스를 정의하고 이를 통해 객체를 생성하지만, 자바스크립트에서는 이러한 클래스 개념이 없다. 대신 객체 리터럴이나 앞서 설명했던 생성자 함수로 객체를 생성한다. 이렇게 생성된 객체의 부모 객체가 바로 프로토타입 객체.
즉, 상속 개념과 마찬가지로
자식 객체는 부모 객체가 가진 프로퍼티 접근이나 메서드를 상속받아 호출하는 것이 가능.자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티가 있다. ECMAScript에서는 이러한 링크를
암묵적 프로토타입 링크(implicit prototype link)라고 부르며 이러한 링크는 모든 객체의 Prototype
프로퍼티에 저장됨. 이 책에서는 이러한 링크를 Prototype 링크라고 명명함.
단, 함수 객체의 prototype 프로퍼티와 객체의 숨은 프로퍼티인 Prototype 링크를 구분해야 한다. 이 둘의 차이점을 알려면 자바스크립트의 객체 생성 규칙을 알아야 한다.
자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수인 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 부모 객체로 설정하는 Prototype 링크로 연결한다.
예제 4-37 prototype 프로퍼티와 Prototype 링크 구분
// Person 생성자 함수
function Person(name) {
this.name = name;
}
// foo 객체 생성
var foo = new Person('foo');
console.dir(Person);
console.dir(foo);
Person()
생성자 함수는 prototype
프로퍼티로 자신과 링크된 프로토타입 객체(Person.prototype
)를 가리킨다. 그리고 Person()
생성자 함수로 생성된 foo
객체는 Person()
함수의 프로토타입 객체를 Prototype
링크로 연결한다. 즉, 같은 것을 가리킴.
prototype
프로퍼티는 함수의 입장에서 자신과 링크된 프로토타입 객체를 가리키고 있으며, 이에 반해 Prototype
링크는 객체의 입장에서 자신의 부모 객체인 프로토타입 객체를 내부의 숨겨진 링크로 가리키고 있다.
결국, 자바스크립트에서 객체를 생성하는 건 생성자 함수의 역할이지만,
생성된 객체의 실제 부모 역할을 하는 건 생성자 자신이 아닌 생성자의 prototype 프로퍼티가 가리키는 프로토타입 객체4. 함수와 프로토타입 체이닝 (2) - 4.2.3 함수 객체의 기본 프로퍼티에서 간단히 설명한 것처럼, __proto__
프로퍼티는 모든 객체에 존재하는 숨겨진 프로퍼티로 객체 자신의 프로토타입 객체를 가리키는 참조 링크 정보. ECMAScript에서는 이것을 Prototype
프로퍼티로 정하고, 내부적으로만 사용된다고 명시하고 있지만, 크롬이나 파이어폭스 같은 브라우저에서는 __proto__
프로퍼티로 명시적으로 제공하고 있다.
따라서 __proto__
프로퍼티나 Prototype
프로퍼티는 같다고 간주하면 된다.
4.5.2 객체 리터럴 방식으로 생성된 객체의 프로토타입 체이닝
자바스크립트에서 객체는 자기 자신의 프로퍼티뿐만 아니라, 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티 또한 마치 자신의 것처럼 접근하는 것이 가능. 이것을 가능케 하는 게 바로 프로토타입 체이닝
예제 4-38 객체 리터럴 방식에서의 프로토타입 체인이
var myObject = {
name: 'foo',
sayName: function () {
console.log('My name is ' + this.name);
}
};
myObject.sayName(); // My name is foo
console.log(myObject.hasOwnProperty('name')); // true
console.log(myObject.hasOwnProperty('nickName')); // false
myObject.sayNickName(); // Uncaught TypeError: Object #<Object> has no method 'sayNickName'
myObject
는name
프로퍼티와sayName()
메서드를 가진 객체.myObject
객체가sayNickName()
메서드를 가지고 있지 않으므로 에러 발생myObject
객체에hasOwnProperty()
메서드가 없음에도 결과가 정상적으로 출력됨. (에러 발생 x)
hasOwnProperty()
메서드 : 이 메서드를 호출한 객체에 인자로 넘긴 문자열 이름의 프로퍼티나 메서드가 있는지 체크하는 자바스크립트 표준 API 함수. Object.prototype
객체에 포함되어 있다.
3.2.1 객체 생성에서 설명했듯이 객체 리터럴로 생성한 객체는 Object()
라는 내장 생성자 함수로 생성된 것. Object()
생성자 함수도 함수 객체이므로 prototype
이라는 프로퍼티 속성이 있다. —> 앞서 설명한 자바스크립트의 규칙으로 생성한 객체 리터럴 형태의 myObject
는 Object()
함수의 prototype
프로퍼티가 가리키는 Object.prototype
객체를 자신의 프로토타입 객체로 연결.
프로토타입 체이닝
자바스크립트에서 특정 객체의 프로퍼티나 메서드에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티 또는 메서드가 없다면 Prototype
링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 차례대로 검색하는 것
4.5.3 생성자 함수로 생성된 객체의 프로토타입 체이닝
생성자 함수로 객체를 생성하는 경우는 객체 리터럴 방식과 약간 다른 프로토타입 체이닝이 이뤄진다. 하지만 두 가지 방식 모두 다음과 같은 기본 원칙을 잘 지키고 있다.
자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체(부모 객체)로 취급한다.
예제 4-39 생성자 함수 방식에서의 프로토타입 체이닝
// Person() 생성자 함수
function Person(name, age, hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
// foo 객체 생성
var foo = new Person('foo', 30, 'tennis');
// 프로토타입 체이닝
console.log(foo.hasOwnProperty('name')); // true
// Person.prototype 객체 출력
console.dir(Person.prototype);
foo
객체의 생성자는Person()
함수 —>foo
객체의 프로토타입 객체는 자신을 생성한Person
생성자 함수 객체의prototype
프로퍼티가 가리키는 객체(Person.prototype
). —>foo
객체의 프로토타입 객체는Person.prototype
foo.hasOwnProperty()
메서드를 호출했지만,foo
객체는 이 메서드를 가지고 있지 않기 때문에 프로토타입 체이닝으로foo
의 부모 객체인Person.prototype
객체에서hasOwnProperty()
메서드를 찾는다. 그러나 4.2.3.2 prototype 프로퍼티에서 알아봤듯이 함수에 연결된 프로토타입 객체는constructor
프로퍼티만을 가진 객체이므로hasOwnProtpety()
메서드는 없다. —>Person.protytpe
의 프로토타입 객체Object.prorotype
에 가서 이 메서드를 찾는다.
4.5.4 프로토타입 체이닝의 종점
자바스크립트에서 Object.prototype
객체는 프로토타입 체이닝의 종점.
—> 객체 리터럴 방식이나 생성자 함수 방식에 상관없이 모든 자바스크립트 객체는 프로토타입 체이닝으로 Object.prototype
객체가 가진 프로퍼티와 메서드에 접근하고, 서로 공유가 가능하다!
—> 자바스크립트 표준 빌트인 객체인 Object.prototype
에는 hasOwnProperty()
나 isPrototypeOf()
등과 같이 모든 객체가 호출 가능한 표준 메서드들이 정의되어 있다.
4.5.5 기본 데이터 타입 확장
자바스크립트의 숫자, 문자열, 배열 등에서 사용되는 표준 메서드들의 경우, 이들의 프로토타입인 Number.prototype
, String.prototype
, Array.prototype
등에 정의되어 있다. 물론 이러한 기본 내장 프로토타입 객체 또한 Object.prototype
을 자신의 프로토타입으로 가지고 있어서 프로토타입 체이닝으로 연결된다.
자바스크립트는 Object.prototype
, String.prototype
등과 같이 표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메서드들을 추가하는 것을 허용한다.
예제 4-40 String 기본 타입에 메서드 추가
String.prototype.testMethod = function () {
console.log('This is the String.prototype.testMethod()');
};
var str = "this is test";
str.testMethod();
console.dir(String.prototype);
4.5.6 프로토타입도 자바스크립트 객체다
함수가 생성될 때, 자신의 prototype
프로퍼티에 연결되는 프로토타입 객체는 디폴트로 constructor
프로퍼티만을 가진 객체다. 당연히 프로토타입 객체 역시 자바스크립트 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는 것이 가능하다. 그리고 이렇게 변경된 프로퍼티는 실시간으로 프로토타입 체이닝에 반영됨.
예제 4-41 프로토타입 객체의 동적 메서드 생성 예제 코드
// Person() 생성자 함수
function Person(name) {
this.name = name;
}
// foo 객체 생성
var foo = new Person('foo');
// foo.sayHello(); // error
// 프로토타입 객체에 sayHello() 메서드 정의
Person.prototype.sayHello = function () {
console.log('Hello');
}
foo.sayHello(); // Hello
4.5.7 프로토타입 메서드와 this 바인딩
프로토타입 객체는 메서드를 가질 수 있다. —> 프로토타입 메서드
만약 프로토타입 메서드 내부에서 this
를 사용한다면 어디에 바인딩?
메서드 호출 패턴에서의 this
는 그 메서드를 호출한 객체에 바인딩된다는 규칙이 그대로 적용됨.
예제 4-12 프로토타입 메서드와 this 바인딩
// Person() 생성자 함수
function Person(name) {
this.name = name;
}
// getName() 프로토타입 메서드
Person.prototype.getName = function () {
return this.name;
};
// foo 객체 생성
var foo = new Person('foo');
console.log(foo.getName()); // (출력값) foo
// Person.prototype 객체에 name 프로퍼티 동적 추가
Person.prototype.name = 'person';
console.log(Person.prototype.getName()); // (출력값) person
foo
객체에서getName()
메서드를 호출하면,getName()
메서드는foo
객체에서 찾을 수 없으므로 프로토타입 체이닝이 발생한다.foo
객체의 프로토타입 객체인Person.prototype
에서getName()
메서드가 있으므로, 이 메서드가 호출됨. 이때getName()
메서드를 호출한 객체는foo
이므로,this
는foo
객체에 바인딩된다. —>foo.getName()
의 결과값으로foo
가 출력됨.Person.prototype.getName()
메서드와 같이 프로토타입 체이닝이 아니라, 바로Person.prototype
객체에 접근해서getName()
메서드를 호출하면? 이때는getName()
메서드를 호출한 객체가Person.prototype
이므로this
도 여기에 바인딩된다. 그리고Person.prototype
객체에name
프로퍼티를 동적으로 추가하고 ‘person’을 저장했으므로this.name
은 ‘person’이 출력된다.
4.5.8 디폴트 프로토타입은 다른 객체로 변경이 가능하다
자바스크립트에서는 함수를 생성할 때 해당 함수와 연결되는 디폴트 프로토타입 객체를 다른 일반 객체로 변경하는 것이 가능. —>
객체지향의 상속을 구현(6장에서 더 자세히 살펴봄)
주의! 생성자 함수의 프로토타입 객체가 변경되면, 변경된 시점 이후에 생성된 객체들은 변경된 프로토타입 객체로 Prototype
링크를 연결한다. 이에 반해 생성자 함수의 프로토타입이 변경되기 이전에 생성된 객체들은 기존 프로토타입 객체로의 Prototype
링크를 그대로 유지.
예제 4-43 프로토타입 객체 변경
// Person() 생성자 함수
function Person(name) {
this.name = name;
}
console.log(Person.prototype.constructor);
// foo 객체 생성
var foo = new Person('foo');
console.log(foo.country); // undefined
// 디폴트 프로토타입 객체 변경
Person.prototype = {
country: 'korea',
};
console.log(Person.prototype.constructor);
// bar 객체 생성
var bar = new Person('bar');
console.log(foo.country); // undefined
console.log(bar.country);
console.log(foo.constructor);
console.log(bar.constructor);
- 객체 리터럴 방식으로 생성한
country
프로퍼티를 가진 객체로 프로토타입 객체(Person.prototype
)를 변경. 이 이후Person.prototype.constructor
는 어떤 값? 변경한 프로토타입 객체는 디폴트 프로토타입 객체가 아니기 때문에constructor
프로퍼티가 없다. —> 프로토타입 체이닝 발생. 변경된 프로토타입 객체는 객체 리터럴 방식으로 생성했으므로Object.prototype
을Prototype
링크로 연결. —>Object.prototype
역시Object()
생성자 함수와 연결된 빌트인 프로토타입 객체이기 때문에Object()
생성자 함수를constructor
프로퍼티에 연결. —>Person.prototype.constructor
값은Object()
생성자 함수가 출력됨.
4.5.9 객체의 프로퍼티 읽기나 메서드를 실행할 때만 프로토타입 체이닝이 동작한다
객체의 특정 프로퍼티를 읽으려고 할 때, 프로퍼티가 해당 객체에 없는 경우 프로토타입 체이닝이 발생한다. 반대로 객체에 있는 특정 프로퍼티에 값을 쓰려고 한다면 이때는 프로토타입 체이닝이 일어나지 않는다. 자바스크립트는 객체에 없는 프로퍼티에 값을 쓰려고 할 경우 동적으로 객체에 프로퍼티를 추가한다.
예제 4-44 프로토타입 체이닝과 동적 프로퍼티 생성
// Person() 생성자 함수
function Person(name) {
this.name = name;
}
Person.prototype.country = 'Korea';
var foo = new Person('foo');
var bar = new Person('bar');
console.log(foo.country); // Korea
console.log(bar.country); // Korea
foo.country = 'USA';
console.log(foo.country); // USA
console.log(bar.country); // Korea
#책/인사이드자바스크립트
'language > JavaScript' 카테고리의 다른 글
5. 실행 컨텍스트와 클로저 (2) (0) | 2019.11.27 |
---|---|
5. 실행 컨텍스트와 클로저 (1) (0) | 2019.11.26 |
4. 함수와 프로토타입 체이닝 (4) (0) | 2019.11.22 |
4. 함수와 프로토타입 체이닝 (3) (0) | 2019.11.21 |
4. 함수와 프로토타입 체이닝 (2) (0) | 2019.11.21 |