프로토타입 - object.create(), 생성자 함수, 클래스 기반

oYJo·2025년 4월 3일

JavaScript

목록 보기
52/52

객체를 생성하는 방법

  1. 객체 리터럴 : 가장 간단하고 편리하다
  2. Object 생성자 함수
  3. 생성자 함수
  4. Object.create
  5. 클래스

프로토타입

자바스크립트는 기본적으로 프로토타입 기반 언어이다

모든 객체는 Object.getPrototypeOf(obj)를 통해 프로토타입 체인에 접근할 수 있다

프로토타입 체인을 활용하면 객체 간 속성과 메서드를 공유할 수 있다

✔️프로토타입 체인
프로토타입 체인에 따라 속성, 메서드를 검색한다
상위에서 물려받은 객체 정보를 담은 것
= 프로토타입 상속 관계를 보여준다

1️⃣ object.create 함수, ES5상속

Object.create(proto)

정의 : 지정된 프로토타입 객체 & 속성을 새 객체를 만든다

매개변수. :

  1. proto : 새로 만든 객체의 프로토타입이어야 할 객체

  2. propertiesObject(선택사항)

    지정되고 undefined가 아니면, 자신의 속성
    (즉, 자체에 정의되어 그 프로토타입 체인에서 열거가능하지 않은 속성)이 열거 가능한 객체는 해당 속성명으로 새로 만든 객체에 추가될 속성 설명자(descriptor)를 지정한다

    이러한 속성은Object.defineProperties()의 두 번째 인수에 해당한다

특정 객체를 프로토타입으로 설정해서 새 객체를 생성할 수 있다

생성자 함수를 사용하지 않고 간결한 방식으로 상속을 구현할 수 있다

상속받은 객체를 커스텀하여 확장할 수도 있습니다.

✔️proto = [[prototype]]
함수를 포함한 모든 객체가 가지고 있는 인터널 슬롯
객체 입장에서 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다
함수 객체 경우 → Function.prototype을 가리킨다

Object.create(null)을 사용해 __proto__가 없는 '아주 단순한 객체’를 만들거나, 을 사용하는게 좋다

한편, Object.create를 사용하면 객체의 얕은 복사본(shallow-copy)을 만들 수 있다

✔️얕은 복사 vs 깊은 복사

얕은 복사

  • 원시 타입 → 해당 값 복사
  • 객체, 배열 등 참조 타입 → 참조하는 메모리 주소 복사
    최상위 속성만 복사하고 중첩된 객체 & 배열은 그대로 참조하기에 원본 데이터에 영향 줄 수 있다

    깊은 복사
  • 객체, 배열 등 모든 속성 값을 재귀적으로 탐색해서 복사한다
  • 원본 객체 & 배열, 복사본 객체 & 배열은 서로 다른 메모리를 참조하게 된다

    복사본을 수정해도 원본 데이터에 영향가지 않는다
const person = {
	greet() {
console.log("Hello!");
	}
};
const user = Object.create(person);
user.greet(); // Hello!

__proto__

다소 구식이기에 더는 사용하지 않는다
↓ 아래 3 방법을 권장한다

Object.create(proto, [descriptors])

– [[Prototype]]이 proto를 참조하는 빈 객체를 만든다. 이때 프로퍼티 설명자를 추가로 넘길 수 있다

Object.getPrototypeOf(obj)

– obj의 [[Prototype]]을 반환한다(proto getter와 같다)

Object.setPrototypeOf(obj, proto)

– obj의 [[Prototype]]이 proto가 되도록 설정한다(proto setter와 같다)

❓Object.create 상속은 언제 사용?
가볍고, 유연적인 방식이라
객체 간 상속은 필요하는데, 생성자 함수까진 사용하지 않을 때 사용할 수 있다
하지만, 메서드 추가와 확장할 때 불편하다

→ 그래서 생성자 함수 기반 상속이 나왔다
생성자 함수 기반 상속은 new 연산자와 함께 호출하고, 프로퍼티 & 메서드를 추가할 수 있다


2️⃣ 생성자 함수, ES5 상속

생성자 함수 기반 상속

ES5에서는 생성자 함수를 사용하여 상속을 구현한다

부모 객체의 생성자를 호출하여 속성을 상속받고, prototype을 조작하여 메서드를 상속한다

function Animal(name) {
	this.name = name;
}

Animal.prototype.speak = function() {
	console.log(`${this.name} makes a noise.`);
};

