자바스크립트 패턴 — 3.5 new 객체 생성
1. new 객체와 객체 리터럴의 차이
배열을 new Array()로도 생성할 수 있고 [] 대괄호로도 생성할 수 있는데, 왜 대체로 []를 사용하며 둘의 차이는 무엇일까요?
const a1 = [5] // 배열의 첫 번째 공간에 5라는 값이 들어 있습니다.
const a2 = new Array(5) // 배열 공간이 5개 생성됩니다.이처럼 확연한 차이가 있습니다. 일반적으로 객체 리터럴 방식을 선호하는 이유는 다음과 같습니다.
- 코딩하기 편하며 가독성이 좋습니다.
- 속도가 빠릅니다.
new객체 생성 방식은 오버라이딩이 가능하므로 위험합니다. 객체 리터럴을 활용하면 자바스크립트의 오버라이딩을 방지할 수 있습니다.
이 책에서 말하는 new 객체 생성은 단순히 new Object() 방식이 아닌 new 연산자를 활용한 생성자 함수 패턴입니다.
생성자 함수 기준으로 객체를 생성했을 때 객체 리터럴과의 차이를 확인해 보겠습니다.
function chimp (hasThumb, hasSwing) {
this.hasThumb = hasThumb
this.hasSwing = hasSwing
}
const a1 = {
hasThumb : true,
hasSwing : false,
}
const a2 = new chimp()
const a3 = new chimp(false, true)
console.log(a1) // --- (1)
console.log(a2) // --- (2)
console.log(a3) // --- (3)![]()
이 결과에서 알 수 있는 사항은 다음과 같습니다.
- 객체 리터럴 방식과
new연산자를 활용한 생성자 함수는 객체의prototype내constructor(생성자)의 property 값이 다르게 생성됩니다. - 변수
a2와a3은 동일한 이름의 함수를 참조해서 생성했지만 서로의 값에 영향을 주지 않으며, 객체를 보다 쉽게 생성할 수 있습니다. constructor내부에 새로운prototype이 있는 이유는, 자바스크립트에서는 함수도 Object의 영역에 포함되기 때문에new연산자로 생성할 때chimp()함수에 대한prototype정보도 함께 가져오기 때문입니다.
추가로, 생성자 함수와 일반 함수를 구분하기 위해 개발자들은 생성자 함수의 경우 함수명을 파스칼 표기법(대문자로 시작) 으로 표기하는 것을 하나의 약속으로 합니다.
2. new 객체 생성 패턴
작업자간의 약속만으로는 강제력이 없으므로, 코드 내에서 직접 new 사용을 강제할 수 있습니다.
function Chimp (hasThumb, hasSwing) {
if (!(this instanceof Chimp)) {
throw new Error('이 객체는 new를 사용하여 생성해야 합니다')
}
this.hasThumb = hasThumb
this.hasSwing = hasSwing
}
const a1 = Chimp() // Error 메시지가 호출됩니다.
const a2 = new Chimp()instanceof 연산자를 사용하면 객체가 특정 클래스에 포함되는지 여부와 상속관계 등을 확인할 수 있습니다.
에러 대신 new 연산자를 붙여 자동으로 반환하도록 처리할 수도 있습니다.
function Chimp (hasThumb, hasSwing) {
if (!(this instanceof Chimp)) {
return new Chimp(hasThumb, hasSwing)
}
this.hasThumb = hasThumb
this.hasSwing = hasSwing
}다만 이 경우, 작업자마다 new 사용 여부가 달라져 코드의 일관성 문제가 생길 수 있습니다. 작업자가 new를 붙이도록 유도하는 것이 더 좋습니다.
ES6에서는 이를 보다 직관적으로 확인할 수 있는 속성이 추가되었습니다.
function Chimp (hasThumb, hasSwing) {
if (!(new.target)) {
throw new Error('이 객체는 new를 사용하여 생성해야 합니다')
}
this.hasThumb = hasThumb
this.hasSwing = hasSwing
}new.target은 new 연산자를 사용하여 호출했는지를 의미하기 때문에 의미적으로 훨씬 직관적입니다. 다만 Internet Explorer에서는 사용이 불가하므로 호환성에 주의해야 합니다.
참고: new.target - MDN
3. 생성자 함수 내에 함수 추가 vs 프로토타입에 함수 추가
function Chimp (hasThumb, activeTime) {
if (!(this instanceof Chimp)) {
throw new Error('이 객체는 new를 사용하여 생성해야 합니다')
}
this.hasThumb = hasThumb
this.activeTime = activeTime
// 각 객체 인스턴스는 자신만의 activeMoment 사본을 가집니다.
this.activeMoment = function (daily) {
return daily === this.activeTime
}
}
const a1 = new Chimp('a1', 'Morning')
const a2 = new Chimp('a2', 'Afternoon')function Monkey (hasThumb, activeTime) {
if (!(this instanceof Monkey)) {
throw new Error('이 객체는 new를 사용하여 생성해야 합니다')
}
this.hasThumb = hasThumb
this.activeTime = activeTime
}
// 프로토타입에 함수를 정의합니다.
Monkey.prototype.activeMoment = function (daily) {
return daily === this.activeTime
}
const b1 = new Monkey('b1', 'Morning')
const b2 = new Monkey('b2', 'Afternoon')직접 검증하기는 어렵지만, 책에서는 프로토타입에 함수를 정의하면 생성자 함수 내부에 함수 프로퍼티를 추가할 때보다 메모리 점유율을 낮추고 성능을 높이는 이점이 있다고 설명합니다. 인스턴스마다 함수 사본을 갖지 않고 프로토타입 하나를 공유하기 때문입니다.