Programming Language/Javascript

[자바스크립트] 프로토타입과 상속

ggyongi 2025. 1. 4. 22:16
반응형

1. 객체 생성의 두 가지 방법

a. 객체 리터럴 방식

  - 간단한 객체 생성에는 편리하나 동일 구조를 가진 객체를 만드려고 할 때 비효율적임(코드 중복)

const person = {
  name: "Alice",
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  },
};

console.log(person.name); // "Alice"
person.greet(); // "Hello, my name is Alice"

 

b. 생성자 함수 방식

- new 키워드를 사용하여 생성자 함수를 호출

- 새로운 객체를 생성하고 초기화하는 로직을 재사용 가능

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

const alice = new Person("Alice");
const bob = new Person("Bob");

alice.greet(); // "Hello, my name is Alice"
bob.greet();   // "Hello, my name is Bob"

 

2. 프로토타입

a. 기본 개념

- 객체 간 상속을 구현하는 JavaScript의 핵심 메커니즘

- 모든 객체는 프로토타입이라는 속성을 가짐

- 프로토타입은 객체의 부모 역할을 하며, 객체가 가지고 있지 않은 속성과 메서드를 찾는 데 사용됨

 

b. 프로토타입 체인

객체의 속성이나 메서드를 접근할 때, 가장 먼저 현재 객체에서 해당 속성이 있는지 확인하고
없다면, 프로토타입 객체로 이동하여 속성을 탐색. 그래도 없으면 프로토타입 체인을 따라 최상위 객체(Object.prototype)까지 탐색.

(프로토타입 체인의 끝: Object.prototype의 프로토타입은 null로, 체인의 끝을 나타냄)

const parent = {
  greet() {
    console.log("Hello from parent");
  },
};

const child = Object.create(parent); // parent를 child의 프로토타입으로 설정
child.name = "Child";

console.log(child.name); // "Child" (child 객체에서 찾음)
child.greet();           // "Hello from parent" (프로토타입 체인을 따라 parent에서 찾음)
console.log(child.__proto__ === parent); // true

 

c. __proto__와 prototype

__proto__

- 모든 객체가 가지는 숨겨진 프로퍼티로, 객체의 프로토타입에 대한 참조를 제공함

- 프로토타입 체인 탐색에 사용됨

- 직접 사용하는 것은 권장되지 않으며, 대신 Object.getPrototypeOf()를 사용하는 것이 좋음

 

prototype

- 함수 객체만 가지는 속성으로, 해당 함수로 생성된 객체들이 참조할 프로토타입을 정의함

- 주로 생성자 함수와 함께 사용됨

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

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

const dog = new Animal("Dog");

console.log(dog.__proto__ === Animal.prototype); // true
dog.speak(); // "Dog makes a noise"

 

* 생성자 내부 방식과 비교

위의 코드처럼 프로토타입으로 공통 함수를 정의하는 거랑

아래처럼 생성자 함수 내부에서 공통 함수를 정의하는 거랑 뭐가 다를까?

function PersonInternal(name) {
  this.name = name;
  this.greet = function () {
    console.log(`Hello, my name is ${this.name}`);
  };
}

const aliceInternal = new PersonInternal("Alice");
const bobInternal = new PersonInternal("Bob");

console.log(aliceInternal.greet === bobInternal.greet); // false (메서드 중복)

 

생성자 내부 정의: 객체마다 메서드가 생성됨 → 메모리 낭비

프로토타입 정의: 메서드가 한 번만 생성되고 공유됨 → 효율적

 

3. 프로토타입의 상속 방법

a. 기본적인 상속 방법: Object.create() 함수 사용 => 프로토타입 체인을 따라 부모 객체의 메서드를 상속받음

const parent = {
  greet() {
    console.log("Hello from parent");
  },
};

const child = Object.create(parent); // parent를 프로토타입으로 설정
child.name = "Child";

child.greet(); // "Hello from parent" (프로토타입 체인을 통해 상속)

 

b. 생성자 함수와 상속

Dog 생성자

- Animal 생성자를 호출해 속성을 상속

- Object.create로 부모의 메서드 상속

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  console.log("Animal speaks");
};

function Dog(name) {
  Animal.call(this, name); // 부모 생성자의 속성을 상속
}

// 프로토타입끼리 상속한다고 생각해야함
// 어차피 speak도 Animal의 프로토타입에 있는 함수이기때문에
Dog.prototype = Object.create(Animal.prototype);

const dog = new Dog();
dog.speak(); // "Animal speaks" (Dog.prototype이 Animal.prototype을 상속)

console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true, 최상위 프로토타입 Object.prototype을 자동으로 상속
console.log(Object.prototype.__proto__); // null

 

c. ES6 클래스 상속

- extends로 부모 클래스를 상속

- super를 통해 부모 클래스의 메서드를 호출 가능

- 프로토타입 기반 상속을 더 직관적으로 표현

class Animal {
  constructor(name) {
    this.name = name;
  }

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

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks`);
  }
}

const dog = new Dog("Buddy");
dog.speak(); // "Buddy barks"