[자바스크립트] 객체지향 프로그래밍

류명운

·

2016. 8. 14. 01:55

반응형

[자바스크립트] 객체지향 프로그래밍의 문법 학습에 대한 포스팅입니다.


들어가기에 앞서

객체지향 프로그래밍(Object-Oriented Programming)은 크고 견고한 프로그램을 만들기 위한 개발자들의 노력의 산물이다.

좀 더 나은 프로그램을 만들기 위한 프로그래밍 패러다임으로 로직을 상태(sate)와 행위(behave)로 이루어진 객체로 만드는 것이다. 그리고 이 객체들은 마치 레고 블럭처럼 조립해서 하나의 프로그램을 만드는 것이 객체지향 프로그래밍이라고 할 수 있다.

쉽게 생각하자. 객체는 변수와 메소드를 그룹핑한 것이다.


1. 생성자와 new

객체란 서로 연관된 변수와 함수를 그룹핑한 그릇이라고 할 수 있다. 객체 내의 변수를 프로퍼티(property) 함수를 메소드(method)라고 부른다. 객체를 만드는 다음 예제코드를 확인하자.

var person = {}
person.name = 'egoing';
person.introduce = function(){
    return 'My name is '+this.name;
}
document.write(person.introduce());

→My name is egoing

: 객체를 만드는 과정이 분산되어 있다. 객체를 정의 할 때 값을 셋팅하도록 코드를 바꿔보자.

var person = {
    'name' : 'egoing',
    'introduce' : function(){
        return 'My name is '+this.name;
    }
}
document.write(person.introduce());

→My name is egoing


위 예제코드가 만약 다른 사람의 이름을 담을 객체가 필요하다면 객체의 정의를 반복해야 할 것이다. 객체의 구조를 재활용할 수 있는 방법이 필요하다. 이 때 사용하는 것이 생성자다.


1. 1 생성자

생성자(constructor)는 객체를 만드는 역할을 하는 함수다. 자바스크립트에서 함수는 재사용 가능한 로직의 묶음이 아니라 객체를 만드는 창조자라고 할 수 있다.

함수를 호출할 때 new을 붙이면 새로운 객체를 만든 후에 이를 리턴한다. 다음 예제코드는 새로운 객체를 변수 p에 담았다.

function Person(){}
var p = new Person();
p.name = 'egoing';
p.introduce = function(){
    return 'My name is '+this.name; 
}
document.write(p.introduce());

→My name is egoing

여러사람을 위한 객체를 만든다면 다음 예제코드와 같이 작성해야 할 것이다.

