Language/Javascript

[Javascript] 자바스크립트 상속 - 프로토 타입 생성자, 체인

재은초 2023. 7. 19. 11:55
반응형

상속(inheritance)이란?

  • 상속이란 새로운 클래스에서 기존 클래스의 모든 속성과 메소드를 사용할 수 있는 것을 의미한다.
  • C#이나 C++과 같은 클래스 기반(class-based)의 객체 지향 언어와는 달리 자바스크립트는 프로토타입 기반(prototype-based)의 객체 지향 언어다.
  • 클래스 기반 객체지향 프로그래밍 언어는 객체 생성 이전에 클래스를 정의하고 이를 통해 객체(인스턴스)를 생성한다. 하지만 프로토타입 기반 객체지향 프로그래밍 언어는 클래스 없이도 객체를 생성할 수 있다. (ECMAScript 6에서 클래스가 추가되었다) 
  • 프로토타입 기반이기 때문에 상속의 개념이 클래스 기반의 객체 지향 언어와는 약간 다른데, 자바스크립트에서는 현재 존재하고 있는 객체를 프로토타입으로 사용하여 해당 객체를 복제하여 재사용하는 것을 상속이라고 한다.

 

프로토타입(prototype)

  • 자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 프로토타입(prototype) 객체와 연결되어 있다. 그리고 이것은 마치 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게 한다.
  • 자바스크립트의 모든 객체는 최소한 하나 이상의 다른 객체로부터 상속을 받으며, 이때 상속되는 정보를 제공하는 객체를 프로토타입이라고 한다.
  • 프로토타입 객체는 생성자 함수에 의해 생성된 각각의 객체에 공유 프로퍼티를 제공하기 위해 사용한다.
var student = {
  name: 'Lee',
  score: 90
};

// student에는 hasOwnProperty 메소드가 없지만 아래 구문은 동작한다
console.log(student.hasOwnProperty('name')); // true

console.dir(student);

// student 객체는 __proto__ 프로퍼티로 자신의 부모(프로토타입) 객체인 Object.prototype를 가리킨다
console.log(student.__proto__ === Object.prototype); // true
  • 자바스크립트의 모든 객체는 [[Prototype]]이라는 가상 프로퍼티인 내부 슬롯(internal slot)를 가진다. [[Prototype]]의 값은 null 또는 객체이며 상속을 구현하는데 사용된다. [[Prototype]] 객체의 데이터 프로퍼티는 get 액세스를 위해 상속되어 자식 객체의 프로퍼티처럼 사용할 수 있지만 set 액세스는 허용되지 않는다.
  • [[Prototype]]의 값은 Prototype(프로토타입) 객체이며 __proto__ accessor 프로퍼티로 접근할 수 있다. __proto__ 프로퍼티에 접근하면 내부적으로 Object.getPrototypeOf가 호출되어 프로토타입 객체를 반환한다.
  • 객체를 생성할 때 프로토타입은 결정되며, 결정된 프로토타입 객체는 다른 임의의 객체로 변경할 수 있다. 이것은 부모 객체인 프로토타입을 동적으로 변경할 수 있다는 것을 의미하며 이러한 특징을 활용하여 객체의 상속을 구현할 수 있다.
내부 슬롯과 내부 메소드란?
- 자바스크립트 엔진의 내부 동작을 설명하기 위해 ECMAScript에서 정의한 가상 프로퍼티(pseudo property)와 가상 메서드(pseudo method)다.
- 내부 슬롯과 메서드는 자바스크립트 엔진의 내부 로직이므로 직접적으로 접근하거나 호출할 수 없는데, [[prototype]]과 같은 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공한다. (ex : object.__proto__)
const o = {};
o.[[prototype]]  // Error
o.__proto__      // Object.prototype

  

