45. 자바스크립트 객체

소개

이전 글 에서 함수의 컨셉에 대해 소개했다. 함수를 사용함으로서 프로그램들을 논리적인 덩어리들로 쪼개서 당신의 코드를 좀더 잘 조직화하고, 쉽게 재사용할 수 있었다. 이제 당신이 자바스크립트 프로그래밍의 핵심적인 개념들을 거부감 없이 받아들일 수 있게 되었으므로, 객체 에 대해 소개함으로서 활용의 폭을 더욱 넓혀보고자 한다. 객체를 사용함으로서 함수로 정의한 기능성들을 하나로 묶을 수 있고, 또한 그것을 이곳저곳으로 전달하며 또한 참조할 수 있다. 이러한 능력은 당신이 앞으로 작성할 코드에 상당히 실질적인 의미를 갖게 될 것이다 – 비록 지금 이 순간에는 다소 모호하게 들리겠지만 말이다.

눈치채지 못했을수도 있지만, 당신은 이 시리즈를 따라 오면서 암묵적으로 객체들을 사용해 왔다. 이제 이것에 대해 좀 더 명시적인 설명을 함으로서 자바스크립트에서 객체가 어떻게 동작하는지, 그리고 객체를 통해 당신의 프로그램을 좀 더 명확하게 하고 재사용하기 쉽도록 하는 방법을 알려주겠다.

이 글의 구조는 다음과 같다:

  • 왜 객체를 사용하는가?
  • 익숙한 영역
  • 객체 생성하기
  • 자가 참조
  • 연관 배열로서의 객체
  • 객체 리터럴
  • 요약 – 배울것이 더 많이 있다
  • 읽어볼것들
  • 연습문제
다운로드하거나 직접 실행해볼 수 있는 예제가 준비되어 있다. 이것은 삼각형의 면적을 계산하는 코드를 포함하고 있는데, 객체를 사용한 것과 사용하지 않은 것이 있다. 이 코드는 아래에서 설명하는 예제들로 만들어져 있다. 삼각형 객체 예제를 실행해보라.

왜 객체를 사용하는가?

객체에 대해 주의를 기울이는 단 하나 가장 중요한 이유는, 객체를 사용함으로서 당신의 코드에서 데이터와 처리과정을 더 잘 표현할 수 있기 때문이다. 익숙한 예제로서, 당신이 삼각형에 관한 뭔가를 코딩한다고 가정하자. 삼각형은 3개의 변 을 가지고 있다는 것을 알고 있으므로, 특정한 삼각형에 대해 다루려면 당연히 3개의 변수를 만들어야 할 것이다:

// This is a triangle.
var sideA = 3;
var sideB = 4;
var sideC = 5;

자, 삼각형이 준비되었다. 하지만 아직 명확하지는 않다. 따로따로 추적해야 할 세개의 변수를 이제 만들었고, 당신이 이것을 가지고 무엇을 어떻게 하려고 했었나를 떠올리게 해 줄 주석이 있다. 이것은 그다지 명확하거나, 사용할만하다고 할 수는 없다. 어쨌든, 계속해보자. 이 “삼각형”에 대해 어떤 계산을 할 수 있을까? 면적을 알기 위해서, 다음과 같은 함수를 사용할 수 있을 것이다:

function getArea( a, b, c ) {
  // Heron의 공식을 이용해서 삼각형의 면적을 계산한다.

  var semiperimeter   =   (a + b + c) / 2;
  var calculation     =   semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c);
  return Math.sqrt( calculation );
}

alert( getArea( sideA, sideB, sideC ) );

뭔가 계산을 하기 위해서는 삼각형에 대한 모든 정보를 전달해주어야 한다는 것을 깨달았을 것이다. 삼각형과 관련된 활동(계산)은 삼각형의 데이터로부터 완전히 분리되어 있는데, 이러한 고립에는 그다지 의미가 있어 보이지 않는다.