function Person(name){
    this.name = name;
    this.introduce = function(){
        return 'My name is '+this.name; 
    }   
}
var p1 = new Person('egoing');
document.write(p1.introduce()+"
"); var p2 = new Person('leezche'); document.write(p2.introduce());

→My name is egoing

→My name is leezche

: 생성자 내에서 이 객체의 프로퍼티를 정의하고 있다. 이러한 작업을 초기화라고 한다. 이를 통해서 코드의 재사용성이 대폭 높아졌다.

* 코드를 통해서 알 수 있듯이 생성자 함수는 일반함수와 구분하기 위해서 첫 글자를 대문자로 표시한다.


1. 2 자바스크립트 생성자의 특징

일반적인 객체지향 언어에서 생성자는 클래스의 소속이다. 하지만 자바스크립트에서 객체를 만드는 주체는 함수다. 함수에 new를 붙이는 것을 통해서 객체를 만들 수 있다는 점은 자바스크립트에서 함수의 위상을 암시하는 단서이면서 또 자바스크립트가 추구하는 자유로움을 보여주는 사례라고 할 수 있다.



2. 전역객체


2. 1 전역객체란?

전역객체(Global object)는 특수한 객체다. 모든 객체는 이 전역객체의 프로퍼티다.

function func(){
    alert('Hello?');    
}
func();
window.func();

→Hello

: func();와 window.func();는 모두 실행이 된다. 모든 전역변수와 함수는 사실 window 객체의 프로퍼티다. 객체를 명시하지 않으면 암시적으로 window의 프로퍼티로 간주된다.

var o = {'func':function(){
    alert('Hello?');
}}
o.func();
window.o.func();

자바스크립트에서 모든 객체는 기본적으로 전역객체의 프로퍼티임을 알 수 있다.


2. 2 전역객체 API

ECMAScript에서는 전역객체의 API를 정의해두었다. 그 외의 API는 호스트 환경에서 필요에 따라서 추가로 정의하고 있다. 이를테면 웹브라우저 자바스크립트에서는 alert()이라는 전역객체의 메소드가 존재하지만 node.js에는 존재하지 않는다. 또한 전역객체의 이름도 호스트환경에 따라서 다른데, 웹브라우저에서 전역객체는 window이지만 node.js에서는 global이다.



3. this

this는 함수 내에서 함수 호출 맥락(context)를 의미한다. 맥락이라는 것은 상황에 따라서 달라진다는 의미인데 즉 함수를 어떻게 호출하느냐에 따라서 this가 가리키는 대상이 달라진다는 뜻이다. 함수와 객체의 관계가 느슨한 자바스크립트에서 this는 이 둘을 연결시켜주는 실질적인 연결점의 역할을 한다.


3. 1 함수호출

함수를 호출했을 때 this는 무엇을 가리키는지 다음 예제코드를 확인하자.

function func(){
    if(window === this){
        document.write("window === this");
    }
}
func(); 

→window === this

: this는 전역객체인 window와 같다.


3. 2 메소드의 호출

다음 예제코드를 확인하여 메소드의 호출을 이해하자

var o = {
    func : function(){
        if(o === this){
            document.write("o === this");
        }
    }
}
o.func();

→o === this

8줄 코드를 보면 객체 o에 소속된 func() 메소드를 호출하였고 호출된 메소드 내의 this는 해당 메소드가 소속된 함수를 가리킨다는 것을 알 수 있다.

* 메소드는 객체에 소속된 함수를 뜻한다.


3. 3 생성자의 호출

다음 예제코드는 함수를 호출했을 때와 new를 이용해서 생성자를 호출했을 때의 차이를 보여준다.

var funcThis = null; 
 
function Func(){
    funcThis = this;
}
var o1 = Func();
if(funcThis === window){
    document.write('window 
'); } var o2 = new Func(); if(funcThis === o2){ document.write('o2
'); }

→window o2

: 생성자는 빈 객체를 만든다. 그리고 이 객체내에서 this는 만들어진 객체를 가르킨다. 이것은 매우 중요한 사실이다. 생성자가 실행되기 전까지는 객체는 변수에도 할당될 수 없기 때문에 this가 아니면 객체에 대한 어떠한 작업을 할 수 없기 때문이다.

function Func(){
    document.write(o);
}
var o = new Func();

→undefined


3. 4 apply, call

함수지향 프로그래밍에서 살짝 다루었지만, 함수는 객체이다. 함수의 메소드(객체에 소속된 함수)인 apply, call을 이용하면 this의 값을 제어할 수 있다.

apply 메소드를 이용하여 함수의 맥락을 결정하는 다음 예제코드를 확인해보자.

var o = {}
var p = {}
function func(){
    switch(this){
        case o:
            document.write('o
'); break; case p: document.write('p
'); break; case window: document.write('window
'); break; } } func(); func.apply(o); func.apply(p);

→window o p


3. 5 this 정리

var obj = { //객체
    met : function(){ //메소드
        if(obj === this){
            console.log("obj === this");
        }
    }
}
obj.met();

→obj === thios

: met()를 호출하였을때 met()라는 method의 객체는 obj이므로 this는 obj가 된다.



4. 상속


4. 1 상속(inheritance)이란?

객체는 연관된 로직들로 이루어진 작은 프로그램이라고 할 수 있다. 상속은 객체의 로직을 그대로 물려 받는 또 다른 객체를 만들 수 있는 기능을 의미한다. 단순히 물려받는 것이라면 의미가 없을 것이다. 기존의 로직을 수정하고 변경해서 파생된 새로운 객체를 만들 수 있게 해준다.

다음 예제코드를 살펴보자.

function Person(name){
    this.name = name;
    this.introduce = function(){
        return 'My name is '+this.name; 
    }   
}
var p1 = new Person('myeonguni');
document.write(p1.introduce()+"
");

→My name is myeonguni

위의 예제코드를 상속을 위한 기본적인 준비로 아래와 같이 바꿔보자.

* 이번 포스팅에서는 상속의 개념에 대해 다루고 원리에 대해서는 다음 장(5. prototype)에서 자세히 다루도록 한다.

function Person(name){
    this.name = name;
}
Person.prototype.name=null;
Person.prototype.introduce = function(){
    return 'My name is '+this.name; 
}
 
function Programmer(name){
    this.name = name;
}
Programmer.prototype = new Person();
 
var p1 = new Programmer('myeonguni');
document.write(p1.introduce()+"
");

→My name is myeonguni

: Programmer이라는 생성자를 만든다음 이 생성자의 prototype과 Person의 객체를 연결했더니 Programmer 객체도 메소드 introduce를 사용할 수 있게 되었다.

: 하지만 위 예제코드는 Programmer가 Person의 기능을 상속하고 있지만 단순히 똑같은 기능만을 갖게 되었다. 상속이 만약 이처럼 단순히 똑같은 기능만을 갖게된다면 상속의 의의는 사라질 것이다. 부모의 기능을 계승 발전할 수 있는 것이 상속의 가치다.

* 상속받은 부모의 기능을 계승 발전할 수 있는 다음 예제코드를 살펴보자.

function Person(name){
    this.name = name;
}
Person.prototype.name=null;
Person.prototype.introduce = function(){
    return 'My name is '+this.name; 
}
 
function Programmer(name){
    this.name = name;
}
Programmer.prototype = new Person();
Programmer.prototype.coding = function(){
    return "hello world";
}
 
function Designer(name){
    this.name = name;
}
Designer.prototype = new Person();
Designer.prototype.design = function(){
    return "beautiful!";
}
 
var p1 = new Programmer('myeonguni');
document.write(p1.introduce()+"    ");
document.write(p1.coding()+"
"); var p2 = new Designer('egoing'); document.write(p1.introduce()+" "); document.write(p1.design()+"
");

→My name is myeonguni hello world

→My name is egoing beautiful

: Programmer와 Designer는 Person의 기능을 가지고 있으면서 Person이 가지고 있지 않은 메소드 각 coding, design을 가지고 있다.

* 자바스크립트에서 상속은 prototype을 통해 구현할 수 있다. 자바스크립트는 prototype based language이다. 그만큼 prototype은 자바스크립트의 함수와 객체의 개념에서 아주 중요한 역할을 한다.



5. prototype

상속의 구체적인 수단인 prototype에 대해서 자세히 알아보자. prototype이란 말 그대로 객체의 원형이라고 할 수 있다. 함수는 객체다. 그러므로 생성자로 사용될 함수도 객체다. 객체는 프로퍼티(property)를 가질 수 있는데 prototype이라는 프로퍼티는 그 용도가 약속되어 있는 특수한 프로퍼티다.

prototype에 저장된 속성들은 생성자를 통해서 객체가 만들어질 때 그 객체에 연결된다.

다음 예제코드를 확인해보자.

function Ultra(){}
Ultra.prototype.ultraProp = true;
 
function Super(){}
Super.prototype = new Ultra();
 
function Sub(){}
Sub.prototype = new Super();
 
var o = new Sub();
console.log(o.ultraProp);

→ture

: 생성자 Sub를 통해서 만들어진 객체 o가 Ultra의 프로퍼티 ultraProp에 접근 가능한 것은 prototype 체인으로 Sub와 Ultra가 연결되어 있기 때문이다. 내부적으로는 아래와 같은 일이 일어난다.

  • 1) 객체 o에서 ultraProp를 찾는다.
  • 2) 없다면 Sub.prototype.ultraProp를 찾는다.
  • 3) 없다면 Super.prototype.ultraProp를 찾는다.
  • 4) 없다면 Ultra.prototype.ultraProp를 찾는다.

: prototype는 객체와 객체를 연결하는 체인의 역할을 하는 것이다. 이러한 관계를 prototype chain이라고 한다.

* Super.prototype = Ultra.prototype 으로하면 안된다. 이렇게하면 Super.prototype의 값을 변경하면 그것이 Ultra.prototype도 변경하기 때문이다. Super.prototype = new Ultra();는 Ultra.prototype의 원형으로 하는 객체가 생성되기 때문에 new Ultra()를 통해서 만들어진 객체에 변화가 생겨도 Ultra.prototype의 객체에는 영향을 주지 않는다.



6. 표준 내장 객체의 확장

표준 내장 객체(Standard Built-in Object)는 자바스크립트가 기본적으로 가지고 있는 객체들을 의미한다. 내장 객체가 중요한 이유는 프로그래밍을 하는데 기본적으로 필요한 도구들이기 때문이다. 결국 프로그래밍이라는 것은 언어와 호스트 환경에 제공하는 기능들을 통해서 새로운 소프트웨어를 만들어내는 것이기 때문에 내장 객체에 대한 이해는 프로그래밍의 기본이라고 할 수 있다.

자바스크립트는 아래와 같은 내장 객체를 가지고 있다.

  • Object
  • Function
  • Array
  • String
  • Boolean
  • Math
  • Date
  • RegExp

원한다면 자바스크립트의 내장 객체와 같은 것을 만들 수 있다. 이것을 사용자 정의 객체라고 한다.

그럼 내장 객체 중 하나인 배열을 확장해보자.

다음 예제코드는 배열에서 특정한 값을 랜덤하게 추출하는 것이다.

var arr = new Array('seoul','new york','ladarkh','pusan', 'Tsukuba');
function getRandomValueFromArray(haystack){
    var index = Math.floor(haystack.length*Math.random());
    return haystack[index]; 
}
console.log(getRandomValueFromArray(arr));

: 위 예제코드 처럼 작성하면 랜덤하게 값이 추출되어 출력되는 것을 확인할 수 있다. 하지만 조금 더 세련된 방법은 이 함수를 배열 객체에 포함시키는 것이다. 그렇게하면 마치 배열에 내장된 메소드인 것처럼 위의 기능을 사용할 수 있다.

위 예제코드를 확장하여 랜덤한 값을 추출하는 함수를 모든 배열의 객체가 가질 수 있도록 해보자. 다음 예제코드를 확인하자.

Array.prototype.rand = function(){
    var index = Math.floor(this.length*Math.random());
    return this[index];
}
var arr = new Array('seoul','new york','ladarkh','pusan', 'Tsukuba');
console.log(arr.rand());



7. Object

Object 객체는 객체의 가장 기본적인 형태를 가지고 있는 객체이다. 다시 말해서 아무것도 상속받지 않는 순수한 객체다. 자바스크립트에서는 값을 저장하는 기본적인 단위로 Object를 사용한다.

동시에 자바스크립트의 모든 객체는 Object 객체를 상속 받는데, 그런 이유로 모든 객체는 Object 객체의 프로퍼티를 가지고 있다.


7. 1 Object 객체의 메뉴얼을 읽는 방법

1) https://developer.mozilla.org/en-US/ 접속

2) WEB PLATFORM 카테고리의 JavaScript 클릭

3) Reference 카테고리의 JavaScript reference 클릭

4) Fundamental objects 카테고리의 object 클릭

5) 필요한 Properties 혹은 Methods 학습