프로토타입 객체의 생성

  • 객체 생성자 함수(object constructor function)를 작성하여 프로토타입을 생성할 수 있으며, 관례상 객체 생성자 함수 이름의 첫 문자만을 대문자로 작성한다.
  • 생성자 함수를 작성하고 new 연산자를 사용해 객체를 생성하면, 같은 프로토타입을 가지는 객체들을 생성할 수 있다.
function Dog(color, name, age) {     // 개에 관한 생성자 함수 작성
    this.color = color;              // 색에 관한 프로퍼티
    this.name = name;                // 이름에 관한 프로퍼티
    this.age = age;                  // 나이에 관한 프로퍼티
}

var myDog = new Dog("흰색", "마루", 1); // 이 객체는 Dog라는 프로토타입를 가짐

document.write(myDog.color + myDog.name + " 강아지"); // 흰색 마루 강아지

일반 객체에 프로퍼티 및 메소드 추가

function Dog(color, name, age) {
    this.color = color;
    this.name = name;
    this.age = age;
}

var myDog = new Dog("흰색", "마루", 1);

myDog.family = "시베리안 허스키";     // 품종에 관한 프로퍼티 추가

myDog.breed = function() {        // 털색 포함한 품종 반환해주는 메소드 추가
    return this.color + " " + this.family;
}

document.write(myDog.breed() + "입니다"); // 흰색 시베리안 허스키입니다

프로토타입 객체에 프로퍼티 및 메소드 추가

  • 프로토타입의 경우에는 생성자 함수에 직접 추가해야만 이후에 생성되는 모든 다른 객체에도 적용할 수 있다.
function Dog(color, name, age) {
    this.color = color;
    this.name = name;
    this.age = age;
    this.family = "시베리안 허스키";  // 프로토타입에 프로퍼티 추가할 때 기본값 지정

    this.breed = function() {
        return this.color + " " + this.family;
    };
}

var myDog = new Dog("흰색", "마루", 1);
var hisDog = new Dog("갈색", "콩이", 3);

document.write("우리 집 강아지는 " + myDog.family + "이고, 친구네 집 강아지도 " + hisDog.family + "입니다.");
// 우리 집 강아지는 시베리안 허스키이고, 친구네 집 강아지도 시베리안 허스키입니다.

 

프로토타입 프로퍼티

prototype 프로퍼티

  • 프로토타입 프로퍼티를 이용하면 현재 존재하고 있는 프로토타입에 새로운 속성이나 메소드를 손쉽게 추가할 수 있다.
  • 직접 생성한 프로토타입은 새로운 속성이나 메소드를 마음껏 추가하거나 삭제할 수 있으며, 자바스크립트 표준 객체의 프로토타입도 임의로 수정할 수 있으나 오류 발생가능성이 커서 수정해서는 안된다.
function Dog(color, name, age) {
    this.color = color;
    this.name = name;
    this.age = age;
}

Dog.prototype.family = "시베리안 허스키"; // Dog 프로토타입에 family 프로퍼티 추가

Dog.prototype.breed = function() {    // Dog 프로토타입에 breed 메소드 추가
    return this.color + " " + this.family;
};

var myDog = new Dog("흰색", "마루", 1);
var hisDog = new Dog("갈색", "콩이", 3);
 
document.write("우리 집 강아지는 " + myDog.family + "이고, 친구네 집 강아지도 " + hisDog.family + "입니다");
// 우리 집 강아지는 시베리안 허스키이고, 친구네 집 강아지도 시베리안 허스키입니다

document.write("우리 집 강아지의 품종은 " + myDog.breed() + "입니다");
// 우리 집 강아지의 품종은 흰색 시베리안 허스키입니다