좀 더 보자. 나는 이 함수와 변수들에 대해 상당히 범용적인 이름을 정해주었다: getArea, sideA 와 같이 말이다. 다음주에, 직사각형에 대해서 동작하도록 프로그램을 확장해야 함을 깨달았다면 어떻게 할 것인가? 아마도, 사각형에 사용하기 위해 sideA 와 sideB 같은 변수를 사용하려고 하겠지만, 이러한 변수는 이미 사용되었다. side1 과 side2 를 사용할수도 있겠지만, 이런 변수를 사용함으로서 혼동과 재난을 초래할 것이 분명함을, 당신도 이미 짐작할 것이라고 생각한다. 아마도, rectangleSideA, rectangleSideB 같은 변수들을 시도해 보다가 모순 속에 빠져들게 될 것 같다. 삼각형에 대해 이미 만들어 둔 코드를 다시 수정해서 triangleSideA 같은 것을 쓰게끔 하다가, 필경 에러를 만들어 낼 것이다. 이러한 수작업과 오류는 물론 함수의 이름에도 계속된다 – 두개의 다각형에 대해서, 그 계산이 개념적으로는 같은 것이기 때문에, 똑같이 getArea 라는 이름을 사용하고 싶지만, 그렇게 할 수 없다. 데이터를 표현할 수 있는 더 좋은 방법이 있을것이다.

몇개의 명령어들을 묶어서 단일한, 적당한 이름을 가진 활동(함수)을 만들어 내는 것이 이치에 맞는다. 마찬가지로, 몇가지 “것” 들을 하나의 단위로 묶어서 객체를 만들어내는 것이 이치에 맞는다. 자바스크립트에서 미리 정의된 기초적 데이터 타입(문자열, 상수, 불린 등)에 제한받는 대신, 객체를 만들어 사용함으로서 당신만의 데이터 타입 연결을 만들어 낼 수 있다. 객체에 사용되는 변수들은 숫자나 타입의 제한을 받지 않는다. 이렇게, 형식에 얽매이지 않는 유연함은, 프로그램을 작성할 때 당신이 다루려고 하는 것을 직접적으로 다루는 구조를 만들어낼 수 있게 하며, 또한 그것들을 자바스크립트에 내장된 기초적인것들과 마찬가지로 사용할 수 있게 해준다. 이제 삼각형과 직사각형 객체를 만들 것인데, 각각의 객체는 그 모양을 다루기 위해 필요한 모든 데이터들을 포함할 것이며, 또한 그 데이터들을 가지고 수행하려고 하는 모든 행동들도 함께 포함할 것이다. 이러한 목적을 염두에 두고, 문법을 보도록 하자.

익숙한 영역

이전 글의 마지막 실습을 보면, 다음과 같은 코드가 포함되어 있다:

var obj = document.getElementById( elementID );

그리고:

obj.style.background = 'rgb('+red+','+green+','+blue')';

자, 객체가 무엇인지 몰랐지만 이미 객체를 사용하고 있었다. 이 두개의 조각을 좀 더 자세히 분석해서 자바스크립트의 객체 문법에 대해 살펴보도록 하자.

var obj = document.getElementById( elementID ) 이 코드는 익숙해 보인다. 명령어 마지막에 포함되어 있는 괄호는 뭔가가 실행될 것이라는 의미이고, 이 실행의 결과가 obj 라는 변수에 저장될 것이라고 짐작할 수 있다. 여기에서 새로운 것은 딱 하나, 중간에 있는 점 이다. 이러한 점 표기법은 자바스크립트가 객체 안에 저장된 데이터에 접근하기 위해 사용하는 방법이다. 점 은 + 나 – 와 마찬가지로, 피연산자 사이에 있는 연산자이다.

관습적으로, 객체 안에 저장되고 점 연산자를 통해 접근하는 데이터를 property 라고 부른다. 프로퍼티는 함수가 될 수 있는데, 이 경우 method 라 부른다. 이러한 단어에 특별한 의미는 없다 – 메서드는 함수일 뿐이고, 프로퍼티는 변수일 뿐이다.

점 연산자의 왼쪽에는 객체가, 오른쪽에는 프로퍼티가 놓인다. 위의 코드에 이러한 규칙을 적용해 본다면, 자바스크립트에 내장되어 있는 document 객체의 getElementById 메서드에 접근한다고 말할 수 있을 것이다. 이것에 관해 더 자세한 내용은 이어질 DOM 여행 글에서 확인할 수 있다.

다음 코드는 조금 더 흥미롭다: 이것은 점을 두개 가지고 있다. 자바스크립트의 객체 지원 중에서 정말로 흥미로운 것 중 하나는, 점 연산자 체인을 통해 복잡한 구조 속으로 들어간다는 것이다. 짧게 말해서, 이러한 체인은 마치 var x = 2 + 3 + 4 + 5; 를 계산하고 값으로 14를 기대하는 것과 같다. 객체 참조는 자신을 반환하고, 이러한 체인이 왼쪽에서 오른쪽으로 계속 이어진다(친구들에게 이것을 설명하면서 자바스크립트가 점 연산자를 “좌측 연결 삽입 연산자” 로 사용한다고 표현해서 깊은 인상을 줄 수 있을 것이다) 이 경우, obj.style 이 먼저 평가되고, 이것을 해석하면 그것의 background 프로퍼티에 접근하는 객체가 된다. 당신이 원한다면, 괄호를 사용해서 이것을 좀 더 명시적으로 표현할 수 있다: (obj.style).background.

