20.03.25 상속(Inheritance) & Prototype Chain

cocoball02·2020년 3월 25일
0

Prototype

Javascript는 프로토타입 기반 언어이다.

프로토타입 기반 프로그래밍이란 객체지향 프로그래밍의 한 형태의 갈래로 클래스가 없고, 클래스 기반 언어에서 상속을 사용하는 것과는 다르게, 객체를 원형(프로토타입)으로 하여 복제의 과정을 통하여 객체의 동작 방식을 다시 사용할 수 있다.
(ES6부터는 Class 개념이 추가되었다.)

Class의 역할은 블루프린트다. 한마디로 하나의 Class를 생성해놓고 필요할 때 그 Class를 사용해 객체를 생성할 수 있다. 이렇게 생성할 경우 만드는 객체마다 속성들을 지정해주지 않아도 Class의 속성들과 메소드를 사용해 더 적은 메모리로 원하는 것들을 구현할 수 있게 된다. Javascript에서는 이런 역할을 Prototype을 통해 할 수 있다.

예시)

function Person(){
}
Person.prototype.name = 'john'

let man = new Person();

console.log(man); //Person {}
  • javascript에서는 함수에 new를 붙여서 사용하면 생성자 함수로 인식한다.
    일반함수에도 동일하게 적용되는 규칙이기 때문에 생성자함수로 사용할 함수명의 시작은 대문자로 작성한다.

위의 예시처럼 우리가 함수를 정의할때 동시에 생성되는 것이 있다.
바로 생성한 함수의 부모객체 역할을 하는 Prototype 객체다.

* 함수가 가지고 있는 prototype 프로퍼티와 Prototpe 객체를 혼동하면 안된다.

prototype 프로퍼티를 통해서 Prototype 객체에 접근할 수 있다.
Prototype 객체는 constructor 프로퍼티와 proto 프로퍼티를 갖고있다.

  • .prototype : 생성자 함수의 prototype 객체를 가리킨다.
  • constructor : Prototype 객체와 연결된 함수를 가리킨다.
  • __proto__ : 자신의 부모인 Prototype 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티.(Prototype Link라고 부른다.)
function Person(){
}
Person.prototype.name = 'john'

console.log(Person.prototype);
// name: "john"
// constructor: ƒ Person()
// __proto__: Object

let man = new Person();
console.log(man.name);	// john

위의 코드를 보면 Person의 Prototype 객체에 name이라는 프로퍼티가 생긴걸 볼 수 있다. 여기서 우리가 Person 함수를 사용해 객체를 생성한다면 객체에서 또한 Prototype 객체가 가진 프로퍼티들을 사용할 수 있게 된다.
우리가 보통 사용하는 배열메소드 또한 같은 원리이다. Array.prototype 객체에 저장되어 있는 프로퍼티와 메소드들을 사용하는 것이다.

그렇다면 위의 man.name은 어떻게 된것일까?
이것이 가능한것은 바로 Prototype Link 때문이다.

위의 출력된 내용 중 __proto__ 라고 된 부분을 볼 수 있다.

모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 Prototype 객체를 prototype link로 연결해준다.

이게 뭔소리냐! 그림으로 보는것이 이해가 빠를것이다.

위의 그림처럼 함수의 prototype 프로퍼티가 가리키는 Prototype 객체와 동일한 객체를 가리킨다는 의미다.

function Person(){
}
Person.prototype.name = 'john'

console.log(Person.prototype);
// {name: "john", constructor: ƒ}

let man = new Person();
console.log(man.__proto__);	
// {name: "john", constructor: ƒ}

객체를 생성하는 건 생성자 함수의 역할이지만, 생성된 객체의 실제 부모역할을 하는 건 생성자 자신이 아닌 생성자의 prototype 프로퍼티가 가리키는 Prototype 객체이다.

Prototype Chaining

특정 객체의 프로퍼티나 메소드에 접근할 때, 해당 객체에 그 프로퍼티 혹은 메소드가 없다면 __proto__(prototype link)를 따라 자신의 부모 역할을 하는 Prototype 객체의 프로퍼티를 차례대로 검색하는 것을 말한다.

function A(){
};
A.prototype.sayHello = function(){
  console.log('hello');
}
function B(){
};
B.prototype = Object.create(A.prototype);
function C(){
};
C.prototype = Object.create(B.prototype);

let z = new C();
z.sayHello();	// hello

위의 코드의 경우 z라는 객체는 C라는 생성자 함수를 사용하여 만들었다.
분명 C 생성자 함수에는 sayHello라는 메소드가 없음에도 불구하고 z객체는 사용하고 있다. 이것이 가능한 이유가 바로 Prototype Chaining 때문이다.
z객체가 sayHello라는 메소드를 실행하면 먼저 C prototype 객체를 검사한다. 없다면 C.prototype에 연결된 부모가 되는 B prototype 객체를, 또 없다면 그 부모인 A prototype 객체까지 검사하는것이다.

상속이란?

객체의 로직을 그대로 물려받아서 또 다른 객체를 만들 수 있는 기능이다.
* 부모 객체의 로직을 수정할 수 있다.

그렇다면 어떻게 상속할 수 있을까?
첫번째 방식으로는 Object.create 메소드를 사용하여 자식 생성자의 prototype에 부모객체를 추가하는 방식이 있다.

function Person(){
	this.name = 'john';
};
function Man(){
  Person.call(this);//부모 객체에 있는 프로퍼티를 가져온다.
}
Man.prototype = Object.create(Person.prototype);
//그냥 Person.prototype을 할당할 경우 자식 객체에서 프로퍼티를 추가하거나 수정하면 부모객체에도 그대로 영향을 미친다.(같은 Prototype 객체를 참조하고 있기 때문에.)
//new 생성자를 할당해주면 제대로 된 상속이 이루어지지 않는다. 원하지 않는 프로퍼티들까지 함께 추가되는 경우도 발생한다.

Man.prototype.constructor = Man;
//constructor는 prototype 객체를 생성한 함수를 가리키기 때문에 수정해야한다.

let person = new Man();
console.log(person);//Man {name: "john"}

Object.create 메소드는 지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만든다.
이때 메소드의 파라미터로 들어가는 값은 프로토타입이 되어야 할 객체다.

두번째 방식으로는 ES6부터 추가된 Class 개념을 이용하여 상속할 수 있다.
* (두번째 방식이 훨씬 간편하지만 첫번째 방식의 원리를 잘 이해해야 두번째 방식을 사용하는데도 수월하다.)

class Person{
  constructor(name){
    this.name = name;
  }
  eat(){
    console.log('먹는중!!');
  }
}

class Man extends Person{	//extends 뒤에 부모 객체를 작성한다.
  constructor(name){
    super(name)	// 부모 객체의 프로퍼티를 가져온다.
  }
  sleep(){
    console.log('자는중!!');
  }
}

let john = new Man('john');
console.log(john);	//Man {name: "john"}

* 아직 Prototype에 대한 이해가 어렵다. 더 공부하면서 알게되는 내용들을 보충할 예정이다.

profile
부지런하게 기록하자

0개의 댓글