게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌16 - JavaScript - 8년 전 등록 / 6년 전 수정

객체 지향 프로그래밍(생성자와 프로토타입)

이번 시간에는 자바스크립트식 객체 지향 프로그래밍(OOP, Object Oriented Programming)에 대해 알아보겠습니다.

생성자

지난 시간에 Date 객체를 new Date()로 만들었던 것 기억하시나요? Date는 분명 객체라고 했는데 new를 붙이고 함수처럼 호출했습니다. 바로 자바스크립트 생성자(constructor) 함수입니다. 객체를 생성하는 함수를 생성자 함수라고 부릅니다. 다른 언어에서는 class가 있지만 자바스크립트에서는 없습니다. 생성자 함수가 그 역할을 대신합니다. 예를 들면 사람 생성자를 만들면 다음과 같습니다.

function Person(name, gender) {
  this.name = name;
  this.gender = gender;
  this.sayHello = function() {
    alert(this.name + ' said "hello"');
  }
  this.... // 사람의 속성과 메소드를 더 정의할 수 있습니다.
}

함수를 만들 때처럼 function을 쓰긴 했는데 함수와는 달리 대문자로 시작하게 만듭니다. 이게 규칙입니다. 생성자를 바탕으로 실제 사람 객체를 만들 수 있습니다. new라는 키워드를 사용해서 호출합니다. new 생성자(인자); 이렇게 하면 됩니다. Zero와 그의 친구 Hero를 만들어보죠!

var zero = new Person('Zero', 'm'); // Person {name: 'Zero', gender: 'm'}
var hero = new Person('Hero', 'f'); // Person {name: 'Hero', gender: 'f'}
zero.sayHello(); // 'Zero said "hello"'
hero.sayHello(); // 'Hero said "hello"'

하나의 Person 생성자를 바탕으로 zero와 hero 두 사람 객체를 만들었습니다. 그리고 이 객체들은 공통적으로 sayHello라는 메소드를 갖고 있습니다.

function Person(name, gender) {
  this.name = name;
  this.gender = gender;
  this.sayHello = function() {
    alert(this.name + ' said "hello"');
  }
}

다시 보면요. Person 옆에 (name, gender)는 처음 만들 때 매개변수를 받는 부분임을 알 수 있습니다. 그렇게 받은 매개변수들을 this.name, this.gender에 저장합니다. this는 바로 생성자 함수 자신을 가리킵니다. 이렇게 this에 저장된 것들은 new를 통해 객체를 만들 때 그 객체에 적용됩니다.

프로토타입

function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}
Person.prototype.sayHello = function() {
  alert(this.name + ' said "hello"');
};

이번에는 다른 건 위의 코드와 같은데 this.sayHello 대신에 Person.prototype에 sayHello를 넣었습니다. prototype은 처음보죠? prototype 객체는 사전 그대로 원형을 뜻합니다. 원래의 모습이죠. 같은 생성자로부터 만들어진 객체들은 모두 이 원형 객체를 공유합니다. 따라서 Person의 prototype 객체에 sayHello라는 메소드를 넣으면 Person 생성자로 만든 모든 객체는 이 메소드 사용이 가능합니다. 공유하고 있기 때문이죠.

그런데 this.sayHello보다 prototype에 Person.prototype.sayHello로 넣는 게 더 효율적입니다. prototype은 모든 객체가 공유하고 있어서 한 번만 만들어지지만, this에 넣은 것은 객체 하나를 만들 때마다 메소드도 하나씩 만들어지기 때문에 불필요한 메모리 낭비가 발생합니다.

아예 메소드뿐만 아니라 속성까지 다 prototype에 넣기도 합니다. 어떤 때 그러는지는 링크 를 참조하세요.

prototype과 __proto__

new Person('Nero', 'm'); // Person {name: "Nero", gender: "m", __proto__: Object}

Nero라는 새 사람 객체를 만들었더니 그 안에 처음보는 __proto__라는 객체가 있습니다. 눌러보니

{
  constructor: function Person(name, gender),
  sayHello: function() {},
  __proto__: Object
}

constructor과 우리가 추가한 sayHello 그리고 또다시 __proto__가 있네요.

undefined