객체 생성

삼각형 객체를 만들기 위해, 다음의 문법을 사용해서 명시적으로 생성할 것이다:

var triangle = new Object();

triangle 는 이제 비어있는 기초이며 무언가를 그 위에 쌓아올려야 한다. 점 연산자를 통해 프로퍼티들을 추가함으로서 그렇게 할 수 있다:

triangle.sideA  =   3;
triangle.sideB  =   4;
triangle.sideC  =   5;

객체에 새로운 프로퍼티를 생성하기 위해서 다른 특별한 일을 할 필요는 없다. 자바스크립트가 점 연산자를 평가할 때, 이것은 대단히 관대한 방법을 사용한다. 존재하지 않는 프로퍼티에 접근하려고 시도한다면, 자바스크립트는 그것을 새로 만들어낸다. 존재하지 않는 프로퍼티를 읽으려 한다면 자바스크립트는 “undefined” 값을 반환한다. 이것은 편리하지만, 주의깊게 사용하지 않는다면 에러를 만들어 낸다. 따라서 오타에 주의하도록 하자.

메서드를 추가하는 것 역시 비슷하다 – 예제를 보자:

triangle.getArea    =   function ( a, b, c ) {
  // Return the area of a triangle using Heron's formula

  var semiperimeter   =   (a + b + c) / 2;
  var calculation     =   semiperimeter * (semiperimeter - a) *
                                (semiperimeter - b) * (semiperimeter - c);
  return Math.sqrt( calculation );

};      // 이 줄에 사용된 세미콜론을 눈여겨 보기 바란다. 이것은 규칙의 일부이다.

이것이 함수 선언과 무척 비슷하다고 생각한다면, 제대로 보고 있는 것이다. 그저 함수 이름이 빠졌을 뿐이다. 자바스크립트는 익명 함수라는 개념을 갖고 있는데, 함수는 이름을 가지는 대신 다른 값들과 마찬가지로 변수에 저장된다. 이 코드에서, 나는 익명 함수를 만들어내고 그것을 triangle 객체의 getArea 프로퍼티에 저장했다. 이제 객체는 데이터와 함께 함수를 데리고 다닌다.

자가 참조

삼각형 객체를 만들어 낸 목적 중 하나는, 삼각형의 데이터와, 그 데이터를 가지고 할 계산 사이의 관계를 만들어 내는 일이었는데, 아직 그것을 완성하지 못했다. triangle.getArea 메서드가 여전히 각 변의 길이를 필요로 하고 있음을 알 수 있을 것이다. 따라서 이런 식으로 명령해야 하는데:

triangle.getArea( triangle.sideA, triangle.sideB, triangle.sideC );

이것이 처음에 했던 코드보다는 나은데, 왜냐하면 이것은 데이터와 계산 사이의 관계를 명확하게 표현하고 있기 때문이다. 하지만, 그 관계는, 메서드에게 어떤 값을 가지고 계산할지를 매번 알려줄 필요가 없는 것이어야 한다. 메서드는 자신을 포함하고 있는 객체 안에서 데이터를 수집할 수 있어야 하고, 사용자에게 값을 입력할 것을 요구하지 않고 그 데이터를 사용해야 한다.

메서드가 자신을 포함한 객체 안에서 데이터를 수집할 수 있게 하는 비결은 this 키워드 안에 있다. 메서드를 정의할 때 이 키워드를 사용함으로서, 메서드가 실행될 때 같은 객체 내의 다른 프로퍼티나 메서드를 참조하도록 지정할 수 있다. this 를 사용하도록 getArea 를 다시 수정하면 다음과 같다:

triangle.getArea    =   function () {
  // Return the area of a triangle using Heron's formula

  var semiperimeter   =   (this.sideA + this.sideB + this.sideC) / 2;
  var calculation     =   semiperimeter * (semiperimeter - this.sideA) * (semiperimeter - this.sideB) * (semiperimeter - this.sideC);

  return Math.sqrt( calculation );

};      // Note the semi-colon here, it's mandatory.

