[JS] Prototype이란?

2024. 8. 2. 21:43프로그래밍/Javascript

Prototype이란?

원래 prototype이라는 단어는 시제품이라는 뜻을 가지고 있다. 하지만 여기서 말하는 prototype은 javascript에서 사용하는 prototype이라는 개념이다.

 

(여기서부턴 객체지향 언어의 개념에 대한 이해가 다소 필요할 수 있다.)

 

Javascript에서의 객체

Javascript는 객체 기반의 스크립트 언어다. 하지만 일반적인 객체지향 언어가 객체를 구현하기 위해 class라는 개념을 사용하는 것과 다르게, (ES6 이전의) javascript는 class라는 개념을 사용하지 않는다.

대신 javascript에서는 생성자 함수를 통해 객체를 생성한다. 객체지향 언어에서도 생성자 함수를 통해 객체를 생성하는데 무슨 소린가 싶을 수도 있다. 차이가 있다면 객체지향 언어는 class를 정의하고, 해당 class 내에 class명과 동일한 이름의 함수를 생성자 함수로 정의하여 생성자 함수가 class 내에 포함되어 있는 반면에, javascript에서는 일반 함수를 생성자 함수처럼 동작시켜 객체를 만든다.

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

Person.prototype.greet = function() {
    console.log(`Hello, my name is ${this.name}`);
}; // prototype은 일단 이해하지 않고 넘어간다. 당장은 class의 method를 정의하는거라 이해하면 된다.

const alice = new Person('Alice');
alice.greet(); // "Hello, my name is Alice"

 

위 코드는 javascript에서 간단한 객체를 생성하는 예시다. 일반적인 함수를 정의하듯이 function 키워드를 사용하여 Person이라는 함수를 정의하였고, 이를 생성자 함수로 사용한다. 이후 new라는 키워드를 사용하여 alice라는 객체를 생성한다.

 

Class 상속(inheritance)은 어떻게?

객체지향 언어에서는 상속(inheritancd)이라는 개념이 존재한다. 쉽게 말하면, 일반적인 객체지향 언어에서는 특정 class의 속성과 method를 물려받는 하위 class를 만들 수 있다.

아래는 java에서 class 상속의 예시다.

// 부모 클래스
public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public void speak() {
        System.out.println(name + " makes a sound.");
    }
}

// 자식 클래스
public class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super(name); // 부모 클래스의 생성자 호출
        this.breed = breed;
    }

    // 메서드 오버라이딩
    @Override
    public void speak() {
        System.out.println(super.name + " barks.");
    }
}

 

extends 키워드를 통해 Dog이라는 class가 Animal이라는 class를 상속한다고 선언하여 Dog를 Animal의 하위 class로 선언할 수 있고, super라는 키워드를 통해 상위 class의 데이터에도 접근할 수 있다.

class를 사용하는 객체지향 언어의 경우 상속하는 상위 class에 대한 정보가 class에 정의되어 있지만, class라는 개념을 사용하지 않는 javascript에서는 상속을 구현하기 위해 다른 방식을 선택할 필요가 있었다. (여담으로, 브라우저에서 돌아가는 가벼운 스크립트 언어로 쓰기엔 class라는 구조는 조금 복잡하다고 한다.) 그렇게 해서 나온 개념이 prototype이라는 개념이다.

 

Prototype?

Prototype이란 모든 객체나 함수가 기본적으로 가지는 특성을 정의해 둔 속성이다. 쉽게 말하면, class에 정의하는 것들을 생성자 함수에 직접 저장해둔 것이다. (참고로 javascript에서는 함수 역시 객체의 일종이다. 따라서 함수에 직접적으로 멤버를 정의하는 것이 가능하다.)

위에서 사용한 예시를 다시 갖고오면

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

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

const alice = new Person('Alice');
alice.greet(); // "Hello, my name is Alice"

 

여기서 Person.prototype 내에 greet라는 함수를 정의한다. 이때 Person이라는 생성자 함수를 통해 alice라는 객체를 생성하게 되면, javascript는 alice라는 객체 내에 __proto__라는 속성을 만들고, 이 속성이 생성자 함수의 prototype 속성을 가리키도록 설정된다.

그리고 alice에서 greet라는 함수를 호출하면 javascript는 먼저 alice라는 객체 내에 해당 함수가 정의되어 있는지를 찾는다. 이 경우 alice라는 객체 내에는 해당 함수가 존재하지 않는데, 이때 javascript는 alice.__proto__ 속성으로 넘어가 greet라는 함수가 존재하는지를 찾는다. alice.__proto__는 Person.prototype 속성이고, Person의 prototype에 사용자가 정의한 greet라는 함수가 존재하기 때문에 최종적으로 해당 greet라는 함수를 호출하게 된다.

