JavaScript

[JavaScript] 객체지향 프로그래밍

자바스크립트의 6가지 자료형 : 숫자, 문자열, 불리언, 함수, 객체, 정의되지 않은 자료형
이번 장에서는 객체를 다룹니다.

객체 개요

배열과의 비교

var array = ['사과', '바나나', '망고', '딸기'];
// 배열: 인덱스로 접근 (ex: array[0], array[1], ...)

var product = {
    name: '7D 건조 망고',
    kind: '당절임',
    ingredient: '망고, 설탕, 메타중아황산나트륨, 치자황색소',
    origin: '필리핀'
};
// 객체: 키로 접근
// (ex1: product['name'], product['kind'], ...)
// (ex2: product.name, product.kind, ...)

 

객체의 속성과 메소드

var person = {
    // 속성
    name: '홍길동',
    gender: '남자',
    
    // 메소드
    eat: function(food) {
        alert(this.name + '이 ' + food + '을/를 먹습니다.');
    },
    getGender: function() {
        alert(this.name + '은 ' + this.gender + '입니다.');
    }
};

person.eat('밥');
person.getGender();

배열 내부에 있는 값 하나하나를 요소라고 부르듯이 객체 내부에 있는 값 하나하나를 속성(property)이라고 부릅니다. 그리고 속성중에서 함수 자료형인 속성을 특별히 메소드(method)라고 부릅니다.

소스코드를 보면 this라는 키워드가 나오는데 this는 자기 자신의 객체를 의미하는 키워드입니다. 즉, this.name은 '홍길동'을 나타냅니다. 그리고 보통 많은 프로그래밍 언어들은 this라는 키워드를 생략해서 사용할 수 있는 경우가 많은데 자바스크립트는 this 키워드를 생략할 수 없습니다.

 

객체와 반복문

var product = {
    name: '7D 건조 망고',
    kind: '당절임',
    ingredient: '망고, 설탕, 메타중아황산나트륨, 치자황색소',
    origin: '필리핀'
};

var output='';
for(var key in product) {
    output += key + ': ' + product[key] + '\n';
}
alert(output);

객체에서 반복문을 사용하려면 일반적인 반복문은 사용할 수 없고 for in 반복문을 사용해야 합니다.

 

키워드(in, with)

var product = {
    name: '7D 건조 망고',
    kind: '당절임',
    ingredient: '망고, 설탕, 메타중아황산나트륨, 치자황색소',
    origin: '필리핀',
    
    getName: function() {
        return this.name;
    }
};

alert('name' in product); // true
alert('shop' in product); // false
alert('getName' in product); // true

in 키워드는 특정 속성 또는 메소드가 그 객체에 있는지 판별합니다.

 

var product = {
    name: '7D 건조 망고',
    kind: '당절임',
    ingredient: '망고, 설탕, 메타중아황산나트륨, 치자황색소',
    origin: '필리핀',
    
    getName: function() {
        return this.name;
    }
};

with(product) {
    alert(name);
    alert(kind);
    alert(ingredient);
    alert(origin);
    alert(getName());
}

with 키워드는 product.name 처럼 써야할 것을 name만 사용할 수 있게 도와줍니다.

만약 객체의 속성 이름과 외부 변수의 이름이 같으면 충돌이 발생합니다. 이 경우 객체의 속성이 우선적으로 선택됩니다. 만약 외부의 변수를 사용하고 싶다면 window.변수이름 으로 사용하면 됩니다. window는 자바스크립트 최상위 객체입니다.
var product = {
    name: '7D 건조 망고',
    kind: '당절임',
    ingredient: '망고, 설탕, 메타중아황산나트륨, 치자황색소',
    origin: '필리핀',
    
    getName: function() {
        return this.name;
    }
};

var name = '외부변수 이름'; // name 이름 충돌
with(product) {
    alert(name);        // 7D 건조 망고
    alert(window.name); // 외부변수 이름
}

 

 

동적 속성 추가와 제거

// 비어있는 상태의 객체 생성
var student = {};

// 동적으로 속성 추가
student.name = '김민우';
student.grade = 3;
student.major = '컴퓨터 공학';

// 동적으로 메소드 추가
student.toString = function() {
    var output='';
    for(var key in student) {
        if(key != 'toString')
            output += key + '\t' + student[key] + '\n';
    }
    return output;
};

// 출력 확인
alert(student.toString());

// 동적으로 속성 제거
delete(student.grade);

// 출력 확인(toString()을 굳이 사용하지 않아도 출력됩니다.)
alert(student);

주석에 보이는 것처럼 속성이나 메소드를 선언, 정의하면 동적으로 속성이 추가되고 delete 키워드를 사용하면 속성이 제거됩니다.