여기서 보는 바와 같이, this 키워드는 거울과 비슷하게 동작한다. getArea 메서드가 실행될 때, 메서드는 객체 내부에서 sideA, sideB, sideC 프로퍼티를 검색하게 된다. 객체 내부에 프로퍼티가 이미 정의되어 있으므로, 외부에서 입력하는 값에 의존하지 않고 계산을 할 수 있다.

이것은 조금 과하게 단순화시킨 표현이다. this 키워드가 언제나 메서드를 포함하는 객체를 참조하는 것은 아니다 – 사실 이 키워드는 자신을 호출하는 문맥에 기반해서 동작한다. 모호함에 대해 사과드린다. 하지만, 이것을 다루는 것은 이 글의 초점을 벗어나는 일이 될 것이다. 아무튼, 이 글에서는, this 키워드는 항상 triangle 객체를 참조할 것이다.

연관된 배열로서의 객체

객체의 프로퍼티와 메서드에 접근하는 방법에는 점 연산자만이 있는것은 아니다. 이미 다루었던 글, 배열에 대하여를 통해서 충분히 익숙해져 있을, 대괄호 표기법script notation을 사용해서 더 효율적으로 접근할 수 있다. 짧게 말해, 당신은 객체를 연관된 – 일반적인 배열이, 값에 숫자를 할당하는 것과는 대조적으로 값에 문자열을 할당하는 – 배열이라고 생각해도 무방하다는 것이다. 이러한 표기법을 사용해서, triangle 객체를 다른 방법으로 작성할 수 있다:

var triangle = new Object();
triangle['sideA']   =   3;
triangle['sideB']   =   4;
triangle['sideC']   =   5;
triangle['getArea'] =   function ( a, b, c ) {
  // Return the area of a triangle using Heron's formula

  var semiperimeter   =   (a + b + c) / 2;
  var calculation     =   semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c);
  return Math.sqrt( calculation );

};      // Note the semi-colon here, it's mandatory.

언뜻 보기에는, 이것은 불필요하게 길게 쓴 것으로 보일 수 있다. 왜 간단한 점 연산자를 사용하지 않는가? 이러한 새로운 문법의 장점은, 프로퍼티의 이름이 프로그램 안에서 완전하게 정의될 필요가 없다는 것이다. 프로퍼티의 이름을 명시하기 위해 변수를 사용할 수 있고, 이러한 것을 허용함으로서 프로그램은 참으로 유연해질 수 있다 – 문맥에 기초해서 다양한 동작을 수행할 수 있다는 뜻이다. 예를 들어, 두개의 객체가 하나의 프로퍼티를 공유하는지 비교해 볼 수 있는 함수를 작성할 수 있다:

function isPropertyShared( objectA, objectB, propertyName ) {
  if (
     typeof objectA[ propertyName ] !== undefined
     &&
     typeof objectB[ propertyName ] !== undefined
     ) {
         alert("Both objects have a property named " + propertyName + "!");
       }
}

이러한 함수는, 점 표기법을 사용해서는 절대 만들어 낼 수 없는데, 점 연산자 뒤에는 프로퍼티의 이름을 명시적으로 적어주어야만 하기 때문이다. 당신은 이 문법을 아주 많이 사용하게 될 것이다.

참고 : 연관된 배열은 Perl 언어에서는 해쉬 라고 부르며, C# 에서는 해쉬테이블, C++ 에서는 맵, 자바 에서는 해쉬맵, Python 언어에서는 딕셔너리, 이렇게 다양하게 표현된다. 이것은 아주 강력하면서도 기초적인 프로그래밍 기법이고, 이것의 다른 이름을 이미 알고 있었을 수도 있다.

객체 리터럴

아주 친숙한 코드를 좀 더 자세히 들여다보도록 하자:

alert(“Hello world”);

alert는 함수이고, “Hello world” 라는 문자열을 인수로 가지고 호출되고 있다는 것을 알 수 있다. 여기에서 강조하고자 하는 것은, 이렇게 쓸 필요가 없다는 것인데:

var temporaryString = "Hello world";
alert(temporaryString);

자바스크립트는, 따옴표(” ”)로 둘러싸인 모든 것은 문자열로 취급되어야 한다고 이해하며, 그것이 어디에 쓰였든 간에 올바르게 처리할 수 있는 필요한 모든 일을 백그라운드로 처리한다. 문자열이 만들어지고, 그것이 함수의 인수로 전달되는 과정이 한번에 이루어졌다. 정형적으로는, “Hello world” 이것은 문자열 리터럴 을 참조한다: 문자열을 생성하기 위해 필요한 모든 것을 완전하게 입력한 것이다.

