new 연산자

new 연산자

new 연산자는 개발자가 사용자 정의 객체 타입의 인스턴스나, 생성자 함수를 가진 내장 객체 타입 중 하나의 인스턴스를 만들 수 있게 해 줍니다. Baseline Widely available — 2015년 7월부터 여러 브라우저에서 지원되는 잘 정착된 기능입니다.

출처: new

본문

new 연산자는 개발자가 사용자 정의 객체 타입 또는 생성자 함수(constructor function)를 가진 내장 객체 타입의 인스턴스를 만들 수 있게 해 줍니다.

시도해 보기 (Try it)

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

const car1 = new Car("Eagle", "Talon TSi", 1993);

console.log(car1.make);
// Expected output: "Eagle"

구문 (Syntax)

new constructor
new constructor()
new constructor(arg1)
new constructor(arg1, arg2)
new constructor(arg1, arg2, /* …, */ argN)

매개변수 (Parameters)

  • constructor: 객체 인스턴스의 타입을 지정하는 클래스 또는 함수입니다. 이 표현식은 충분한 우선순위를 가진 무엇이든 될 수 있는데, 식별자, 속성 접근, 또 다른 new 표현식이 포함되지만 옵셔널 체이닝은 허용되지 않습니다.
  • arg1, arg2, argN: 생성자가 호출될 때 전달되는 값들의 목록입니다. new Foonew Foo()와 동등합니다. 즉, 인자 목록을 지정하지 않으면 Foo는 인자 없이 호출됩니다.

설명 (Description)

함수가 new 키워드로 호출되면 그 함수는 생성자로 사용됩니다. new는 다음 작업을 수행합니다:

  1. 비어 있는 평범한 JavaScript 객체를 만듭니다. 편의상 이를 newInstance라고 부릅니다.
  2. 프로토타입이 객체(Object)라면 newInstance[[Prototype]]을 생성자 함수의 prototype 속성으로 가리키게 합니다. 그렇지 않으면 newInstanceObject.prototype[[Prototype]]으로 하는 평범한 객체로 남습니다. 참고: 생성자 함수의 prototype 속성에 추가된 속성/객체는 그 생성자 함수로 만들어진 모든 인스턴스에서 접근할 수 있습니다.
  3. 주어진 인자들로 생성자 함수를 실행하며, newInstancethis 컨텍스트로 바인딩합니다(즉, 생성자 함수 안에서 this를 참조하는 모든 곳이 이제 newInstance를 가리킵니다).
  4. 생성자 함수가 비원시(non-primitive) 값을 반환하면 그 반환값이 전체 new 표현식의 결과가 됩니다. 그렇지 않고 생성자 함수가 아무것도 반환하지 않거나 원시 값을 반환하면 대신 newInstance가 반환됩니다. (보통 생성자는 값을 반환하지 않지만, 정상적인 객체 생성 과정을 덮어쓰기 위해 값을 반환하도록 선택할 수 있습니다.)

클래스는 new 연산자로만 인스턴스화할 수 있습니다 — 클래스를 new 없이 호출하려 하면 TypeError가 발생합니다.

사용자 정의 생성자 함수로 객체를 만드는 데는 두 단계가 필요합니다:

  1. 이름과 속성을 지정하는 함수를 작성해 객체 타입을 정의합니다. 예를 들어 객체 Foo를 만드는 생성자 함수는 다음과 같습니다:
function Foo(bar1, bar2) {
  this.bar1 = bar1;
  this.bar2 = bar2;
}
  1. new로 객체의 인스턴스를 만듭니다:
const myFoo = new Foo("Bar 1", 2021);

참고: 어떤 객체는 그 자체가 또 다른 객체인 속성을 가질 수 있습니다. 아래 예제들을 참고하세요.

이미 정의된 객체 인스턴스에는 언제든 속성을 추가할 수 있습니다. 예를 들어 car1.color = "black" 문장은 car1color 속성을 추가하고 값 "black"을 할당합니다.

하지만 이는 다른 객체에는 영향을 주지 않습니다. 새 속성을 같은 타입의 모든 객체에 추가하려면 생성자의 prototype 속성에 속성을 추가해야 합니다. 이는 그 함수로 만들어진 모든 객체가 공유하는 속성을 정의하며, 객체 타입의 인스턴스 하나만이 아니라 전부가 공유합니다. 다음 코드는 Car 타입의 모든 객체에 "original color" 값을 가진 color 속성을 추가하고, 그 후 인스턴스 객체 car1에서만 그 값을 문자열 "black"으로 덮어씁니다. 자세한 내용은 prototype 문서를 참고하세요.