__proto__가 바로 실제 객체를 만들 때 생성자의 prototype이 참조된 모습입니다. 생성자의 prototype을 참조하기 때문에 __proto__와 prototype은 같습니다. 아까 Person.prototype.sayHello를 했던 것이 들어있죠. 추가로 constructor(생성자)에 대한 정보까지 들어있습니다. __proto__ 안에 들어있는 __proto__는 지금은 생각하지 않으셔도 됩니다. 이 강좌 에 나와있습니다.

정리하면

  • constructor는 생성자 함수 그 자체를 가리킴
  • prototype은 생성자 함수에 정의한 모든 객체가 공유할 원형
  • __proto__는 생성자 함수를 new로 호출할 때, 정의해두었던 prototype을 참조한 객체
  • prototype은 생성자 함수에 사용자가 직접 넣는 거고, __proto__는 new를 호출할 때 prototype을 참조하여 자동으로 만들어짐
  • 생성자에는 prototype, 생성자로부터 만들어진 객체에는 __proto__
  • 따라서 사용자는 prototype만 신경쓰면 된다. __proto__는 prototype이 제대로 구현되었는지 확인용으로 사용한다.

prototype, __proto__와 constructor의 관계

prototype과 constructor는 부모자식 관계라고 생각하면 됩니다. Person.prototype.constructor === Person; 입니다. 또한 Person.prototype === (Person생성자로 만들어진 객체).__proto__; 이기 때문에 (Person생성자로 만들어진 객체).__proto__.constructor === Person; 도 성립합니다.

상속의 필요성

위와 같이 생성자 함수를 만들고, new 키워드를 통해 객체도 만들었습니다. Person 생성자 외에도 Vehicle이나 Machine 같은 생성자를 만들 수도 있겠죠?

function Vehicle(name, speed) {
  this.name = name;
  this.speed = speed;
}
Vehicle.prototype.drive = function () {
  console.log(this.name + ' runs at ' + this.speed)
};

이렇게 Vehicle 생성자를 만들었습니다. drive 메소드로 달릴 수 있네요! 근데 drive 메소드 하나밖에 없으니 좀 심심하죠? boost라는 메소드를 만들어서 최고 속도로 달릴 수 있는 기능을 추가하면 괜찮을 거 같은데요. 그래서 Vehicle.prototype.boost = function () {...} 이렇게 추가하자니 한 가지가 걸립니다. Vehicle 안에는 트럭, SUV, 스포츠카, 세단 등 많은데 트럭이 boost하는 건 좀 이상하지 않나요?

이럴 때 상속이 필요합니다. 상속은 외국에서 확장(extend)이라고도 표현합니다. 즉 부모 생성자의 기능을 물려받는 동시에 새로운 기능을 추가할 수도 있는거죠.

다음 시간에는 객체를 상속하는 방법에 대해 알아보겠습니다. Vehicle 생성자의 기능을 상속하고 확장한 Sedan 객체를 만들어봅시다!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

14개의 댓글이 있습니다.
4년 전
좋은 강의 감사합니다.
5년 전
안녕하세요. 우선 좋은 정보들 감사합니다.

초보같은 질문입니다만

function Car(model, year){

var _model = model || "mk1";
this.year = year || "2007";

this.getModel = function(){
return _model;
}

this.setModel = function(model){
_model = model;
}


this.getYear = function(){
return this.year;
}
this.setyear = function(year){
this.year = year;
}

}

_model 변수를 이렇게 사용하면 private변수로 사용할 수 있는것 같은데,

우선 이런 방식으로 인스턴스를 생성해서 사용하면

메서드를 생성하는 객체들 마다 만들기 때문에 효율적인 방식이 아니고,

프로토타입으로 메서드를 생성하면 _model변수에 접근할 수 없기 때문에

