본문으로 바로가기
해당 본문에서는 제로초님의 객체지향 프로그래밍(생성자와 프로토타입)의 내용을 일부 인용하고 있습니다.

이번글에서는 자바스크립트의 주요 기능중 하나인 생성자와 프로토타입에 대해서 정리해보려고 하며 이와 뗄수없는 관계인 this에 대해서 글을 작성해보려고 한다.

생성자

자바스크립트는 기본적으로 객체지향 프로그래밍 언어이다. 다른 언어에서 객체를 사용하는 방법은 꽤나 다양하지만 이번 글에서는 자바스크립트(ES6)에서 객체를 사용할때 어떻게 사용하는지에 대해서 써보려고 한다.

 

자바스크립트에는 생성자 함수라는것이 있다. 일반적인 함수와 달리 객체를 생성하는 함수를 생성자 함수라고 부르며 생성자 함수는 첫글자가 대문자로 시작한다. 예를 들어 스마트폰 생성자를 만들면 다음과 같다.

function Phone(brand, name) {
  this.brand = brand;
  this.name = name;
  this.logPhone = function() {
    console.log(`제가 사용중인 휴대폰은 ${this.brand}사의 ${this.name}입니다.`);
  }
}

let baeksan = new Phone('Apple', 'Iphone 12 Pro'); // 생성자 함수는 new라는 키워드를 사용해서 호출합니다.
let dylan  = new Phone('Samsung', 'Galaxy S21');
baeksan.logPhone(); // [출력] : '제가 사용중인 휴대폰은 Apple사의 Iphone 12 Pro입니다.'
dylan.logPhone(); // [출력] : '제가 사용중인 휴대폰은 Samsung사의 Galaxy S21입니다.'

하나뿐인 Phone생성자를 사용해서 baeksan, dylan 두 사람의 객체를 만들었다. 그리고 이 객체들은 공통적으로 logPhone이라는 메소드를 가지고 있다. 코드를 살펴보자면 생성자 함수 Phone()은 매개변수로 brand, name를 받고 있고 이렇게 받은 값을 this.brand, this.name에 저장한다. 여기서 this란 생성자 함수 자신을 가리키며 이러한 방식으로 저장된 변수는 new를 통해 객체를 만들때 해당 객체에 적용되며 외부에서 접근할 수 없게(생성자 함수 밖에서 this.brand 변수를 호출해도 실행되지 않도록) 캡슐화된다.

프로토타입

프로토타입은 다른 언어에서 사용하는 클래스(Class)라는 개념을 가지고 있지 않다. 대신 프로토타입(Prototype)이라는것이 존재하며 이것이 자바스크립트가 객체지향(프로토타입) 기반 언어라고 불리는 이유이다. 클래스가 없으니 클래스의 기능인 상속도 없으며 이를 대체하기 위해 프로토타입을 기반으로 상속기능을 구현할 수 있도록 만들어져 있다.

function Phone(brand, name) {
  this.brand = brand;
  this.name = name;
}

Phone.prototype.logPhone = function () {
  console.log(`제가 사용중인 휴대폰은 ${this.brand}사의 ${this.name}입니다.`);
};

let baeksan = new Phone('Apple', 'Iphone 12 Pro'); // 생성자 함수는 new라는 키워드를 사용해서 호출합니다.
let dylan  = new Phone('Samsung', 'Galaxy S21');
baeksan.logPhone(); // [출력] : '제가 사용중인 휴대폰은 Apple사의 Iphone 12 Pro입니다.'
dylan.logPhone(); // [출력] : '제가 사용중인 휴대폰은 Samsung사의 Galaxy S21입니다.'

이번 코드와 위에서 나온 코드가 다른점은 this.logPhone 대신 Phone.prototype에 logPhone을 넣었다. prototype을 사용해 Phone의 prototype객체에 logPhone이라는 메소드를 넣으면 Phone생성자로 만든 모든 객체는 해당 메소드(logPhone)를 공유하고 있기때문에 똑같이 사용이 가능하다.

 

이렇게 프로토타입을 사용하는 가장 큰 이유는 효율성때문인데 prototype은 모든 객체가 함께 공유하고 있어서 한번만 만들어지지만, this에 넣은 값(속칭 속성)은 객체 하나를 만들때마다 메소드도 하나씩 만들어지기 때문에 불필요한 메모리낭비가 발생한다. 이런 장점 때문에 함수뿐만 아니라 속성까지 전부 prototype에 넣는 방식을 기용하기도 하는데 이를 플라이급 디자인 패턴이라고 부른다.

상속

상속은 부모 생성자의 기능을 물려받으면서 새로운 기능도 추가하는것을 의미한다.

function Phone(brand, name) {
  this.brand = brand;
  this.name = name;
}
Phone.prototype.logPhone = function () {
  console.log(`제가 사용중인 휴대폰은 ${this.brand}사의 ${this.name}입니다.`);
};

function Display(brand, name, size) {
  Phone.apply(this, arguments) // Phone의 this와 arguments(매개변수)를 그대로 받기
  this.size = size;
}

Display.prototype = Object.create(Phone.prototype); // Display과 Phone의 prototype을 연결
Display.prototype.constructor = Display; // 오류 해결 코드
Display.prototype.playVideo = function (videoResolution) {
  if (videoResolution > this.size) {
    console.log('영상 해상도가 디스플레이 해상도보다 높습니다.');
  } else {
    console.log('재생할 수 있는 영상입니다.');
  }
};

let baeksan = new Phone('Apple', 'Iphone 12 Pro', 1080);
baeksan.logPhone(); // [출력] : '제가 사용중인 휴대폰은 Apple사의 Iphone 12 Pro입니다.'
baeksan.playVideo(1440); // [출력] : '영상 해상도가 디스플레이 해상도보다 높습니다.'

코드를 보면 Display()를 새로 선언하고 여기에 Phone()의 기능(속성, prototype)를 상속받고 있다. 기존의 prototype을 사용하던것과 다른게 있다면 부모 함수를 연결하는 코드와 상속 과정에서 발생하는 오류를 해결하는 코드가 추가되어있다는 점이 다르다.

Display.prototype = Object.create(Phone.prototype); // Display과 Phone의 prototype을 연결
Display.prototype.constructor = Display; // 오류 해결 코드

이다음부터는 위에서 prototype을 사용한것과 같은 방법대로 사용하면 되며 해당 코드에서는 영상 해상도를 인자값으로 받고 생성자 함수를 선언할때 사용했던 값과 대조해 '영상 해상도가 디스플레이 해상도보다 높습니다' 라는  메시지를 출력하고 있다. 또한, Objecet.create(Phone.prototype)new Phone()과의 차이점은 Object.create는 객체를 만들되 생성자는 실행하지 않는다. 즉, 프로토타입만 상속시키는 것이다.

This

this란 기본적으로 window이다. window는 웹에서 최상위 스코프를 의미하며 웹에 내장된 모든 함수, 변수는 window.valueName과 같은 방식으로 가져올 수 있다. 그러나 this.name과 같은 변수를 함수 내에서 선언하면 this는 더이상 window가 아닌 해당 함수내에서 제한된 스코프 범위를 가지게 된다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayHi = function() {
  console.log(this.name, this.age);
}

var baeksan = new Person('Baeksan', 19); // Person {name: "Hero", age: 33}as bo
baeksan.sayHi(); // [출력] : Beaksan, 19

이처럼 this를 함수내에서 사용하면 캡슐화 효과를 얻을 수 있고 prototype의 인자로도 사용할 수 있기 때문에 일반 변수(let, var, const)를 사용하는것보다 더 효율적으로 코드를 작성할 수 있다.