function Dog(name) {
	Animal.call(this, name); // 부모 생성자 호출
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const myDog = new Dog('Buddy');
myDog.speak(); // Buddy makes a noise.

예시

function Car(){
    this.wheel = 18;
};

Car.prototype.cc = 2000;

//Using Object.create()
var audi = new Car();
var bmw = Object.create(Car.prototype);

console.log(bmw.wheel); //undefined
console.log(bmw.cc); //2000

//Using New Keyword
console.log(audi.wheel); //18
console.log(audi.cc); //2000

Car 생성자가 실행되지 않기 때문, Object.create(Car.prototype) 사용

Object.create(Car.prototype)가 Car 생성자를 실행하지 않기 때문이다

new Car()를 호출하면 Car 생성자 함수가 실행된다

  • this.wheel = 18;이 실행되어 audi 객체에 wheel 속성이 직접 추가됨.

audi.proto === Car.prototype이므로 cc 속성을 프로토타입에서 찾을 수 있다

결과적으로 audi.wheel === 18, audi.cc === 2000이 출력된다

var bmw = Object.create(Car.prototype);

Object.create(Car.prototype)는 새로운 객체를 만들고 그 객체의 프로토타입을 Car.prototype으로 설정할 뿐이다

하지만 Car 생성자 함수가 실행되지 않기 때문에 this.wheel = 18;이 실행되지 않음.

즉, bmw 객체에는 wheel 속성이 없고, 프로토타입 체인(bmw.proto)을 따라가도 Car.prototype에는 wheel 속성이 없으므로 undefined가 된다

반면 Car.prototype.cc = 2000;으로 정의된 cc 속성은 bmw.proto에 존재하므로 bmw.cc === 2000이 출력된다

해결 방법

bmw 객체도 wheel 속성을 가지게 하려면 Car 생성자를 실행해야 한다

  1. 방법 1: new 키워드 사용
var bmw = new Car();
console.log(bmw.wheel); // 18
  1. 방법 2: Object.create() 후 생성자 호출
var bmw = Object.create(Car.prototype);
Car.call(bmw); // 생성자 호출
console.log(bmw.wheel); // 18

3️⃣ ES2015(ES6) Class 기반 상속

ES6 Class 기반 상속

ES6에서는 class 문법이 도입되어 좀 더 직관적이게 사용할 수 있다

클래스도 결국 프로토타입 기반으로 동작

클래스 내부에서 constructor 메서드를 통해 객체 초기 상태 설정 가능

  • extends 키워드 : 부모 클래스 상속 가능 클래스 뿐만 아니라, 생성자 함수를 상속받아 클래스를 확장할 수 있다 키워드 앞에 반드시 클래스가 와야한다 = 상속받을 대상 결정한다
  • super() : 부모 생성자 호출 가능 수퍼 클래스의 constructor 호출해서 인스턴스 생성
class Base {}
    
class Derived extends Base {}
    
class Base {
      constructor() {}
}
    
class Derived extends Base {
      constructor(...args) { super(..args); }
}
    ```
    
```jsx
    class Base {
      constructor(x, y) {
        console.log(this); // Derived {}
        console.log(new.target); //Derived
      }
    }
    
    class Derived {}

✔️new.target
new 연산자와 함께 호출된 함수를 가리킨다

class Animal {
	constructor(name) {
	[this.name](http://this.name/) = name;
	}

	speak() {
    console.log(`${this.name} makes a noise`);
	}

}

class Dog extends Animal {
	constructor(name, breed) {
	super(name);
	this.breed = breed;
	}
	speak() {
    console.log(`${this.name} barks.`);
	}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // Buddy barks.
  • 클래스는 new 연산자 없이 호출하면 에러가 발생한다 인스턴스 생성이 목적이기에, 반드시 new 연산자와 함께 호출해야한다
  • 클래스는 호이스팅이 발생하지 않는 것처럼 동작한다 바인딩 실행되기 전까지 액세스할 수 없는 TDZ에 머문다
  • 클래스 내 모든 코드는 암묵적으로 strict mode가 지정되어 실행된다
  • 열거되지 않는다 [[Enumerable]] 값 = false

❓ES5의 생성자 함수 상속과 ES6 클래스 상속의 차이점

ES5 생성자 함수는 프로토타입 체인 활용, 생성자 기반으로 만드는 방식
call, Object.create, prototype 조작 등이 필요하다
→ 코드가 길어지고 가독성이 떨어진다

ES6 Class 상속은 직관적, super 사용(부모 클래스 기능 쉽게 확장 가능)
→ 하지만 이 클래스도 결국 내부적으론 프로토타입을 활용하는거라 기존 방식보단 편리하게 보이지만 근원적으로 같은 개념이다

❓프로토타입 상속, ES6 클래스 상속 중 어떤 방식이 더 좋은가?
프로토타입 상속은 자바스크립트 기본인 만큼
하지만 복잡한 코드, new 키워드 사용, this 바인딩이 어렵다는 단점이 있다
하지만 ES6 클래스는 결합성이 높기에 변경하기 어렵다
→ 계급 계층구조으로 만들어졌기에

profile
Hello! My Name is oYJo

0개의 댓글