이런 방식으로는 사용하지 않는건가요?
5년 전
Car.prototype.getModel = function() { this.getModel() }
이런 식으로 할 수 있습니다. this.getModel은 return _model 이런 코드가 되겠죠.
6년 전
생성자 변경이 궁금합니다.
저는 테스트로 생성자를 변경하면 실제 객체생성이 되는 생성자가 변경될까? 테스트 했는데 그것도 아니네요..
상속관련 예제 코드들을 보다보면 생성자를 변경해 주는 코드를 본적이 있어서요.
변경을 해주는 이유가 뭘까요?
생성된 객체의 생성자를 명시적으로 표시해주는 용도?? 인가요?
6년 전
A.prototype.constructor = A; 이 코드를 말씀하시는 건가요? 이건 버그 픽스입니다. 원래 객체 지향 프로그래밍 상속에서는 이렇게 되어야하는데 이렇게 안 나오기 때문에 하는 겁니다.
6년 전
안녕하세요 제로초님, 생성자함수에대해 궁금한게 있습니다!! 생성자 함수Person 안에서 this는 생성자 자신을 가린킨다고 하셨는데 어떤책들을 보면 this가 새로 만들어지는 객체를 가리킨다는 말이 있습니다 그래서 혼란스러운데요 이걸 어떤식으로 이해 해야할지 잘 모르겠습니다 ㅜㅜ
6년 전
this는 원래 window를 가리킵니다(strict모드에서는 undefined). 그런데 new로 생성자를 호출하면 this는 새로 만들어지는 객체를 가리키게 됩니다.
6년 전
그럼 위에서 말씀하신 this가 생성자함수 자신을 가리킨다는 말은 무슨뜻인가요?
6년 전
보통 생성자 함수를 쿠키틀, new로 생성한 객체를 쿠키라고 비유하는데요. 쿠키틀에서 this는 생성자함수에 바인드된 this(무엇인지 모름)를 가리키고, 그게 나중에 new로 쿠키를 찍어낼 때 그대로 객체로 다시 바인드되는거죠.
6년 전
음 그럼 생성자함수가 실행되기전 이름없는 객체 = 생성자함수, new로 실행되었을때 this는 생성자로 생성되는 객체를 가리킨다 이게 맞나요? //질문 내용이 바보같아서 답답하실텐데 그래도 친절한 답변에 감사드려요
6년 전
생성자 함수는 new가 붙기 전까지는 그냥 함수일 뿐입니다. 생성자 함수가 따로 있는 게 아니에요. 생성자 함수의 this는 그냥 일반 함수의 this랑 같습니다.
6년 전
아..드디어 이해됐습니다 감사합니다!!!!
6년 전
앞서 함수 프로퍼티에 값을 매기는 구문으로 분명 name : value 형식을 취했던것을
여기서는 this.name = value 형식으로 쓰시네요.
위 두 표현에는 어떤 차이도 없나요?
6년 전
name: value는 객체 리터럴({ })에서 쓰는 것이고, this.name = value는 생성자로 객체를 만들 때 씁니다~ 객체 상속같은 기능을 활용하려면 생성자 쓰셔야 돼요~
6년 전
답변 감사드립니다.
객체 리터럴 연산자 착각 부분은 제가 완전 더위먹었나 봅니다.
역시 테스트해봐도 맴버변수중 this. 키워드를 안부친건 객체생성시 완전 소멸된건지 일절 접근이 안되는게 신기하네요. 여긴 static 한정자도 없으니 인스턴스에서 사용하려면 무조건 this. 은 필수겠네요.
6년 전
나중에 생성자에서 class 문법이 나오고 static도 추가되었습니다~ https://www.zerocho.com/category/ECMAScript/post/5911b1983b87740018a06b0e
6년 전
prototype 과 _PROTO_ 둘이 이해가 안됩니다.
prototype 으로 메소드를 만드는 방식이 효율적이다는 말은 이해가 가는데,
Person 을 상속받은 객체 a,b 가 있는데 Person 생성자에 직접접근하지 않고
a._proto_.sayhello = function(){} \u003c- 이런방식으로 메소드를 추가해도 Person 을 상속받은
두객체에서는 둘다 sayHello 라는 메소드를 쓸 수 있더라구요
Person.prototype 과 (Person 을 상속받은 객체)._proto_ 의 차이는 무엇인가요?
6년 전
아 이해했습니다. _proto_ 가 생성자의 Prototype 을 참조하니 _proto_를 수정하면 생성자의 Prototype 값도 바뀌겠군요 배열1를 얕은 복사해서 배열2를 만들었을때 배열2를 수정했을시 배열1이 바뀌는 원리랑 같은거죠?
6년 전
네네 그런데 _proto_가 표준이 아니라서 안 쓰시는 게 좋습니다.
6년 전
prototype에 대해 의문점과 헷갈리는 부분이 많았는데 도움이 많이 되었습니다
다만 궁금한게 있는데 Person.prototype.sayHello()인 부분에 관한 질문인데요
Person을 상속하는 두 객체에서 한 객체가 sayHello()를 재정의할때 메모리 할당을 새로 하게 되는 건가요?
6년 전
상속한 객체가 sayHello()를 "재정의". 재정의하는 순간 메모리 할당이 새로 됩니다. 기존에 있던 객체의 값을 바꾸는 게 아니라 새로운 클래스에 sayHello를 추가하는 것이니까요.
6년 전
질문 있는데요 그럼 매개변수를 안에 입력 안할때는 Object.create(vehicle.prototype) 인데 그냥 뉴 안에 값 입력 안하고 new Vehicle() 이런 식으로 쓰면 안되나요?
6년 전
그러셔도 되는데 버그가 좀 있긴 합니다. Object.create를 권장합니다.
6년 전
자주 들어와서 필요한 자료를 보는데 이번에도 역시 많은 도움이됐어요 항상 감사드립니다 *^^*
6년 전
언제나 좋은글 감사합니다, 좋은 글 잘 읽고 갑니다. 좋은 하루 되세요.
6년 전
감사합니다. 좋은 하루 되세요~
7년 전
많은 자바스크립트 서적과 포스팅을 봤지만 가장 이해가 잘되는거 같습니다.
7년 전
감사합니다! 저도 이렇게 정리할 수 있을 때까지 정말 많은 시행착오를 거쳤습니다. ㅠㅠ
7년 전
너 사람아니지! 인공지능이 대답 해 준거지! 농담입니다.. 상당히 빠른답변 고맙습니다 저도 현재 vue를 사용해서 앱을 만들고있는데 정적 파일의 문제인지 아니면 cookie에 관한 문제인지 모르겠는데 가끔 제로초님 블로그 보다가 답글 왔다고 이메일 옴 -> 클릭해서 들어가면 제가 올린댓글도 사라지고 답변도 없는 경우가 있더라구요 한 3번??정도 그런 경험이 있네욥
7년 전
쿠키의 문제입니다 ㅠㅠ 새로고침 하셔야 보여요
7년 전
와 상당히 이해를 쉽게하게 알려주시네요! 아 궁금증이 하나 생겨서요
함수를 생성할때 해당 객체 내부에 하기보단 바깥에 person.prototype.say.....이런식으로 해주는게 좋다고 하셨는데요
궁금한게