function Car() {}
const car1 = new Car();
const car2 = new Car();

console.log(car1.color); // undefined

Car.prototype.color = "original color";
console.log(car1.color); // 'original color'

car1.color = "black";
console.log(car1.color); // 'black'

console.log(Object.getPrototypeOf(car1).color); // 'original color'
console.log(Object.getPrototypeOf(car2).color); // 'original color'
console.log(car1.color); // 'black'
console.log(car2.color); // 'original color'

참고: 생성자 함수는 일반 함수처럼(new 없이) 호출될 수도 있지만, 그 경우 새 객체가 생성되지 않고 this의 값도 달라집니다.

함수는 new.target을 확인해 new로 호출되었는지 알 수 있습니다. new.target은 함수가 new 없이 호출될 때만 undefined입니다. 예를 들어, 호출될 때와 생성될 때 다르게 동작하는 함수를 가질 수 있습니다:

function Car(color) {
  if (!new.target) {
    // Called as function.
    return `${color} car`;
  }
  // Called with new.
  this.color = color;
}

const a = Car("red"); // a is "red car"
const b = new Car("red"); // b is `Car { color: "red" }`

클래스를 도입한 ES6 이전에는 대부분의 JavaScript 내장 기능이 호출 가능하면서도 생성 가능했지만, 그중 상당수가 서로 다른 동작을 보였습니다. 몇 가지를 들자면:

  • Array(), Error(), Function()은 함수로 호출하든 생성자로 사용하든 동일하게 동작합니다.
  • Boolean(), Number(), String()은 호출되면 그 인자를 각각의 원시 타입으로 강제 변환하고, 생성되면 래퍼 객체를 반환합니다.
  • Date()는 호출되면 현재 날짜를 나타내는 문자열을 반환하는데, 이는 new Date().toString()과 동등합니다.

ES6 이후에는 어떤 것이 생성자이고 어떤 것이 함수인지에 대해 언어가 더 엄격해졌습니다. 예를 들어:

  • Symbol()BigInt()new 없이만 호출할 수 있습니다. 생성하려 하면 TypeError가 발생합니다.
  • ProxyMapnew로만 생성할 수 있습니다. 호출하려 하면 TypeError가 발생합니다.

예제 (Examples)

객체 타입과 객체 인스턴스

자동차를 위한 객체 타입을 만들고 싶다고 가정해 봅시다. 이 타입의 객체를 Car라고 부르고, make, model, year 속성을 가지게 하려고 합니다. 이를 위해 다음 함수를 작성합니다:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

이제 다음과 같이 myCar라는 객체를 만들 수 있습니다:

const myCar = new Car("Eagle", "Talon TSi", 1993);

이 문장은 myCar를 만들고 속성에 지정된 값들을 할당합니다. 그러면 myCar.make의 값은 문자열 "Eagle", myCar.year의 값은 정수 1993이 되는 식입니다.

new를 호출하면 원하는 만큼 많은 자동차 객체를 만들 수 있습니다. 예를 들어:

const kensCar = new Car("Nissan", "300ZX", 1992);

그 자체가 또 다른 객체인 객체 속성

Person이라는 객체를 다음과 같이 정의한다고 가정해 봅시다:

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

그리고 두 개의 새 Person 객체를 다음과 같이 인스턴스화합니다:

const rand = new Person("Rand McNally", 33, "M");
const ken = new Person("Ken Jones", 39, "M");

그런 다음 Car의 정의를 재작성해 Person 객체를 받는 owner 속성을 포함시킬 수 있습니다:

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
}

새 객체를 인스턴스화하려면 다음을 사용합니다:

const car1 = new Car("Eagle", "Talon TSi", 1993, rand);
const car2 = new Car("Nissan", "300ZX", 1992, ken);

새 객체를 만들 때 리터럴 문자열이나 정수 값을 전달하는 대신, 위 문장들은 randken 객체를 소유자(owner)의 매개변수로 전달합니다. car2의 소유자 이름을 알아내려면 다음 속성에 접근하면 됩니다:

car2.owner.name;

클래스와 함께 new 사용하기

class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const p = new Person("Caroline");
p.greet(); // Hello, my name is Caroline

명세 (Specifications)

new 연산자는 ECMAScript 언어 명세에 정의되어 있습니다.

브라우저 호환성 (Browser compatibility)

이 기능은 대부분의 현대 브라우저에서 널리 지원됩니다(Baseline Widely available). 자세한 호환성 표는 MDN의 브라우저 호환성 섹션을 참고하세요.

더 알아보기