8장에서 언급했던 내용을 떠올려보면, 메서드는 객체의 프로퍼티이자 호출 가능한 함수다.
메서드를 호출하면, 메서드를 호출하면, 메서드 내의 this 키워드는 메서드가 속한 객체를 가리킨다.
만약, 여러분들이 Rectangle의 객체로 표현된 사각형의 넓이를 곗나하고 싶다면,
한 가지 방법으로 다음과 같은 방식을 사용할 수 있다.
이 함수는 작동을 하긴 하지만, 객체지향적인 방법이 아니다.
객체지향적인 언어에서는 객체를 함수의 인자로 넘겨주는 방법보다 메서드를 사용하는 것이 좋다.
아래에는 이 방법이 설명되어 있다.
var r = new Rectangle(8.5, 11);
// 메서드를 한 개를 추가한다.
r.area = function() { return this.width * this.height; }
// 이제 넓이를 계산하기 위해서 메서드를 호출한다.
var a = r.area();
위의 예에서 메서드를 추가하는 방법이 다소 이상하게 보일 수도 있다. 이 점은 area 프로퍼티와 넓이를 계산하는 함수를 생성자 내부에서 연결시키는 방법을 통해 개선시킬 수 있다. 아래는 이 방법을 통해 개선시킨 Rectangle() 생성자이다.
this.height = h;
this.area = function() { return this.width * this.height; }
여러분들은 이제 새로운 생성자를 사용하여 다음과 같이 코드를 작성할 수 있다.
var r = new Rectangle(8.5, 11);
var a = r.area();
생성자 내부에서 메서드를 추가하는 방법이 좀 더 좋은 방법이긴 하지만 가장 좋은 해결책은 아니다.
이 방법을 통해 객체를 생성하면, 생성된 모든 사각형 객체는 프로퍼티를 세 개 가진다.
사각형마다 width와 height 프로퍼티의 값은 다르겠지만, 각 Rectangle 객체의 area 프로퍼티는 항상 같은
함수를 의미한다.(물론 이것을 바꿀 수도 있지만, 대체로 바꾸지 않고 사용한다). 한 클래스에 속하는 객체들이 공유하게끔 만들어진 메서드에, 일반적인 프로퍼티를 사용하는 것은 비효율적이다.
자바스크립트는 이를 해결할 수 있는 방법도 제시한다. 자바스크립트의 모든 객체는 프로토타입이라고 불리는 또 다른 객체를 내부적으로 참조할 수 있다. 그리고 객체는 프로토타입의 프로퍼티들을 자신의 프로퍼티로 가져온다. 다시 말해, 자바스크립트의 객체는 자신의 프로토타입에 있는 프로퍼티들을 상속받는다.
이전 절에서 new 연산자는 빈 객체를 새로 생성한 후, 생성자 함수를 호출한다고 이야기했다. 하지만, 이것이 완벽한 흐름을 보여주지는 않는다. 빈 객체가 생성되면, new 연산자는 해당 객체의 프로토타입을 설정한다.
이때 생성된 객체는, 자신을 만들어낸 생성자의 prototype이라는 프로터티가 있는데, 이것은 함수가 정의될 때부터 자동적으로 생성되고 초기화 된다. prototype 프로퍼티의 초기값은 프로퍼티가 하나 있는 객체로
지정된다. 이 프로퍼티는 constructor라고 불리는데 프로토타입이 연관되어 있는 생성자 함수를 가리킨다.
(7장에서 나왔던 constructor 프로퍼티를 다시 떠올려 보라. 이것이 바로 모든 객체에 constructor 프로퍼티가 있는 이유다.) 여러분들이 프로토타입 객체에 추가한 프로퍼티들은 생성자를 사용하여 초기화되는 모든 객체의 프로퍼티로 나타난다.
Rectangle() 생성자를 사용하는 예를 보고, 좀 더 개념을 명확히 하자.
function Rectangle(w, h) {
this.height = h;
// 프로토타입 객체는 각 인스턴스들이 공유해야 하는 프로퍼티나 메서드를 정의한다.
Rectangle.prototype.area = function() { return this.width * this.height; }
생성자는 객체들의 클래스를 위한 이름을 정의하고, width나 height와 같이 인스턴스마다 다를 수 있는
프로퍼티의 값을 초기화시킨다. 그리고 프로토타입 객체는 생성자와 연결되며, 이 생성자를 통해 생성되는
객체들은 프로토타입이 가진 프로퍼티들을 똑같이 상속받는다. 이 말은 프로토타입 객체가 메서드나 상수
같은 프로퍼티들을 위치시키기에 좋은 곳임을 의미한다.
상속이 프로퍼티 값을 찾는 과정의 일부로서 자동적으로 발생한다는 사실을 유념하여 보라. 프로퍼티는 프로토타입에서 새로운 객체로 복사되는 것이 아니다. 마치 새로운 객체의 프로퍼티인 것처럼 보일 뿐이다. 이는 두 가지 중요한 사실을 의미한다. 우선, 프로토타입 객체를 사용하면 각 객체가 프로토타입의 프로퍼티를 상속받기 때문에 이들이 필요로 하는 메모리의 양을 상당히 줄일 수 있다. 다음으로, 프로토타입에 새로운 프로퍼티가 추가되면, 이미 생성되었던 객체일지라도 추가된 프로퍼티를 그대로 상속받는다. 이것은 (비론 좋은 생각은 아닐 수도 있지만) 기존의 클래스에 새로운 메서드를 추가할 수 있다는 사실을 말한다.
상속받은 프로퍼티는 객체의 일반적인 프로퍼티처럼 작동한다. 그리고 이 프로퍼티들은 for/in 루프를 통해
열거될 수 있으며, in 연산자를 사용하여 시험할 수 있다. 또한, 여러분은 Object.hasOwnProperty() 메서드를 사용해 이들을 종류별로 구별해낼 수도 있다.
r.hasOwnProperty("width"); //true. width는 r의 직접적인 프로퍼티이다.
r.hasOwnProperty("area"); //false. area는 r이 상속받은 프로퍼티이다.
"area" in r; //true. area는 r의 프로퍼티이다.
1. 상속받은 프로퍼티의 읽기와 쓰기
클래스에는 프로퍼티의 집합과 더불어 한 개의 프로토타입 객체가 있다.
클래스의 인스턴스는 여러 개가 있을 수 있는데, 이들은 각각 프로토타입의 프로퍼티들을 상속받는다.
한 개의 프로토타입 프로퍼티는 여러 개의 객체들이 상속받을 수 있기 때문에 자바스크립트에서는
프로퍼티 값을 읽거나 쓸 때 비대칭성을 의무적으로 지키게 하고 있다. 여러분이 객체 o의 프로퍼티인 p를
읽을 때, 자바스크립트는 o에 p라는 이름을 가진 프로퍼티가 있는지 검사한다.
이것이 바로 프로토타입 기반의 상속이 작동할 수 있는 이유다.
반면, 자바스크립트는 여러분이 프로퍼티의 값을 쓰려고 할 때는 프로토타입 객체를 사용하지 않는다.
이렇게 하는 이유를 살펴보기 위해, 반대를 생각해 보자. 객체 o에 p라는 프로퍼티가 없을 때,
프로퍼티 o.p의 값을 설정하려고 하는 상황을 생각해 보라. 더 나아가 자바스크립트가 이런 상황을 더욱
진행시켜 o의 프로토타입 객체가 있는 p를 찾고, 프로토타입의 프로퍼티 값을 설정할 수 있게 한다고
생각해 보라. 이제 여러분은 전혀 의도하지 않았지만 모든 객체의 p값을 바꿔버렸다.
따라서 프로퍼티의 상속은 프로퍼티를 쓸 때가 아닌 읽을 때만 일어난다. 여러분이 만약, 객체 o가 프로토타입에서 상속받은 프로퍼티 p를 설정하려 하면, 이때부터 o에는 새로운 프로퍼티인 p가 만들어진다. 이제 o에는 p라고 이름 붙은 자신만의 프로퍼티가 생기고, 더이상 p의 값을 프로토타입에서 상속받지 않는다. 그리고 자바스크립트는 여러분이 p의 값을 읽으려 할때, 먼저 o의 프로퍼티들을 살펴본다. 이때, 자바스크립트는 o에 정의되어 있는 p를 찾으므로 프로토타입 객체를 검색하지 않으며 그곳에 있는 p의 값을 절대 찾지 않는다.
우리는 종종 이것을 o에 있는 프로퍼티 p가 프로토타입 객체에 있는 프로퍼티 p를 '가렷다(shadows)' 혹은 '숨겼다(hides)'라고 말한다. 프로토타입 상속은 다소 혼란스러운 주제가 될 수도 있다. 그림 9-1에 여기서
설명한 개념들을 표현했다.
프로토타입의 프로퍼티들은 클래스의 모든 객체가 공유하기 때문에, 모든 객체가 같이 사용하는 프로퍼티들을 정의해놓는 것이 이해하기 쉽다. 이 점은 프로토타입이 메서드를 정의해두기에 안성맞춤이라는 것을 말한다. 그 외에 상수 값(예를 들어, 수학적인 계산에 사용되는 값들)인 프로퍼티들도 프로토탕비의 프로퍼티로
정의되는 것이 적당하다. 만약, 여러분의 클래스가 굉장히 자주 사용되는 기본값을 프로퍼티로 정의한다면
이런 프로퍼티와 그 기본값을 프로토타입 객체에 정의해둘 수도 있다. 이렇게 하면, 기본값을 사용하지 않는 일부 객체들만 그들이 원하는 값이 되도록 재정의하게 할 수 있다.
2. 내장형 타입의 확장
사용자 정의 클래스만 프로토타입 객체가 존재하지는 않는다. String이나 Date 같은 내장형(built-in) 클래스에도 프로토타입이 있으며, 여러분들은 여기에 값을 할당할 수 있다. 예를 들어, 다음의 코드는 모든 String 객체가 사용할 수 있는 새로운 메서드를 한 개 정의한다.
String.prototype.endsWith = function(c) {
방금 String 프로토타입 객체에 endsWith()라는 새로운 메서드를 정의했으며,
여러분은 이 메서드를 아래와 같이 사용할 수 있다.
message.endsWith('h') // 거짓을 반환한다.
message.endsWith('d') // 참을 반환한다.
내장형 타입을 여러분이 만든 메서드를 사용하여 확장시키는 데는 많은 논란이 있다.
만약 여러분이 이런 기능을 사용한다면, 필연적으로 코어 자바스크립트 API의 사용자 정의 버전을 만들게
된다. 만약 다른 프로그래머가 여러분의 코드를 읽거나 관리해야 한다면, 전혀 들어보지도 못한 메서드
때문에 혼란스러울 수도 있다. 따라서 여러분이 자바스크립트의 낮은 레벨(low-level)에 해당하는
프레임워크를 만들어 다른 프로그래머들이 사용하게 하려는 목적이 아니라면, 내장형 타입의 프로토타입
객체는 건드리지 않는 것이 좋다.
Object.prototype에는 어떤 프로토 타입도 추가해서는 안 된다.
여러분이 추가한 프로퍼티나 메서드는 for/in 루프를 사용하여 열거될 수 있고, 이를 Object.prototype에
추가하면 자바스크립트의 모든 객체가 볼 수 있다. 예를 들어, 자바스크립트에서 { } 같은 빈 객체는 열거할 프로퍼티가 없다고 간주된다. 하지만, Object.prototype에 추가된 것은 빈 객체의 열거 가능한 프로퍼티가
되며, 객체를 연관 배열(associative array)로 사용하는 코드가 작동하지 않게 만든다.
여기서 보여주는 내장형 객체 타입을 확장하는 기술은 코어 자바스크립트의 네이티브(native) 객체들에서만 작동한다. 만약 자바스크립트가 웹 브라우저나 자바 애플리케이션 같은 다른 내용에 포함되면, 호스트 객체(host object)에 접근할 수 있다. 예를 들어, 웹 브라우저의 문서 내용을 가리키는 객체가 호스트 객체가 될 수 있다. 이런 호스트 객체에 일반적으로 생성자나 프로토타입 객체가 없으면, 이들을 확장할 수 없다.
내장형 네이티브 클래스의 프로토타입을 확장하는 것이 안전하고 유용한 경우가 한 가지 있다.
오래되었거나 호환이 되지 않는 자바스크립트가 표준 메서드를 포함하고 있지 않으면 새로운 메서드를
추가할 수 있다. 예를 들어, 마이크로소프트(Micorsoft)의 인터넷 익스플로러(Internet Explorer) 4와 5에는 Function.apply()라는 메서드가 빠져있다. 이 함수는 제법 중요한 역할을 담당하는데, 여러분은 아래 같은
코드를 작성하여 이를 대체할 수 있다.
// 이 해결 방법은 Aaron Boodman의 코드를 참고했다.
if (!Function.prototype.apply) {
// 이를 위해서, eval()을 사용해야 한다.
Function.prototype.apply = function(object, parameters) {
var o = object || window; // 함수를 호출하는 객체
var args = parameters || []; // 전달할 인자들
// 이 함수를 잠시 o의 메서드로 만든다.
// 이를 위해서, 존재하지 않을법한 프로퍼티의 이름을 만들어 사용한다.
o._$_apply_$_ = f;
// 메서드를 호출하기 위해서 eval()을 사용할 것이다.
// 이를 위해, 호출 문을 문자열로 적었따. 우선, 전달한 인자들의 리스트를 만든다.
var stringArgs = [];
for(var i = 0; i < args.length; i++)
// 전달인자들의 문자열을 콤마(,)로 구별되게 이어 붙인다.
var arglist = stringArgs.join(",");
// 이제 메서드를 호출하는 전체 문자열을 만든다.
var methodcall = "o._$_apply_$_(" + arglist + ");";
// 메서드를 호출하기 위해 eval() 함수를 사용한다.
var result = eval(methodcall);
// 함수를 객체와 분리시킨다.
delete o._$_apply_$_;
// 결과를 반환한다.
return result;
다른 예로, 파이어폭스(FireFox) 1.5에 새롭게 구현되어 있는 배열 메서드를 생각해 보자(7.7.10항을 보라).
만약 여러분이 새로운 Array.map() 메서드를 사용하고 싶고 그 코드가 이 메서드를 지원하지 않는 플랫폼에도 작동하길 원한다면, 이래 코드를 사용하여 호환성을 유지할 수 있다.
// 새로운 배열로 만들어 반환한다. 만약 map()이 두 개의 전달인자와 함께 호출되면, 함수 f는
// 두 번째 인자의 메서드로서 호출된다. 함수가 호출될 때, f()는 세 개의 인자를 전달받는다.
// 세 번째 인자는 배열 원소의 값이다.
// 두 번째 인자는 배열 요소가 배열에서 어디에 위치하는 값인지를 나타내는 인덱스이며,
// 세 번째 인자는 배열 자신이다. 대부분 단지 첫 번째 인자만을 필요로 한다.
if (!Array.prototype.map) {
for(var len = this.length, i = 0; i < len; i++) {
return results;
'IT_Programming > JavaScript' 카테고리의 다른 글
자바스크립트 완벽가이드 - 9.4 공통적인 객체 메서드 (0) | 2010.07.04 |
---|---|
자바스크립트 완벽가이드 - 9.3 자바스크립트의 클래스 시뮬레이션 (0) | 2010.07.04 |
자바스크립트 완벽가이드 - 9.1 생성자 (0) | 2010.07.04 |
자바스크립트 완벽가이드 - 8.9 Function() 생성자 (0) | 2010.07.04 |
자바스크립트 완벽가이드 - 8.8 함수 유효 범위와 클로저 (0) | 2010.07.04 |