var a = function(name){
this.name = name; \u003c\u003c단순히 변수
this.sayName = function(){ \u003c\u003c객체
consoel.log('name')
}
}

새로 컨스트럭트를 생성하면 각 변수??라고해야하나요 거기에 해당 객체가 또 생겨서 그렇게 만드는게 메모리를 많이 먹는다는 말씀이신가요?

protype을 해서 새로운 객체를 생성해주면 한번만 생성한다는 얘기가.. 조금 이해가 안됩니다

만약 저 내부안에 list : {name : 'kimchi',name:'man'} 이라는 리스트가 존재한다면 이 또한 객체를 생성했기 때문에 만약 다시또 new를 사용해서 생성하면 새로 만들어진다는 말씀이신지요..

프로그래밍을 c#으로 입문을 해서 상당히 헷갈리네요
7년 전
일단 객체가 생기면 메모리를 잡아먹기 때문에 객체가 공통적으로 사용하는 프로토타입 메소드들은 중복 생성되지 않게 해서 메모리 양을 줄이자는 게 주요 골자입니다.
네 리스트를 생성자 함수 안에서 만들면 객체가 새로 만들어집니다(메모리 추가 사용).
프로토타입 메소드들은 Person.prototype 객체 안에 한 번만 들어가있고, 인스턴스들이 메모리 주소를 통해 접근하는 것이기 때문에 낭비가 없습니다.
7년 전
프로토타입이 이해가 될듯하면서 안되네요.ㅜ

Person 생성자함수로 만든 객체 안에 프로토타입객체가 들어있는 형태인가요?

프로토타입의 예제를 예로 들어서

var zero = new Person("zero", m);

이런식으로 zero 변수안에 생성자함수를 통해 객체를 생성하고 sayHello를 호출하려면

zero.prototype.sayHello();

이런식으로 호출하면 되는건가요?

객체안에 또 다른객체가 있는형태로 보면 되는건가요??



제가 이해하고 있는게 잘 이해한건지 모르겠네요ㅜㅜ
7년 전
sayHello는 Person.prototype.sayHello로 등록해두고, 생성자로 만든 객체에서는 zero.sayHello() 이렇게 씁니다.