코드의 마지막 부분에서 toString()을 사용하지 않아도 출력이 되었습니다. 객체를 특별한 과정 없이 바로 출력하게 되면 toString()메소드가 자동으로 호출됩니다. 즉 어떤 객체를 문자열로 바꾸는 작업을 하고 싶다면 toString()메소드를 적극 활용하는 것이 좋습니다.

 

 

객체의 생성

객체를 리턴하는 함수

function makeStudent(name, korean, math, english, science) {
    var willReturn = {
        name: name,
        korean: korean,
        math: math,
        english: english,
        science: science,
        
        getSum: function() {
            return this.korean + this.math + this.english + this.science;
        },
        getAverage: function() {
            return this.getSum() / 4;
        },
        toString: function() {
            return this.name + '\t' + this.getSum() + '\t' + this.getAverage();
        }
    };
    return willReturn;
}

var student = makeStudent('김민우', 96, 98, 92, 97);
alert(student);

함수 makeStudent는 인자로 전달받은 값을 이용하여 객체를 만들어 리턴합니다. 이렇게 하면 같은 형식의 객체를 손쉽게 만들 수 있습니다. 하지만 이 방법은 실제로 거의 사용하지 않습니다. 앞으로 살펴볼 생성자 함수와 프로토타입을 이용하여 객체를 만듭니다.

 

생성자 함수

function Student(name, korean, math, english, science) {
    this.name = name;
    this.korean = korean;
    this.math = math;
    this.english = english;
    this.science = science;
    
    this.getSum = function() {
        return this.korean + this.math + this.english + this.science;
    };
    this.getAverage = function() {
        return this.getSum() / 4;
    };
    this.toString = function() {
        return this.name + '\t' + this.getSum() + '\t' + this.getAverage();
    };
}

var student = new Student('김민우', 96, 98, 92, 97);
alert(student);

new 키워드를 통해 생성자 함수를 호출하면 객체가 생성됩니다. 하지만 객체를 리턴하는 함수와 큰 차이가 없어 보입니다. 이제 프로토타입을 살펴봅시다.

보통 일반적인 함수이름은 소문자로 시작하는데에 반해 생성자 함수는 대문자로 시작합니다. 꼭 그래야 하는 것은 아니지만 개발자 사이 간의 규칙이라고 볼 수 있으므로 꼭 지켜주시는 것이 좋습니다.

 

프로토타입

만약 생성자 함수를 통해 여러 개의 객체를 만든다고 생각해 봅시다. 각 객체는 서로 다른 속성 값들을 가질 것이지만 메소드는 모두 같은 메소드를 갖습니다. 즉 객체를 생성할 때마다 같은 메소드를 반복적으로 생성하는, 메모리 측면에서 아주 비효율적인 작업을 하고 있었던 것입니다. 이를 해결하려면 프로토타입(Prototype)을 이용하면 됩니다. 프로토타입은 생성자 함수를 통해 생성된 객체들이 공통으로 가지는 공간입니다. 즉 메소드를 객체로부터 분리시켜 프로토타입이라는 별도의 메모리 공간에 저장해 놓는 것입니다. 그리고 모든 객체는 프로토타입에 존재하는 메소드를 공유하게 됩니다.

function Student(name, korean, math, english, science) {
    this.name = name;
    this.korean = korean;
    this.math = math;
    this.english = english;
    this.science = science;
}
Student.prototype.getSum = function() {
    return this.korean + this.math + this.english + this.science;
};
Student.prototype.getAverage = function() {
    return this.getSum() / 4;
};
Student.prototype.toString = function() {
    return this.name + '\t' + this.getSum() + '\t' + this.getAverage();
};

var students = [];
students.push(new Student('김일등', 96, 98, 92, 97));
students.push(new Student('김이등', 86, 84, 81, 87));
for(var i in students) {
    alert(students[i]);
}

위와 같이 동적으로 추가하는 방식을 이용하기 때문에 이미 존재하는 객체에 메소드를 추가로 제공할 수도 있습니다.

 

instanceof 키워드

function Student(name) { this.name = name; }
var student = new Student('김민우');
alert(student instanceof Student);  // true
alert(student instanceof Number);   // false

instanceof 키워드는 해당 객체가 어떤 생성자 함수를 통해 생성됐는지 확인할 때 사용합니다.

 

 

캡슐화

캡슐화란 잘못 사용될 수 있는 객체의 특정 부분을 사용자가 직접 사용할 수 없게 막는 기술입니다. 즉 만일의 상황을 대비해서 특정 속성이나 메소드를 사용자가 사용할 수 없게 숨겨 놓는 것입니다. 아래 예제는 사각형의 생성자 함수입니다. 길이에는 양수만 올 수 있다는 것에 유의해서 코드를 보세요.