7. 2 Object 객체의 확장

Object 객체를 확장하면 모든 객체가 접근할 수 있는 API를 만들 수 있다. 다음 예제코드를 통해 Object 객체를 확장하는 방법을 확인해보자.

Object.prototype.contain = function(neddle) {
    for(var name in this){
        if(this[name] === neddle){
            return true;
        }
    }
    return false;
}
var o = {'name':'egoing', 'city':'seoul'}
console.log(o.contain('egoing'));
var a = ['egoing','leezche','grapittie'];
console.log(a.contain('leezche'));

→true true

* Object 객체는 확장하지 않는 것이 바람직하다. 왜냐하면 모든 객체에 영향을 주기 때문에 혼란을 줄 수 있다. 이 경우의 문제를 회피하기 위해서는 프로퍼티의 해당 객체의 소속인지를 체크해볼 수 있는 hasOwnProperty를 사용할 수 있다.

* hasOwnProperty는 인자로 전달된 속성의 이름이 객체의 속성인지 여부를 판단한다. 만약 prototype으로 상속 받은 객체라면 false가 된다.



8. 데이터 타입

데이터 타입이란 데이터의 형태를 의미한다. 데이터 타입은 크게 두가지로 구분할 수 있다. 객체와 객체가 아닌 것.


8. 1 원시 데이터 타입