자바스크립트는, “객체 리터럴” 에 대해서도 비슷한 문법을 가지고 있으며 이것을 통해 문법적인 제약에 지나치게 구애받지 않고 객체를 생성할 수 있다. 객체 리터럴을 가지고, 위에서 만들어 낸 객체를 다시 작성해 보자:

var triangle = {
  sideA:      3,
  sideB:      4,
  sideC:      5,
  getArea:    function ( a, b, c ) {
    // Return the area of a triangle using Heron's formula

    var semiperimeter   =   (a + b + c) / 2;
    var calculation     =   semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c);
    return Math.sqrt( calculation );

  }
};

역주 : 이러한 표기법을 JavaScript Object Notation – 자바스크립트 객체 표기법 – JSON 이라고 부른다.

이러한 문법은 명확하다: 객체 리터럴은 객체의 시작과 끝을 중괄호로 둘러싸며, 중괄호 안에는 프로퍼티 이름 : 프로퍼티 값 쌍이 콤마로 구분되어 나열된다. 이러한 문법을 사용함으로서, 프로그램의 매 행마다 객체 이름을 반복하는 지루한 작업에서 해방되어 쉽게 프로그램을 구조화할 수 있다.

한가지 조심할 것이 있다 : 객체 리터럴의 프로퍼티 리스트 중 마지막 것(여기에서는, getArea) 뒤에 쉼표를 넣는 실수를 아주 흔하게 범한다. 쉼표는 프로퍼티 사이 에만 넣어야 한다 – 마지막에 넣어진 필요없는 쉼표는 에러를 만든다. 특히, 코드를 이후에 수정하면서 프로퍼티를 추가하거나 지울 때 이런 실수를 범하기 쉬우므로, 쉼표가 정확한 위치에 있도록 주의를 기울일 필요가 있다.

요약 – 배울 것이 더 많이 있다

이 글은, 자바스크립트에서 객체가 갖는 가능성과 한계에 대해서 정말로 수박 겉핧기 정도밖에는 되지 않는다. 이 글을 완전히 읽었다면, 스스로 객체를 생성하여 프로퍼티와 메서드를 추가하고 그것을 자가참조의 형태로 사용하는데에는 익숙해 질 것이다. 이것 외에도 정말로 많은 것들이 있지만, 그중 어떤것도 필수적 이지는 않다. 이 글은 긴 여행의 출발점으로서 기획되었으며, 이후 당신이 길을 헤쳐나가는데 필요로 할 도구를 제공하는 목적으로 작성되었다.

읽어볼 것들

연습문제

  • 객체의 프로퍼티를 참조하기 위해 점 표기법 대신 대괄호 표기법을 사용해야 할 때는 어떤 경우인가?
  • 객체는 어떻게 자신을 참조하는가? 왜 이것이 중요한가?
  • 객체 리터럴이란 무엇인가? 객체 리터럴을 생성할때, 쉼표를 어디에 사용해야 하는가?
  • 삼각형을 표현하고, 넓이를 계산하는 객체를 만들었다. 직사각형에 대해서도 그렇게 해 보기를 바란다. this 키워드를 사용해서, 직사각형의 getArea 메서드가 그 데이터를 필요하지 않은 곳에 전달하는 것을 막도록 해 보라.

저자에 관해

Mike West 는 경험많고 성공한 웹 개발자의 탈을 쓴 철학과 학생이다. 그는 웹 분야에서 10년 넘게 일해오고 있으며, 야후! 의 유럽 뉴스 사이트 개발의 책임을 맡은 팀에서 오랫동안 일했다.

2005년, 텍사스 교외의 광활한 평야를 떠나, Mike 는 독일의 뮌헨에 정착했고 독일어를 익히기 위한 그의 고군분투는 다행히 성공적이다. mikewest.org 가 웹에서의 그의 집이며, 후대를 위해 자신의 글과 링크들을 (천천히)모아 가고 있다. 그가 작성한 코드들은 GitHub 에서 찾아볼 수 있다.

  1. 역주 : 캡슐화 란, 함수 안에서 벌어지는 일에 대해 함수 밖에서는 알 방법이 없도록 분리하는 개념이라고 이해하면 된다. 조금 더 번거로울지는 모르겠으나, 에러는 확실히 줄어든다
이 글을 다 읽어주셨다면, 댓글을 남겨주세요. 좋았다라는 격려도 좋고, 잘못된 부분을 지적해 주시는 것도 좋습니다. 마음에 드셨다면 아래 Like 버튼을 눌러서 페이스북과 트위터로 소개해 주시면 더욱 좋겠습니다.