처음 프로그래밍을 배웠을 때(그래봤자 불과 한 달 전), 객체라 하면 떠오르는 것은 Object였다.
let book = {title: 'Harry Potter', author: 'J.K. Rolling', language: 'English', year: 1997}
위와 같이 프로퍼티와 값으로 묶인 정보들의 집합이라 생각했다.
그리고 나는 객체를 '자바스크립트'라는 교과서의 한 챕터 정도로 받아들였다.
프로그래밍(주로 자바스크립트)를 배울 수록 객체에 대한 개념이 확장되는 것을 느낄 수 있었다. 처음 객체가 막연한 '교과서의 한 챕터'가 아니라고 느꼈던 것은 DOM구조를 공부할 때였다.
'console.dir(document.body)'를 입력해보라고 하셔서 따라하고 나니, 'document.body'의 모양새가 어딘가 익숙했다. 객체에서 값을 호출하는 모양과 같았다. 자바스크립트에서 DOM은 document에 구현되어 있는데 이 document가 바로 하나의 객체였던 것이다.
그 후에는 위와 같이 윈도우 역시도 하나의 거대한 객체라는 것을 알 수 있었고,
객체 안에는 함수 역시도 값으로 담기며, 이 경우 함수를 메서드라 칭한다는 것도 알 수 있었다.
'Math.abs()', 'Array.prototype.reduce()' 이런 것들을 왜 '메서드'라 부르는지도 이제서야 이해했다.
지금에와서 객체를 정의한다면 어떤 식이 되어야 할까.
생활코딩 채널의 강의를 참고 하자면, 변수와 메소드를 연관이 있는 것끼리 그루핑하고, 그 그룹핑된 하나하나의 단위들을 객체라고 한다.
막연히 객체지향을 이해하기보다는 함께 언급되는 절차지향과 비교해본다면 이해가 더 쉬울 것이라 생각한다.
먼저 각각 절차지향과 객체지향으로 볼로네제 파스타를 만들어본다.
이렇게만 보면 크게 다르지 않아보인다. 오히려 각 단계에서 세부레시피를 확인해야 한다는 점에서 객체지향으로 볼로네제를 만드는 것이 더 번거로워보일 수도 있다. 하지만 볼로네제, 까르보나라, 알리오올리오 3종세트 만들기를 해본다면 어떨까.
절차 지향으로 3종 파스타를 만들 경우 24개의 단계를 일일이 거쳐야 하지만,
객체 지향으로 3종 파스타를 만들 경우 볼로네제에서 다룬 '면 삶을 물 준비' 단계를 나머지 두 종의 파스타를 만들 때도 그저 반복만 하면 된다!
막상 쓰고 나니 더 적절한 비유를 찾아야 할 것 같지만 여기서 포인트는 객체지향의 경우 수많은 절차들을 카테고라이징하고 있다는 점이다. 객체지향이란 이와 같이 객체들의 조합을 통해 하나의 프로그램을 만드는 방식, 프로그래밍을 객체의 조합으로 접근하는 패러다임을 말한다.
절차지향과 객체지향의 정의와 장단점을 간략히 표로 정리하면 위와 같다.
(객체지향은 캡슐화, 상속, 추상화, 다형화라는 특징이자 장점을 갖는다.)
자바스크립트에서 객체를 생성하는 방법은 4 가지가 있다.
각각의 방식은 아래와 같다.
직관적으로 이해가 가능하지만, 문제는 매번 객체를 생성할 때마다 메소드까지 복제하게 된다. 또한, 중간에 메소드 일괄 수정이 불가하다.
그래서 메소드를 다른 객체에 정의하는 아래의 방식이 등장(?)한다.
메소드가 계속해서 복제되는 문제는 해결되었으나, Functional Shared Instantation 방식을 쓰더라도, 메소드 수정이 불가하다.
그래서 프로토타입을 이용하게 된다.
메소드를 프로토타입에 작성한 후에 이를 끌어와서 쓰도록 하는 아이디어.
얼핏 보면, new와 비슷한 기능을 하는 것과 같이 보인다. 우선 MDN에서 둘의 정의를 비교해보자
Object.create() 메서드는 지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만듭니다.
new 연산자는 사용자 정의 객체 타입 또는 내장 객체 타입의 인스턴스를 생성한다.
새 객체를 만든다는 점에서는 같지만 Object.create()는 '지정된 프로토타입 객체 및 속성'을 갖는 새 객체를 만들고, new연산자는 '사용자 정의 혹은 내장 객체'를 만든다.
이 참고사이트에 등장하는 예시를 살펴보자
function Dog(){
this.pupper = 'Pupper';
};
Dog.prototype.pupperino = 'pups.';
let maddie = new Dog();
let buddy = Object.create(Dog.prototype);
console.log(buddy.pupper); // undefined
console.log(buddy.pupperino); // Pups.
console.log(maddie.pupper); // Pupper
console.log(maddie.pupperino); //Pups.
완벽히 이해는 안 가지만, new Dog이 실제로 생성자코드를 실행하지만, Object.create는 생성자코드를 실행하지 않는다는 점만 우선 인지한다.
실제로 위에서 다룬 코드는 조금 이상한 점이 있다.
movie1을 찍을 경우, 객체 안에 watch라는 메소드에 해당하는 부분이 출력되지 않지만, 또 실행은 된다. (프로토타입에 따로 저장이 된 것이라고 이해해도 되는 것일까?)
이해한 것이 맞다면 이제 객체는 메소드를 메모리에 복제하지도 않으며, 프로토타입에 연결되었으므로, 수정을 할 수도 있다. 그러나 그 과정이 조금 복잡하다. 마지막 네 번째 객체 생성법을 알아보자.
function DogDoll() {
this.head = 1;
this.leg = 4;
}
let welshCorgi = new DogDoll();
let husky = new DogDoll();
위와 같은 형태로 100개의 객체를 만들게 되면 200개의 변수가 메모리에 할당된다. 이러한 문제를 프로토타입을 통해서 해결한다.
function DogDoll() {}
DogDoll.prototype.head = 1;
DogDoll.prototype.leg = 4;
let welshCorgi = new DogDoll();
let husky = new DogDoll();
이렇게 프로토타입을 사용하게 되면, DogDoll.prototype이라는 Object를 만들어두고 welshCorgi와 husky가 이를 공유하게 되므로, 메모리에는 2개의 변수만 할당된다. 이와 같이 프로토타입은 공유할 원형에 해당한다고 이해할 수 있다.