원시 데이터 타입(primitive type)은 기본 데이터 타입이라고 불리며 객체가 아닌 데이터 타입을 말한다. 다음이 이에 해당한다.

  • 숫자
  • 문자열
  • boolean(true/false)
  • null
  • undefined


8. 2 레퍼 객체

var str = 'coding';
console.log(str.length);        // 6
console.log(str.charAt(0));     // "C"

: 위 예제코드의 문자열은 분명히 프로퍼티와 메소드가 있다. 그렇다면 객체다. 그런데 왜 문자열이 객체가 아닌 원시 데이터 타입이라고 할까? 그것은 내부적으로 문자열이 원시 데이터 타입이고 문자열과 관련된 어떤 작업을 하려고 할 때 자바스크립트는 임시로 문자열 객체를 만들고 사용이 끝나면 제거하기 때문이다. 이러한 처리는 내부적으로 일어나며 몰라도 된다. 하지만 원시 데이터 타입과 객체는 좀 다른 동작 방법을 가지고 있기 때문에 이들을 분별하는 것은 결국엔 필요하다.

다음 예제코드를 확인해보자.

var str = 'coding';
str.prop = 'everybody';
console.log(str.prop);      // undefined

: str.prop를 하는 순간에 자바스크립트 내부적으로 String 객체가 만들어진다. prop 프로퍼티는 이 객체에 저장되고 이 객체는 곧 제거 된다. 그렇기 때문에 prop라는 속성이 저장된 객체는 존재하지 않게된다. 이러한 특징은 일반적인 객체의 동작 방법과는 다르다. 하지만 문자열과 관련해서 필요한 기능성을 객체지향적으로 제공해야 하는 필요 또한 있기 때문에 원시 데이터 형을 객체처럼 다룰 수 있도록 하기 위한 객체를 자바스크립트는 제공하고 있는데 그것이 레퍼 객체(wrapper object)다.

