자바스크립트가 객체라는 데이터 타입을 지원하기는 하지만, 클래스를 구체적으로 표현할 수 있는 방법은
제공하지 않는다. 이 점은 자바스크립트를 C++나 자바같은 전통적인 객체지향 언어와 차별화 시킨다.
객체지향 프로그램 언어들은 공통적으로 데이터 타입을 엄격히 검사하며 클래스 기반의 상속을 지원한다.
이런 관점에서 볼 때, 자바스크립트는 진정한 의미의 객체지향 언어가 아니다.
반면, 여러분은 자바스크립트가 객체를 자주 사용한다는 점과 프로토타입의 기반의 상속을 지원한다는
사실을 이미 호가인했다. 이 점 때문에 자바스크립트는 객체지향 언어로 볼 수 있다.
그리고 이런 점은 클래스 기반의 상속 대신 프로토타입 기반의 상속을 사용하는(상대적으로 애매한)
객체지향 언어들에서 영감을 받았다.
비록 자바스크립트가 클래스 기반의 객체지향 언어가 아니긴 하지만 자바나 C++같은 클래스 기반의 언어가 지원하는 기능들을 흉내 낼 수 있다. 이 장에서는 지금까지 비공식적으로 클래스라는 단어를 사용했다.
이 절에서는 자바나 C++같은 클래스 기반의 상속 언어들과 자바스크립트를 좀 더 자세히 비교하겠다.[각주:1]
우선 몇 가지 기본적인 단어들을 정의하면서 시작하자. 여러분이 이미 확인했듯이 객체란, 이름 붙은
여러 개의 데이터와 데이터를 조작하기 위한 메서드가 있는 자료구조다. 객체는 고나련된 값이나 메서드를
하나의 패키지에 묶어 코드의 모듈성과 재사용성을 높임으로써 프로그램을 더욱 쉽게 만들 수 있게 해준다.
자바스크립트의 객체는 프로퍼티의 개수에 제한이 없으며, 동적으로 추가할 수도 있다.
자바나 C++같이 타입을 엄격히 검사하는 언어에서는 이런 기능이 제공되지 않는다. 이들 언어는 객체가 사용되기 전에 미리 프로퍼티들의 집합을 정해두며,[각주:2] 프로퍼티들의 타입도 정해둔다. 만약 여러분이 자바스크립트를 사용하여 객체지향 프로그램 기법을 흉내 내고 싶다면, 객체가 가질 수 있는 프로퍼티들의 집합과 각 프로퍼티의 데이터 타입을 미리 정해두면 된다.
자바와 C++에서 클래스는 객체의 구조를 정의한다. 이들 언어에서 클래스는, 객체의 필드에는 어떤 것이 있음 각 필드에 할당될 수 있는 데이터는 어떤 타입인지 나열한다. 또한 여기에는 객체에서 작동하는 메서드도 정의된다. 자바스크립트는 클래스에 대한 개념이 없지만, 앞에서 본 바와 같이 생성자와 프로토타입 객체를 통해 클래스를 흉내 낸다.
자바스크립트와 클래스 기반의 객체지향 언어들은 모두 한 클래스에 속하는 여러 개의 객체가 있을 수 있다. 이런 객체를 클래스의 인스턴스라고 부르기도 한다. 즉, 클래스는 여러 개의 인스턴스를 가질 수 있다. 객체(클래스의 인스턴스)를 생성하는 과정을 '인스턴스화'라는 단어를 사용하여 표현하기도 한다.
자바에서는 클래스의 이름을 만들 때 첫 글자가 대문자로 싲가하게 하고 객체의 이름은 소문자로 짓는 것이 일반적인 방법이다. 이 방법은 코드 상에서 클래스와 객체를 구분하는 데 도움을 주는데, 자바스크립트에서도 이 방법을 사용하는 것이 편리하다. 예를 들어, 이 장의 앞선 절들에서 클래스 이름은 Rectangle로 정하고 인스턴스는 rect라는 이름으로 만들었다.
자바의 클래서는 네 개의 기본 타입으로 이루어진다. 인스턴스 프로퍼티, 인스턴스 메서드, 클래스 프로퍼티, 클래스 메서드. 다음에 나오는 절들에서는 이들의 차이점을 설명하고 자바스크립트에서 이들을 흉내 내는 방법을 알려주겠다.
1. 인스턴스 프로퍼티
모든 객체에는 자신만으 인스턴스 프로퍼티 사본이 있다. 다시 말해, 한 클래스에 속하는 객체가 열 개 있다면 인스턴스 프로퍼티 사본이 열 개 만들어진다. 예를 들어, Rectangle 클래스의 모든 객체에는 사각형의 너비를 나타내는 width 프로퍼티가 있다. 이때, width는 인스턴스 프로퍼티이다. 각 객체에는 자신만의 인스턴스
프로퍼티 사본이 있기 때문에 이 프로퍼티는 객체가 개인적으로 접근한다.
예를 들어, 만약 r이 Rectangle 클래스의 인스턴스라면 r의 width는 다음과 같은 방법을 사용하여 참조된다.
자바스크립트에서 객체의 프로퍼티는 기본적으로 인스턴스 프로퍼티가 된다. 하지만, 클래스 기반의
객체지향 프로그램을 제대로 흉내 내기 위해 자바스크립트에서의 인스턴스 프로퍼티는 생성자 함수가
생성하고 초기화시키는 프로퍼티라고 하겠다.
2. 인스턴스 메서드
인스턴스 메서드는 데이터 값이 아니라 메서드라는 점을 제외하면 인스턴스 프로퍼티와 상당히 비슷하다
(자바에서는 함수와 메서드가 자바스크립트와 마찬가지로 데이터가 아니기 때문에 구별이 쉽다).
인스턴스 메서드는 특정한 객체나 인스턴스가 호출한다.
Rectangle 클래스의 area() 메서드는 하나의 인스턴스 메서드다.
이 메서드는 다음과 같이 Rectangle의 객체 r이 호출한다.
인스턴스 메서드는 메서드를 호출한 객체나 인스턴스를 참조하기 위해서 this 키워드를 사용한다.
클래스의 인스턴스가 인스턴스 메서드를 호출할 수는 있지만, 인스턴스 프로퍼티같이 각 객체마다 그들만의 메서드 사본을 가진다는 말은 아니다. 대신 각 인스턴스 메서드는 클래스의 모든 인스턴스가 공유한다.
자바스크립트에서 클래스의 인스턴스 메서드는 생성자의 프로토타입 객체가 가진 프로퍼티에 함수 값을
넣어주는 방법을 통해 정의된다. 이 생성자를 통해 생성되는 모든 객체는 함수에 대한 참조를 상속받고
이를 공유한다.
3. 인스턴스 메서드와 this
만약 여러분이 자바나 C++ 프로그래머라면 이들 언어에서 말하는 인스턴스 메서드와 자바스크립트의
인스턴스 메서드에 중요한 차이점이 있다는 사실을 알아차렸을 것이다.
자바와 C++에서 인스턴스 메서드의 유효 범위는 this 객체를 포함한다.
자바에서 area 메서드의 몸체는 간단히 다음과 같았을 것이다.
하지만 여러분은 자바스크립트에서 이 프로퍼티들을 사용하려면 명시적으로 this 키워드를 사용해야 한다는 점을 확인했다.
인스턴스 필드마다 앞에 this를 붙이는 것이 어색하면 메서드 안에서 width 문장을 사용할 수 있다
(6.18절에서 다루었다). 예를 들자면 다음과 같다.
4. 클래스 프로퍼티
자바에서 클래스 프로퍼티란 클래스의 각 인스턴스가 아닌 클래스 자체와 연관되어 있는 프로퍼티를 말한다. 클래스 프로퍼티는 클래스의 인스턴스가 몇 개나 생성되는지에 상관없이 한 개만 존재한다.
인스턴스 프로퍼티가 인스턴스를 통해 접근되듯이 클래스 프로퍼티는 클래스 자신을 통해 접근된다. Number.MAX_VALUE가 자바스크립트에 있는 클래스 프로퍼티의 한 예이다.
클래스 프로퍼티는 한 개의 사본만 있기 때문에 필수적으로 전역에서 접근할 수 있어야 한다.
반면 이 들이 유용한 점은 클래스와 연관되어 있고 논리적인 보호 영역(niche)이 있다는 점이다.
논리적인 보호 영역은 자바스크립트의 네임스페이스에 있는 지점인데 같은 이름을 가진 다른 프로퍼티에
의해 덮어씌워지지 않는다. 여러분은 간단히 생성자 함수의 프로퍼티를 정의하여, 자바스크립트에서도 클래스 프로퍼티를 흉내낼 수 있다.
예를 들자면, 1x1 사각형을 저장해 두기 위해 Rectangle.UNIT이라는 클래스 프로퍼티를 만들고 싶다면
다음과 같은 방식을 사용하면 된다.
Rectangle은 생성자 함수이지만 자바스크립트 함수는 객체이기 때문에
다른 객체에 프로퍼티를 만드는 것과 똑같이 함수 프로퍼티를 만들 수 있다.
5. 클래스 메서드
클래스 메서드는 클래스의 인스턴스보다 클래스 자체와 연관이 있다. 클래스 메서드는 특정 인스턴스를 통해 호출되지 않고 클래스 자체를 통해 호출된다. Date.parse() 메서드(3부에서 등장한다)는 클래스 메서드의 한 예이다. 여러분들은 항상 이 메서드를 Date 클래스의 특정 인스턴스가 아닌 Date 생성자 객체를 통해 호출해야 한다.
클래스 메서드는 생성자 함수를 통해 호출되기 때문에 this 키워드는 특정한 인스턴스를 참조하지 않는다.
대신 생성자 함수 자체를 참조한다. (일반적으로 클래스 메서드는 this를 전혀사용하지 않는다.)
클래스 프로퍼티와 마찬가지로 클래스 메서드도 전역에서 접근할 수 있다. 클래스 메서드는 특정한 객체에서 작동하는 것이 아니기 때문에, 쉽게 클래스를 통해 호출되는 함수라고 생각할 수 있다. 이들 함수와 클래스를 연관시키면 자바스크리트 네임스페이스에서 보호 영역을 제공받을 수 있으며 이를 통해 네임스페이스의 충돌을 방지할 수 있다. 자바스크립트에서 클래스 메서드를 정의하기 위해서는 함수를 생성자 함수의 프로퍼티로 만들면 된다.
[Circle 클래스 예]
예 9-1에 있는 코드는 원을 표현하기 위한 객체를 생성할 생성자 함수와 프로토타입 객체에 대한 것이다. 여기에는 인스턴스 프로퍼티와 인스턴스 메서드, 클래스 프로퍼티, 클래스 메서드가 포함되어 있다.
// 우선 생성자 함수를 정의한다.
function Circle(radius) {
this.r = radius;
// Circle.PI는 클래스 프로퍼티다. 이것은 생성자 함수의 프로퍼티다.
Circle.PI = 3.14159;
// 여기에는 원의 넓이를 계산하기 위한 인스턴스 메서드가 정의되어 있다.
Circle.prototype.area = function() { return Circle.PI * this.r * this.r; }
// 이 클래스 메서드는 Circle의 두 객체들을 받아서 더 큰 반지름을 가진 것을
// 반환한다.
Circle.max = function(a,b) {
esle return b;
// 여기에 나타나있는 코드는 위에서 정의된 필드를 사용하는 예이다.
var c = bew Circle(1.0); // Circle 클래스의 인스턴스를 하나 만든다.
c.r = 2.2; // 인스턴스 프로퍼티 r의 값을 지정한다.
var a = c.area(); // 인스턴스 메서드인 area()를 호출한다.
var x = Math.exp(Circle.PI); // 클래스 프로퍼티인 PI를 사용하여 계산을 한다.
var d = new Circle(1.2); // 다른 Circle 인스턴스를 만든다.
var bigger = Circle.max(c,d); // 클래스 메서드 max()를 사용한다.
[복소수 예]
예 9-2는 다른 예인데, 이전 예에 비해 좀 더 구체적이며 자바스크립트에서 클래스를 정의하는 방법도 보여준다. 여기에 있는 코드와 주석은 주의 깊에 공부해두는 것이 좋다.
/*
* Comple.js:
* 이 파일은 복소수를 표현하기 위한 Complex 클래스를 정의한다.
* 복소수는 실수부와 허수부의 합으로 이루어진다는 사실을 떠올려보다.
* 허수부의 i는 -1의 제곱근이다.
*/
/* 클래스를 정의할 때 가장 먼저 하는 것은 생성자 함수를 정의하는 일이다.
* 생성자는 객체의 인스턴스 프로퍼티를 초기화해야 한다.
* 이들은 각 인스턴스를 같은 클래스의 다른 인스턴스와 차별화하는
* '상태 변수(state variable)'다.
*/
function Complex(real, imaginary) {
this.y = imaginary; // 허수부에 해당하는 숫자
/* 클래스를 정의할 때 두 번째로 하는 작업은 생성자의 프로토타입 객체에
* 인스턴스 메서드(다른 프로퍼티일 수도 있다)를 정의하는 일이다.
* 이 객체에서 정의되는 프로퍼티는 클래스에 속하는 모든 인스턴스가 상속받는다.
* 인스턴스 메서드는 this 키워드에 대해 작동한다는 데 주의하라.
* 많은 메서드가 또 다른 전달인자를 필요로 하지는 않는다.
*/
// 복소수의 크기를 반환한다. 이는 해당 복소수가 복소계의 원점에서 떨어져 있는 거리를 뜻한다.
Complex.prototype.magnitude = function() {
// this와 반대인 복소수를 반환한다.
Complex.prototype.negative = function() {
// this에 복소수를 더하고 새로운 객체에 그 합을 담아서 반환한다.
Complex.prototype.add = function(that) {
// this 복소수와 다른 복소수를 곱하고 그 값을 새로운 Complex 객체로 반환한다.
Complex.prototype.multiply = function(that) {
// Complex 객체를 유용하게 사용할 수 있는 문자열로 변환한다.
// 이 메서드는 Complex 객체를 문자열로 사용할 때 호출된다.
Complex.prototype.toString = function() {
// Complex 객체가 다른 객체와 같은 값인지 검사한다.
Complex.prototype.equals = function(that) {
// 복소수의 실수부를 반환한다. 이 함수는 Complex 객체를
// 기본 타입의 값으로 다룰 때 호출된다.
Complex.prototype.valueOf = function() { return this.x; }
/* 클래스를 정의하는 세 번째 단계는 클래스 메서드와 상수, 필요한 클래스 프로퍼티를
* 생성자 함수의 프로퍼티로 정의하는 것이다(생성자의 프로토타입 객체에 속하는
* 프로퍼티 대신). 클래스 메서드는 this 키워드를 사용하지 않는다는 사실을 주목하라.
* 이 메서드는 전달인자만 다룬다.
*/
// 두 복소수를 더하고 그 결과를 반환한다.
// 이 메서드와 인스턴스 메서드인 add()와 대조해 보라.
Complex.sum = function (a, b) {
// 두 복소수를 곱하고 그 결과를 반환한다.
// 이 메서드를 인스턴스 메서드는 multiply()와 대조해 보라.
Complex.product = function(a, b) {
// 다음은 편리하게 사용할 수 있게 미리 정의된 몇 개의 복소수들이다.
// 이들은 클래스 프로퍼티로 지정되어 있으며 상수임을 나타내기 위해서
// 이름이 대문자다(비록 자바스크립트에 프로퍼티를 읽기 전용으로 만들 수는 없지만).
Complex.ZERO = new Complex(0,0);
Complex.ONE = new Complex(1,0);
Complex.I = new Complex(0,1);
6. private 멤버
자바와 C++같은 객체지향 언어들의 한 가지 공통점은 클래스의 프로퍼티를 클래스 외부의 다른 코드에서
조작할 수 없고 클래스의 메서드만 사용할 수 있게 private으로 선언할 수 있다는 것이다.
객체지향 언어에서 널리 사용되는 '데이터 캡슐화' 라는 기술은 프로퍼티를 private화하고 특별한 메서드를
통해서만 이들을 읽거나 쓸 수 있게 한다. 자바스크립트는 클로저(8.8절에서 다루는 고급 주제(advenced topic)다)를 사용하여 이를 흉내 낼 수 있지만 이를 위해서는 인스턴스마다 접근 메서드가 저장되어 있어야
한다. 이들은 프로토타입 객체에서 상속받지 못한다.
다음 코드는 위에서 설명한 방법이 자바스크립트에서 어떻게 구현되는지 보여준다.
이 코드에 구현되어 있는 Rectangle은 width와 height의 값이 바뀌지 않으며,
접근 메서드를 통해서만 이 프로퍼티들을 사용할 수 있다.
// 대신에 객체의 접근 메서드들을 정의해준다.
// 이 메서드들은 클로저이며, width와 height의 값은 메서드의 유효 범위 체인 안에 있다.
this.getWidth = function() { return w; }
this.getHeight = function() { return h; }
// 클래스의 프로토타입 객체에 일반 메서드가 올 수 있음을 주목하라.
ImmutableRectangle.prototype.area = function() {
더글러스 크록포드(Douglas Crockford)는 private 프로퍼티를 정의하기 위해서 처음 이 기술을 찾아낸
(혹은 알린) 사람으로 칭송받고 있다. 이것과 관련된 원문은 다음 링크에서 찾을 수 있다. http://www.crockford.com/javascript/private.html
'IT_Programming > JavaScript' 카테고리의 다른 글
[펌] 느긋하게 비동기식으로 JavaScript를 웹 문서에 추가하는 방법 (0) | 2010.07.09 |
---|---|
자바스크립트 완벽가이드 - 9.4 공통적인 객체 메서드 (0) | 2010.07.04 |
자바스크립트 완벽가이드 - 9.2 프로토타입과 상속 (0) | 2010.07.04 |
자바스크립트 완벽가이드 - 9.1 생성자 (0) | 2010.07.04 |
자바스크립트 완벽가이드 - 8.9 Function() 생성자 (0) | 2010.07.04 |