document.write("친구네 집 강아지의 품종은 " + hisDog.breed() + "입니다");
// 친구네 집 강아지의 품종은 갈색 시베리안 허스키입니다
  • 모든 객체는 자신의 프로토타입 객체를 가리키는 [[Prototype]] 내부 슬롯(internal slot)을 갖으며 상속을 위해 사용된다. 함수도 객체이므로 [[Prototype]] 내부 슬롯을 갖는데 함수 객체는 일반 객체와는 달리 prototype 프로퍼티도 소유하게 된다.
  • 주의해야 할 것은 prototype 프로퍼티는 프로토타입 객체를 가리키는 [[Prototype]] 내부 슬롯과는 다르다. prototype 프로퍼티와 [[Prototype]]은 모두 프로토타입 객체를 가리키지만, [[Prototype]]는 함수를 포함한 모든 객체가 가지고 있는 내부 슬롯이고 반면에 prototype 프로퍼티는 함수 객체만 가지고 있는 프로퍼티이다.
  • [[Prototype]]은 객체의 입장에서 자신의 부모 역할을 하는 프로토타입 객체를 가리키며 함수 객체의 경우 Function.prototype를 가리키고 prototype 프로퍼티는 함수 객체가 생성자로 사용될 때 이 함수를 통해 생성될 객체의 부모 역할을 하는 객체(프로토타입 객체)를 가리킨다.
function Person(name) {
  this.name = name;
}

var foo = new Person('Lee');

console.dir(Person); // prototype 프로퍼티가 있다.
console.dir(foo);    // prototype 프로퍼티가 없다.

constructor 프로퍼티

  • 프로토타입 객체는 constructor 프로퍼티를 갖는데, 이는 객체의 입장에서 자신을 생성한 객체를 가리킨다.
  • 예를 들어 아래 Person() 생성자 함수에 의해 생성된 객체를 foo 일 때, foo 객체 입장에서 자신을 생성한 객체는 Person() 생성자 함수이며, foo 객체의 프로토타입 객체는 Person.prototype이다. 따라서 프로토타입 객체 Person.prototype의 constructor 프로퍼티는 Person() 생성자 함수를 가리킨다.
function Person(name) {
  this.name = name;
}

var foo = new Person('Lee');

// Person() 생성자 함수에 의해 생성된 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(Person.prototype.constructor === Person);

// foo 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(foo.constructor === Person);

// Person() 생성자 함수를 생성한 객체는 Function() 생성자 함수이다.
console.log(Person.constructor === Function);

 

프로토타입 체인(prototype chain)

  • 프로토타입이 상속되는 가상의 연결 고리를 프로토타입 체인이라고 한다.
  • 자바스크립트에서는 객체 초기화를 사용해 생성된 같은 타입의 객체들은 모두 같은 프로토타입을 가지며, new 연산자를 사용해 생성한 객체는 생성자의 프로토타입을 자신의 프로토타입으로 상속받는다.
  • Object.prototype 객체는 프로토타입 체인에서도 가장 상위에 존재하는 프로토타입으로, 어떠한 프로토타입도 가지지 않으며 아무런 속성도 상속받지 않는다. 자바스크립트의 모든 객체는 Object.prototype 객체를 프로토타입으로 상속받는다.
var obj = new Object();    // 이 객체의 프로토타입은 Object.prototype
var arr = new Array();     // 이 객체의 프로토타입은 Array.prototype
var date = new Date();     // 이 객체의 프로토타입은 Date.prototype

var date = new Date();     // 이 객체는 Date.prototype뿐만 아니라
                              Object.prototype도 프로토타입으로 가진다
  • 자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티 또는 메소드가 없다면 [[Prototype]]이 가리키는 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례대로 검색하는데 이를 프로토타입 체인이라 한다.
var student = {
  name: 'Lee',
  score: 90
}
console.dir(student);
console.log(student.hasOwnProperty('name')); // true
console.log(student.__proto__ === Object.prototype); // true
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); // true
  • student 객체는 hasOwnProperty 메소드를 가지고 있지 않으므로 에러가 발생하여야 하나 정상적으로 결과가 출력되었다. 이는 student 객체의 [[Prototype]]이 가리키는 링크를 따라가서 student 객체의 부모 역할을 하는 프로토타입 객체(Object.prototype)의 메소드 hasOwnProperty를 호출하였기 때문에 가능한 것이다.

 

 
반응형