객체 리터럴 {...} 을 사용하면 객체를 쉽게 만들 수 있습니다. 그런데 개발을 하다 보면 유사한 객체를 여러 개 만들어야 할 때가 생기곤 합니다. 복수의 사용자, 메뉴 내 다양한 아이템을 객체로 표현하려고 하는 경우가 그렇죠.
'new' 연산자와 생성자 함수를 사용하면 유사한 객체 여러 개를 쉽게 만들 수 있습니다.
생성자 함수
생성자 함수(constructor function)와 일반 함수에 기술적인 차이는 없습니다. 다만 생성자 함수는 아래 두 관례를 따릅니다.
- 함수 이름의 첫 글자는 대문자로 시작합니다.
- 반드시
'new'연산자를 붙여 실행합니다.
예시:
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("보라");
alert(user.name); // 보라
alert(user.isAdmin); // false
new User(...)를 써서 함수를 실행하면 아래와 같은 알고리즘이 동작합니다.
- 빈 객체를 만들어
this에 할당합니다. - 함수 본문을 실행합니다.
this에 새로운 프로퍼티를 추가해this를 수정합니다. this를 반환합니다.
예시를 이용해 new User(...)가 실행되면 무슨 일이 일어나는지 살펴 보도록 하겠습니다.
function User(name) {
// this = {}; (빈 객체가 암시적으로 만들어짐)
// 새로운 프로퍼티를 this에 추가함
this.name = name;
this.isAdmin = false;
// return this; (this가 암시적으로 반환됨)
}
이제 let user = new User("보라")는 아래 코드를 입력한 것과 동일하게 동작합니다.
let user = {
name: "보라",
isAdmin: false
};
new User("보라")이외에도 new User("호진"), new User("지민") 등을 이용하면 손쉽게 사용자 객체를 만들 수 있습니다. 객체 리터럴 문법으로 일일이 객체를 만드는 방법보다 훨씬 간단하고 읽기 쉽게 객체를 만들 수 있게 되었죠.
생성자의 의의는 바로 여기에 있습니다. 재사용할 수 있는 객체 생성 코드를 구현하는 것이죠.
잠깐! 모든 함수는 생성자 함수가 될 수 있다는 점을 잊지 마시기 바랍니다. new를 붙여 실행한다면 어떤 함수라도 위에 언급된 알고리즘이 실행됩니다. 이름의 '첫 글자가 대문자’인 함수는 new를 붙여 실행해야 한다는 점도 잊지 마세요. 공동의 약속이니까요.
재사용할 필요가 없는 복잡한 객체를 만들어야 한다고 해봅시다. 많은 양의 코드가 필요할 겁니다. 이럴 땐 아래와 같이 코드를 익명 생성자 함수로 감싸주는 방식을 사용할 수 있습니다.
let user = new function() {
this.name = "John";
this.isAdmin = false;
// 사용자 객체를 만들기 위한 여러 코드.
// 지역 변수, 복잡한 로직, 구문 등의
// 다양한 코드가 여기에 들어갑니다.
};
위 생성자 함수는 익명 함수이기 때문에 어디에도 저장되지 않습니다. 처음 만들 때부터 단 한 번만 호출할 목적으로 만들었기 때문에 재사용이 불가능합니다. 이렇게 익명 생성자 함수를 이용하면 재사용은 막으면서 코드를 캡슐화 할 수 있습니다.
new.target과 생성자 함수
이 절에서 소개할 문법은 자주 쓰이지 않습니다. 자바스크립트의 모든 문법을 학습하고 싶지 않다면 넘어가셔도 좋습니다.
new.target 프로퍼티를 사용하면 함수가 new와 함께 호출되었는지 아닌지를 알 수 있습니다.
일반적인 방법으로 함수를 호출했다면 new.target은 undefined를 반환합니다. 반면 new와 함께 호출한 경우엔 new.target은 함수 자체를 반환해줍니다.
function User() {
alert(new.target);
}
// 'new' 없이 호출함
User(); // undefined
// 'new'를 붙여 호출함
new User(); // function User { ... }
함수 본문에서 new.target을 사용하면 해당 함수가 new와 함께 호출되었는지(in constructor mode) 아닌지(in regular mode)를 확인할 수 있습니다.
이를 활용해 일반적인 방법으로 함수를 호출해도 new를 붙여 호출한 것과 같이 동작하도록 만들어보겠습니다.
function User(name) {
if (!new.target) { // new 없이 호출해도
return new User(name); // new를 붙여줍니다.
}
this.name = name;
}
let bora = User("보라"); // 'new User'를 쓴 것처럼 바꿔줍니다.
alert(bora.name); // 보라
라이브러리를 분석하다 보면 위와 같은 방식이 쓰인 걸 발견할 때가 있을 겁니다. 이런 방식을 사용하면 new를 붙여 함수를 호출하든 아니든 코드가 동일하게 동작하기 때문에, 좀 더 유연하게 코드를 작성할 수 있습니다.
그런데 이 방법을 믿고 객체를 만드는 경우에도 new를 생략하면 코드가 정확히 무슨 일을 하는지 알기 어렵습니다. new가 붙어있으면 새로운 객체를 만든다는 걸 누구나 알 수 있기 때문에 new를 생략해서 객체를 만드는것은 정말 필요한 경우에만 사용하시고 남발하지 않으시길 바랍니다.
생성자와 return문
생성자 함수엔 보통 return 문이 없습니다. 반환해야 할 것들은 모두 this에 저장되고, this는 자동으로 반환되기 때문에 반환문을 명시적으로 써 줄 필요가 없습니다.
그런데 만약 return 문이 있다면 어떤 일이 벌어질까요? 아래와 같은 간단한 규칙이 적용됩니다.
- 객체를
return한다면this대신 객체가 반환됩니다. - 원시형을
return한다면return문이 무시됩니다.
return 뒤에 객체가 오면 생성자 함수는 해당 객체를 반환해주고, 이 외의 경우는 this가 반환되죠.
아래 예시에선 첫 번째 규칙이 적용돼 return은 this를 무시하고 객체를 반환합니다.
function BigUser() {
this.name = "원숭이";
return { name: "고릴라" }; // <-- this가 아닌 새로운 객체를 반환함
}
alert( new BigUser().name ); // 고릴라
아무것도 return하지 않는 예시를 살펴봅시다. 원시형을 반환하는 경우와 마찬가지로 두 번째 규칙이 적용됩니다.
function SmallUser() {
this.name = "원숭이";
return; // <-- this를 반환함
}
alert( new SmallUser().name ); // 원숭이
return문이 있는 생성자 함수는 거의 없습니다. 여기선 튜토리얼의 완성도를 위해 특이 케이스를 소개해보았습니다.
인수가 없는 생성자 함수는 괄호를 생략해 호출할 수 있습니다.
let user = new User; // <-- 괄호가 없음
// 아래 코드는 위 코드와 똑같이 동작합니다.
let user = new User();
명세서엔 괄호를 생략해도 된다고 정의되어 있지만, '좋은 스타일’은 아닙니다.
생성자 내 메서드
생성자 함수를 사용하면 매개변수를 이용해 객체 내부를 자유롭게 구성할 수 있습니다. 엄청난 유연성이 확보되죠.
지금까진 this에 프로퍼티를 더해주는 예시만 살펴봤는데, 메서드를 더해주는 것도 가능합니다.
아래 예시에서 new User(name)는 프로퍼티 name과 메서드 sayHi를 가진 객체를 만들어줍니다.
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "제 이름은 " + this.name + "입니다." );
};
}
let bora = new User("이보라");
bora.sayHi(); // 제 이름은 이보라입니다.
/*
bora = {
name: "이보라",
sayHi: function() { ... }
}
*/
class 문법을 사용하면 생성자 함수를 사용하는 것과 마찬가지로 복잡한 객체를 만들 수 있습니다. class에 대해선 추후 학습하도록 하겠습니다.
요약
- 생성자 함수(짧게 줄여서 생성자)는 일반 함수입니다. 다만, 일반 함수와 구분하기 위해 함수 이름 첫 글자를 대문자로 씁니다.
- 생성자 함수는 반드시
new연산자와 함께 호출해야 합니다.new와 함께 호출하면 내부에서this가 암시적으로 만들어지고, 마지막엔this가 반환됩니다.
생성자 함수는 유사한 객체를 여러 개 만들 때 유용합니다.
자바스크립트는 언어 차원에서 다양한 생성자 함수를 제공합니다. 날짜를 나타내는 데 쓰이는 Date, 집합(set)을 나타내는 데 쓰이는 Set 등의 내장 객체는 이런 생성자 함수를 이용해 만들 수 있습니다. 자세한 내용은 다시 살펴보도록 하겠습니다.
이번 챕터에서 다룬 것은 객체와 생성자에 대한 기본에 불과합니다. 이어지는 챕터에서 다룰 자료형과 함수를 이해하는 데 꼭 필요한 내용이죠.
객체에 대한 학습은 아직 끝나지 않았습니다. 자료형과 함수를 학습한 이후에 다시 객체로 돌아와 프로토타입과 프로토타입 상속, 클래스등을 다루도록 하겠습니다.
댓글
<code>태그를, 여러 줄로 구성된 코드를 삽입하고 싶다면<pre>태그를 이용하세요. 10줄 이상의 코드는 plnkr, JSBin, codepen 등의 샌드박스를 사용하세요.