들어가기 전에
객체 리터럴에 의한 객체 생성 방식
객체 리터럴에 의한 객체 생성 방식은 가장 일반적이고 간단한 객체 생성 방식이다. 그도 그럴게 매우 직관적이고 간편하다. 아래의 코드를 보자.
const person1 = {
name: 'jeong',
introduce() {
return 'hi, my name is jeong'
}
}
이걸 보고 person1
이 이름이 jeong이고 자기를 소개할 수 있다는 것을 모른다? 그것은 코드 실력과 무관한 문해력의 영역이 아닐까 싶다.
하지만 객체 리터럴에 의한 객체 생성 방식은 단 하나의 객체만 생성한다. 따라서 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 수작업으로 아래와 같은 개고생을 해줘야한다.
const person1 = {
name: 'jeong',
introduce() {
return 'hi, my name is jeong'
}
}
const person2 = {
name: 'lee',
introduce() {
return 'hi, my name is lee'
}
}
const person3 = {
name: 'kim',
introduce() {
return 'hi, my name is kim'
}
}
이건 진짜 귀찮은 일이다. 세상사는 귀찮은 일을 귀찮지 않게 발전시키면서 발전해나가지 않나? 이런 귀찮음을 방지하기 위해 만들어진 게 생성자 함수가 아닐까 싶다.
person 객체를 보면 프로퍼티 값이 다를 수는 있지만 구조가 모두 동일하다. 객체 리터럴에 의해 객체를 생성하면 매번 같은 프로퍼티와 메서드를 기술해야 한다. 이 단점을 극복하고 귀찮음에서 탈출하려면 어떻게 해야할까? 구조를 템플릿으로 만들어놓고 달라지는 값만 입력해주면 되지 않을까?
생성자 함수에 의한 객체 생성 방식
생성자 함수에 의한 객체 생성 방식은 객체(인스턴스)를 생성하기 위한 템플릿(클래스)처럼 생성자 함수를 사용해 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.
// 생성자 함수
function Person(name) {
this.name = name;
this.introduce = function () {
return `hi, my name is ${this.name}`;
};
}
// 인스턴스 생성
const person1 = new Person('jeong');
const person2 = new Person('lee');
생성자 함수는 이름 그대로 객체를 생성하는 함수다.
하지만 일반 함수와 큰 차이점이 없다. new
연산자와 함께 호출하면 생성자 함수로 동작할 뿐이다.
🔑 추가 설명 : this
여기서 new
키워드를 사용해야 하는 이유는 this 바인딩과 관련이 있다.
this
는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수다. this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다.
바인딩은 식별자와 값을 연결하는 과정을 의미하며, this 바인딩은 함수가 호출될 때 this가 참조하는 객체를 결정하는 것을 의미한다.
- 기본 바인딩 : 함수가 일반 함수로 호출될 때 적용되며, 이 경우
this
는 전역 객체를 가리킨다.- 전역 객체는 브라우저 환경에서는
window
객체, Node.js 환경에서는global
객체를 가리킨다.
- 전역 객체는 브라우저 환경에서는
- 암시적 바인딩 : 함수가 객체의 메서드로 호출될 때 적용되며, 이 경우
this
는 그 객체를 가리킨다. - 명시적 바인딩 :
call
,apply
,bind
메서드를 사용하여this
를 명시적으로 지정할 때 적용된다. - new 바인딩 :
new
키워드를 사용해 함수를 호출하면 새로운 객체가 생성되고,this
는 그 새로운 객체를 가리킨다.
만약 새로운 생성자 함수를 통해 객체를 생성하는데 new
키워드를 사용하지 않으면 this
가 새로운 객체가 아닌 전역 객체를 가리키므로 this.name
과 같은 코드가 정상적으로 작동하지 않아 아래와 같은 불상사가 일어나게 된다.
function Person(name) {
this.name = name;
}
// new 키워드 없이 호출
const bob = Person('Bob');
console.log(bob); // 출력: undefined
console.log(window.name); // 출력: Bob (브라우저 환경)
하지만 사람이 실수도 좀 할 수 있지. 만약에 new
키워드 없이 호출해도 원하는대로 기능하게 하려면 함수 내부에서 this
바인딩이 올바르게 되었는지 확인할 수도 있다
function Person(name) {
if (!(this instanceof Person)) { // 만약 this 바인딩이 되지 않았다면
return new Person(name); // new 키워드를 사용해 자신을 호출
}
this.name = name;
}
const charlie = Person('Charlie'); // new 없이 호출
console.log(charlie.name); // 출력: Charlie
const dave = new Person('Dave');
console.log(dave.name); // 출력: Dave
생성자 함수의 인스턴스 생성 과정
생성자 함수의 역할은 인스턴스를 생성하는 것과 생성된 인스턴스를 초기화(인스턴스 프로퍼티 추가 및 초기값 할당)하는 것이다.
이때, 인스턴스를 생성하는 것은 필수이고 초기화하는 것은 옵션이다.
앞서 살펴본 Person 생성자 함수를 다시 보자.
function Person(name) {
this.name = name;
this.introduce = function () {
return `hi, my name is ${this.name}`;
};
}
어떠한 의문점이 생긴다. 이 함수는 그 자체로 뭔가를 반환하고 있지 않는데 어떻게 인스턴스를 생성하고 반환하는 것일까?
자바스크립트 엔진은 암묵적인 처리를 통해 인스턴스를 생성하고 반환한다.
new
연산자와 함께 생성자 함수를 호출하면 자바스크립트 엔진이 다음과 같은 과정을 거쳐 암묵적으로 인스턴스 생성, 초기화, 반환을 수행한다. 자바스크립트 엔진은 암묵적으로 하는 일이 참 많다..ㅎㅎ
1. 인스턴스 생성과 this 바인딩
암묵적으로 빈 객체가 생성된다. 이때 생성된 빈 객체가 생성자 함수가 생성한 인스턴스며, 이 빈 객체가 this에 바인딩 된다. 생성자 함수 내부의 this가 생성자 함수가 생성할 인스턴스를 가리키는 이유가 바로 이것이다.
이 처리는 런타임 이전에 실행된다.
function Person(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this 바인딩
console.log(this); // Person {}
this.name = name;
}
2. 인스턴스 초기화
생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩 되어 있는 인스턴스를 초기화한다.
3. 인스턴스 반환
생성자 함수 내부의 모든 코드가 실행되어 처리가 완료되면, 완성된 인스턴스가 바인딩된 this를 암묵적으로 반환한다.
만약 생성자 함수 내에서 다른 객체를 명시적으로 반환하면 암묵적인 this 반환이 일어나지 않는다.
function Person(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this 바인딩
console.log(this); // Person {}
// 2. 인스턴스 초기화
this.name = name;
// 다른 객체 명시적 반환
return {}
}
console.log(new Person('jeong')); // {}
이건 객체를 반환했을 때 일어나는 일이지, 만약 원시값을 반환하면 무시되고 this가 반환된다.
function Person(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this 바인딩
console.log(this); // Person {}
// 2. 인스턴스 초기화
this.name = name;
// 다른 원시값 명시적 반환
return 10;
}
console.log(new Person('jeong')); // Person { name: 'jeong' }
이렇게 생성자 함수 내부에서 명시적으로 뭔가를 return 하는 것은 생성자 함수의 기본 동작을 훼손하는 일이므로 생성자 함수 내부에서는 return 문을 반드시 생략해야 한다.
constructor와 non-constructor의 구분
생성자 함수로서 호출할 수 있는 객체를 constructor, 일반 함수로서만 호출할 수 있는 객체를 non-constructor라고 한다.
자바스크립트 엔진은 함수 정의 방식에 따라 함수를 구분한다.
- constructor : 함수 선언문, 함수 표현식, 클래스
- non-constructor : 메서드(ES6 메서드 축약 표현), 화살표 함수
여기서 주의할 점은 생성자 함수로 만들지 않은 일반 함수에 new 연산자를 붙여 호출하면 생성자 함수처럼 동작할 수 있다는 것이다. 일반 함수와 생성자 함수에 형식적 차이가 없기 때문에 발생하는 일이다. 따라서 구분을 위해 생성자 함수는 첫 문자를 대문자로 기술하는 파스칼 케이스로 명명한다.
들어가기 전에
객체 리터럴에 의한 객체 생성 방식
객체 리터럴에 의한 객체 생성 방식은 가장 일반적이고 간단한 객체 생성 방식이다. 그도 그럴게 매우 직관적이고 간편하다. 아래의 코드를 보자.
const person1 = {
name: 'jeong',
introduce() {
return 'hi, my name is jeong'
}
}
이걸 보고 person1
이 이름이 jeong이고 자기를 소개할 수 있다는 것을 모른다? 그것은 코드 실력과 무관한 문해력의 영역이 아닐까 싶다.
하지만 객체 리터럴에 의한 객체 생성 방식은 단 하나의 객체만 생성한다. 따라서 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 수작업으로 아래와 같은 개고생을 해줘야한다.
const person1 = {
name: 'jeong',
introduce() {
return 'hi, my name is jeong'
}
}
const person2 = {
name: 'lee',
introduce() {
return 'hi, my name is lee'
}
}
const person3 = {
name: 'kim',
introduce() {
return 'hi, my name is kim'
}
}
이건 진짜 귀찮은 일이다. 세상사는 귀찮은 일을 귀찮지 않게 발전시키면서 발전해나가지 않나? 이런 귀찮음을 방지하기 위해 만들어진 게 생성자 함수가 아닐까 싶다.
person 객체를 보면 프로퍼티 값이 다를 수는 있지만 구조가 모두 동일하다. 객체 리터럴에 의해 객체를 생성하면 매번 같은 프로퍼티와 메서드를 기술해야 한다. 이 단점을 극복하고 귀찮음에서 탈출하려면 어떻게 해야할까? 구조를 템플릿으로 만들어놓고 달라지는 값만 입력해주면 되지 않을까?
생성자 함수에 의한 객체 생성 방식
생성자 함수에 의한 객체 생성 방식은 객체(인스턴스)를 생성하기 위한 템플릿(클래스)처럼 생성자 함수를 사용해 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.
// 생성자 함수
function Person(name) {
this.name = name;
this.introduce = function () {
return `hi, my name is ${this.name}`;
};
}
// 인스턴스 생성
const person1 = new Person('jeong');
const person2 = new Person('lee');
생성자 함수는 이름 그대로 객체를 생성하는 함수다.
하지만 일반 함수와 큰 차이점이 없다. new
연산자와 함께 호출하면 생성자 함수로 동작할 뿐이다.
🔑 추가 설명 : this
여기서 new
키워드를 사용해야 하는 이유는 this 바인딩과 관련이 있다.
this
는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수다. this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다.
바인딩은 식별자와 값을 연결하는 과정을 의미하며, this 바인딩은 함수가 호출될 때 this가 참조하는 객체를 결정하는 것을 의미한다.
- 기본 바인딩 : 함수가 일반 함수로 호출될 때 적용되며, 이 경우
this
는 전역 객체를 가리킨다.- 전역 객체는 브라우저 환경에서는
window
객체, Node.js 환경에서는global
객체를 가리킨다.
- 전역 객체는 브라우저 환경에서는
- 암시적 바인딩 : 함수가 객체의 메서드로 호출될 때 적용되며, 이 경우
this
는 그 객체를 가리킨다. - 명시적 바인딩 :
call
,apply
,bind
메서드를 사용하여this
를 명시적으로 지정할 때 적용된다. - new 바인딩 :
new
키워드를 사용해 함수를 호출하면 새로운 객체가 생성되고,this
는 그 새로운 객체를 가리킨다.
만약 새로운 생성자 함수를 통해 객체를 생성하는데 new
키워드를 사용하지 않으면 this
가 새로운 객체가 아닌 전역 객체를 가리키므로 this.name
과 같은 코드가 정상적으로 작동하지 않아 아래와 같은 불상사가 일어나게 된다.
function Person(name) {
this.name = name;
}
// new 키워드 없이 호출
const bob = Person('Bob');
console.log(bob); // 출력: undefined
console.log(window.name); // 출력: Bob (브라우저 환경)
하지만 사람이 실수도 좀 할 수 있지. 만약에 new
키워드 없이 호출해도 원하는대로 기능하게 하려면 함수 내부에서 this
바인딩이 올바르게 되었는지 확인할 수도 있다
function Person(name) {
if (!(this instanceof Person)) { // 만약 this 바인딩이 되지 않았다면
return new Person(name); // new 키워드를 사용해 자신을 호출
}
this.name = name;
}
const charlie = Person('Charlie'); // new 없이 호출
console.log(charlie.name); // 출력: Charlie
const dave = new Person('Dave');
console.log(dave.name); // 출력: Dave
생성자 함수의 인스턴스 생성 과정
생성자 함수의 역할은 인스턴스를 생성하는 것과 생성된 인스턴스를 초기화(인스턴스 프로퍼티 추가 및 초기값 할당)하는 것이다.
이때, 인스턴스를 생성하는 것은 필수이고 초기화하는 것은 옵션이다.
앞서 살펴본 Person 생성자 함수를 다시 보자.
function Person(name) {
this.name = name;
this.introduce = function () {
return `hi, my name is ${this.name}`;
};
}
어떠한 의문점이 생긴다. 이 함수는 그 자체로 뭔가를 반환하고 있지 않는데 어떻게 인스턴스를 생성하고 반환하는 것일까?
자바스크립트 엔진은 암묵적인 처리를 통해 인스턴스를 생성하고 반환한다.
new
연산자와 함께 생성자 함수를 호출하면 자바스크립트 엔진이 다음과 같은 과정을 거쳐 암묵적으로 인스턴스 생성, 초기화, 반환을 수행한다. 자바스크립트 엔진은 암묵적으로 하는 일이 참 많다..ㅎㅎ
1. 인스턴스 생성과 this 바인딩
암묵적으로 빈 객체가 생성된다. 이때 생성된 빈 객체가 생성자 함수가 생성한 인스턴스며, 이 빈 객체가 this에 바인딩 된다. 생성자 함수 내부의 this가 생성자 함수가 생성할 인스턴스를 가리키는 이유가 바로 이것이다.
이 처리는 런타임 이전에 실행된다.
function Person(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this 바인딩
console.log(this); // Person {}
this.name = name;
}
2. 인스턴스 초기화
생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩 되어 있는 인스턴스를 초기화한다.
3. 인스턴스 반환
생성자 함수 내부의 모든 코드가 실행되어 처리가 완료되면, 완성된 인스턴스가 바인딩된 this를 암묵적으로 반환한다.
만약 생성자 함수 내에서 다른 객체를 명시적으로 반환하면 암묵적인 this 반환이 일어나지 않는다.
function Person(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this 바인딩
console.log(this); // Person {}
// 2. 인스턴스 초기화
this.name = name;
// 다른 객체 명시적 반환
return {}
}
console.log(new Person('jeong')); // {}
이건 객체를 반환했을 때 일어나는 일이지, 만약 원시값을 반환하면 무시되고 this가 반환된다.
function Person(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this 바인딩
console.log(this); // Person {}
// 2. 인스턴스 초기화
this.name = name;
// 다른 원시값 명시적 반환
return 10;
}
console.log(new Person('jeong')); // Person { name: 'jeong' }
이렇게 생성자 함수 내부에서 명시적으로 뭔가를 return 하는 것은 생성자 함수의 기본 동작을 훼손하는 일이므로 생성자 함수 내부에서는 return 문을 반드시 생략해야 한다.
constructor와 non-constructor의 구분
생성자 함수로서 호출할 수 있는 객체를 constructor, 일반 함수로서만 호출할 수 있는 객체를 non-constructor라고 한다.
자바스크립트 엔진은 함수 정의 방식에 따라 함수를 구분한다.
- constructor : 함수 선언문, 함수 표현식, 클래스
- non-constructor : 메서드(ES6 메서드 축약 표현), 화살표 함수
여기서 주의할 점은 생성자 함수로 만들지 않은 일반 함수에 new 연산자를 붙여 호출하면 생성자 함수처럼 동작할 수 있다는 것이다. 일반 함수와 생성자 함수에 형식적 차이가 없기 때문에 발생하는 일이다. 따라서 구분을 위해 생성자 함수는 첫 문자를 대문자로 기술하는 파스칼 케이스로 명명한다.