레퍼 객체로는 String, Number, Boolean이 있다. null과 undefined는 레퍼 객체가 존재하지 않는다.



9. 참조


9. 1 참조와 복제

전자화된 시스템의 가장 중요한 특징은 복제다. 현실의 사물과 다르게 전자화된 시스템 위의 데이터를 복제하는데는 비용이 거의 들지 않는다. 바로 이러한 특징이 소프트웨어를 기존의 산업과 구분하는 가장 큰 특징일 것이다.

var a = 1;
var b = a;
b = 2;
console.log(a);

→1

: 결과는 당연히 1이다. 값을 변경한 것은 변수 b이기 때문에 변수 a에 담겨있는 값은 그대로이다. 변수 b의 값에 변수 a의 값이 복제된 것이다.

var a = {'id':1};
var b = a;
b.id = 2;
console.log(a.id);

→2

: 위 예제코드는 그 이전의 예제코드와 놀라운 차이점이 있다. 변수 b에 담긴 객체의 id 값을 2로 변경했을 뿐인데 a.id의 값도 2가 된 것이다. 이것은 변수 b와 변수 a에 담긴 객체가 서로 같다는 것을 의미한다. 이것이 바로 참조(reference)이다.

앞서 살펴본 예제코드는 원시 데이터형(기본 데이터형, Primitive Data Types)이였지만, 바로 위 예제코드에서 사용한 변수의 타입은 객체이다. 객체는 다른 말로는 참조 데이터 형(참조 자료형)이라고도 부른다. 기본 데이터형은 복제 되지만 참조 데이터형은 참조된다. 모든 객체는 참조 데이터형이다.

* 정리하자면 변수에 담겨있는 데이터가 원시형이면 그 안에는 실제 데이터가 들어있고, 객체면 변수 안에는 데이터에 대한 참조 방법이 들어있다고 할 수 있다.


9. 2 함수

일종의 변수할당이라고 할 수 있는 메소드의 매개변수는 어떻게 동작하는가를 예제코드를 통해 살펴보자.

다음 예제코드는 원시 데이터 타입을 인자로 넘겼을 때의 동작 모습이다.

var a = 1;
function func(b){
    b = 2;
}
func(a);
console.log(a);

→1

다음 예제코드는 참조 데이터 타입을 인자로 넘겼을 때의 동작 모습이다.

var a = {'id':1};
function func(b){
    b = {'id':2};
}
func(a);
console.log(a.id);

→1

: 함수 func의 파라미터 b로 전달된 값은 객체 a이다. (b = a) b를 새로운 객체로 대체하는 것은 (b = {'id':2}) b가 가르키는 객체를 변경하는 것이기 때문에 객체 a에 영향을 주지 않는다.

하지만 아래 예제코드는 다르다.

var a = {'id':1};
function func(b){
    b.id = 2;
}
func(a);
console.log(a.id);

→2

: 파라미터 b는 객체 a의 레퍼런스다. 이 값의 속성을 바꾸면 그 속성이 소속된 객체를 대상으로 수정작업을 한 것이 되기 때문에 b의 변경은 a에도 영향을 미치게 된다.



반응형