function Rectangle(w, h) {
    if(w <= 0 || h <= 0) {
        throw '길이는 양수이어야 합니다.';
    }
    // 변수의 선언
    // 지금까지 속성을 추가할 때 this.width = w;처럼 사용하였습니다.
    var width = w;
    var height = h;
    
    //  메소드 게터(getter)와 세터(setter)의 선언
    this.getWidth = function() {
        return width;
    };
    this.getHeight = function() {
        return height;
    };
    this.setWidth = function(val) {
        if(val <= 0) {
            throw '길이는 양수이어야 합니다.';
            // 일단은 throw는 웹페이지 오류를 발생시키는 키워드라고 생각합시다.
        }
        else {
            width = val;
        }
    };
    this.setHeight = function(val) {
        if(val <= 0) {
            throw '길이는 양수이어야 합니다.';
        }
        else {
            height = val;
        }
    };
}

// 기타 메소드 선언
Rectangle.prototype.getArea = function() {
    return this.getWidth() * this.getHeight();
};

지금까지 변수의 속성을 추가할 때 this키워드를 이용하여 추가한 것과 달리 이번에는 변수를 선언하고 메소드를 통해 변수에 접근하였습니다. 그리고 길이(width, height)에는 양수만 올 수 있으므로 양수가 아닌 값이 들어오면 값을 저장하지 못하도록 예외처리를 해 주었습니다. 이렇게 캡슐화를 하게 되면 의도하지 않은 값이 들어오는 것을 미리 방지할 수 있게 됩니다. 그리고 getWidth, getHeight와 같이 값을 가져오는 메소드를 게터(getter)라고 부르고 setWidth, setHeight와 같이 값을 설정하는 메소드를 세터(setter)라고 부릅니다.

 

 

상속

상속은 기존의 생성자 함수나 객체를 기반으로 새로운 생성자 함수나 객체를 쉽게 만드는 것입니다. 즉 기존 객체의 특성을 모두 물려 받습니다.

function Rectangle(w, h) {
    if(w <= 0 || h <= 0) {
        throw '길이는 양수이어야 합니다.';
    }
    var width = w;
    var height = h;
    
    this.getWidth = function() {
        return width;
    };
    this.getHeight = function() {
        return height;
    };
    this.setWidth = function(val) {
        if(val <= 0) {
            throw '길이는 양수이어야 합니다.';
        }
        else {
            width = val;
        }
    };
    this.setHeight = function(val) {
        if(val <= 0) {
            throw '길이는 양수이어야 합니다.';
        }
        else {
            height = val;
        }
    };
}

Rectangle.prototype.getArea = function() {
    return this.getWidth() * this.getHeight();
};

// Square는 Rectangle을 상속받습니다.
function Square(length) {
    this.base = Rectangle;
    this.base(length, length);
}
// prototype도 상속받을 수 있도록 별도 작업이 필요합니다.
Square.prototype = Rectangle.prototype;

 

자식(Square)의 생성자 함수의 base라는 속성(꼭 이름이 base이지 않아도 됩니다.)에 부모(Rectangle)의 생성자 함수를 넣고 실행한 것과 프로토타입을 넣어준 것 두 가지 작업을 하면 상속이 됩니다. 상속이 되었는지 확인을 하려면 아래의 코드를 실행해 보면 됩니다.

var square = new Square(5);         // Square의 인스턴스 생성
alert(square instanceof Rectangle); // 상속 확인(true 출력)

즉 Square의 인스턴스인 square가 생성자 함수 Rectangle로부터 만들어졌다고 인정이 되는 겁니다.

그런데 자바스크립트는 사실 다른 프로그래밍 언어와 달리 상속을 하는 방법이 딱히 정해져 있지 않습니다. 따라서 매우 다양한 상속 방법이 존재하는데 한 가지만 더 살펴보겠습니다.

function Square(length) {
    Rectangle.call(this, length, length);
}
Square.prototype = new Rectangle();
Square.prototype.constructor = Square;

call 메서드는 다른 객체의 메서드를 자신의 메서드처럼 사용할 수 있게 하는 메서드입니다.

댓글

댓글 본문
  1. kwang
    좋은내용 감사합니다^^ 잘설명해주시네요;;ㄷㄷ
  2. 좋은 내용 감사합니다!
  3. miki
    앗 그렇네요.. 감사합니다 수정했습니다!
    대화보기
    • tinews
      캡슐화 부분에 setHeight 하고 setWidth 메소드에서 value -> val 아닌가요?
    • superjang
      내용이 정말 잘 정리되어있네요 ^^
      때댕큐 입니다 ㅋㅋ