Person이라는 생성자 함수를 통해 생성된 객체는 모두 __proto__ 속성에 Person.prototype을 가지고 있게 된다. 마치 Person이라는 class 내에 greet라는 method가 정의되어 있는 것과 유사하게 작동하는 것이다.

 

Prototype Chaining?

이것만으론 상속을 구현했다고 보기는 어렵다. 아래 코드는 추가 예시다. 여기서 Employee는 Person을 상속받는다.

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

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

function Employee(name, job) {
    Person.call(this, name); // 부모 생성자 호출
    this.job = job;
}

// Employee의 프로토타입을 Person의 인스턴스로 설정
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

Employee.prototype.work = function() {
    console.log(`${this.name} is working as a ${this.job}`);
};

const alice = new Employee('Alice', 'Developer');
alice.greet(); // "Hello, my name is Alice"
alice.work();  // "Alice is working as a Developer"

 

이 코드에서는 Employee라는 생성자 함수가 추가로 정의되어 있고, Employee.prototype의 값으로 Object.create(Person.prototype)라는 object(객체)를 넣었다. (Object.create(a)라는 함수는 __proto__에 a라는 값을 갖는 객체를 생성하는 함수다.) 그리고 Employee.prototype에 별도로 work라는 함수를 정의하고, Employee라는 생성자 함수를 통해 alice라는 객체를 생성하였다.

이때 alice에서 greet라는 함수를 호출하면 조금 다르게 돌아간다. 먼저 alice라는 객체에 greet라는 함수가 정의되어 있는지를 찾는다. 앞선 예시와 마찬가지로 alice라는 객체 내에는 해당 함수가 존재하지 않고, javascript는 alice.__proto__ 속성으로 넘어가 greet라는 함수가 존재하는지를 찾는다. 이때 alice.__proto__는 Employee.prototype 속성이고, 여기에도 사용자가 정의한 greet라는 함수는 존재하지 않는다.

 

여기서 javascript는 이 greet라는 함수를 찾기 위해 한 번 더 __proto__ 속성으로 넘어간다. Employee.prototype 속성은 Object.create(Person.prototype)이라는 객체고, 이 객체의 __proto__ 속성은 Person.prototype이기 때문에 결국 alice.__proto__.__proto__는 Person의 prototype을 의미한다. 여기에는 사용자가 정의한 greet라는 함수가 존재하기 때문에 최종적으로 해당 greet 함수를 호출하게 된다.

(만약 여기에도 없었다면 javascript는 greet라는 함수를 찾기 위해 alice.__proto__.__proto__.__proto__인 Object.prototype으로 올라간다. 여기에도 없다면 한 번 더 __proto__ 속성을 타고 올라가지만, Object.prototype.__proto__는 null이다.)

 

Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

 

결과적으로, 위 두 줄의 코드가 Employee를 Person의 하위 생성자 함수로 설정하는 과정이라고 볼 수 있다.

 

이렇게 proto 속성을 통해 상위 prototype 속성과 연결되어 있는 구조를 prototype chain이라고 한다. 이렇게 되면 class의 상속과 마찬가지로, 하위 생성자에서 굳이 중복되는 속성이나 method를 따로 정의하지 않더라도 상속을 통해 상위 생성자 함수의 prototype에 정의된 요소들을 재사용할 수 있다.

 

 

참고 - ES6(ECMAScript 2015) 이후 버전에서의 상속

ES6 이후 버전에서의 javascript에서는 class 문법이 도입되어 다른 객체지향 언어에서 사용하는 class 문법을 사용할 수 있게 되긴 했다. 예시는 아래와 같다.

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

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

class Employee extends Person {
    constructor(name, job) {
        super(name); // 부모 클래스의 생성자 호출
        this.job = job;
    }

    work() {
        console.log(`${this.name} is working as a ${this.job}`);
    }
}

const alice = new Employee('Alice', 'Developer');
alice.greet(); // "Hello, my name is Alice"
alice.work(); // "Alice is working as a Developer"

 

이 코드는 위에서 prototype chaining의 예시로 사용한 코드와 동일한 기능을 수행한다. 물론 편의성을 위해 도입했을 뿐, 내부적으로는 prototype 기반으로 돌아간다.

 

참고자료

https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/Object_prototypes

https://developer.mozilla.org/ko/docs/Web/JavaScript/Inheritance_and_the_prototype_chain