[FN-Interview] 프로토타입, (1)상속

GDana
gdana
Published in
10 min readOct 27, 2019

이 시리즈에서는 front-end-interview-handbook 의 질문들에 답하는 형식으로 채워나갈 예정입니다. 그리고 정확하지 않은 정보나 이건..!;…!!!!! 싶은 부분이 있다면 언제든 댓글 혹은 연락주세요 🙏

완료된 인터뷰

📍 프로토타입 상속이 어떻게 작동하는지 설명하세요

자바스크립트의 프로토타입 상속이란, 상속을 받는 객체를 생성할 수 있게끔 한 부모 객체의 프로퍼티를 사용할 수 있음을 뜻한다. 이것을 프로토타입이 연결되었다는 뜻의 체인(chain)이라 부르는데 체인의 체인을 타고 찾아가는 것을 프로토타입 체이닝이라고 부른다. 그대로 풀이해보면 원형의 ‘연쇄적 처리’라고 해석할 수 있다.

이런 프로토타입의 성질을 이용해서 프로토타입을 이용한 상속apply를 이용한 상속 등을 할 수 있다.

프로토타입이란?

Java, C++는 같은 프로퍼티를 갖는 객체를 여러개 생성할 수 있는 ‘클래스’를 제공한다. 하지만 자바스크립트에서는 클래스가 없기 때문에 객체지향을 구현하기 위한 근간인 프로토타입과 프로토타입 체이닝을 제공한다.

프로토타입 객체는 생성된 객체의 원형(prototype) 즉 부모 객체가 되는 것을 프로토타입 객체라 부르고, 프로토타입 체이닝은 프로토타입이 연결되었다는 뜻의 체인(chain)이 연쇄적으로 일어나는 것을 프로토타입 체이닝이라 부른다.

체이닝을 통해 확인할 수 있는 프로퍼티가 있는데, 객체를 생성한 부모 객체가 프로토타입이였다면 부모 객체의 프로토타입이 가리키는 프로토타입 객체를 실질적인 부모 역할을 하는 객체로 설정하는 것을 [[Prototype]] 링크 또는 __proto__ 프로퍼티 라고 한다.

객체리터럴의 프로토타입 체이닝

let obj = {
name: ‘joker’,
sayName: function(){
console.log(`My Name is ${this.name}`)
}
}
console.log(obj.sayName()) //My Name is joker
console.log(obj.hasOwnProperty(‘name’)) //true

obj에는 hasOwnProperty()가 없음에도 사용할 수 있는 것은 프로토타입 체이닝이 발생했기 때문인데 아래 그림을 보자.

호출한 obj에는 각 name, sayName() 프로퍼티가 있다.

마지막 프로퍼티로 __proto__가 있는 것을 확인할 수 있는데 obj의 실질적인 부모 객체를 가르키는 프로퍼티로 Object가 설정되어 있다. 그리고 이렇게 __proto__를 따라 올라가다 보면 프로토타입의 종점인 Object에 닿을 수 있다(생성자 함수의 프로토타입 체이닝의 joker설명 참조)

때문에 객체리터럴로 프로토타입 체이닝이 가능했던 이유는 객체리터럴로 객체를 생성하면 자바스크립트 내부에서 객체를 생성하는 Object()생성자 함수가 실행된다. 그러면 객체리터럴로 생성된 객체는 생성자 함수 Object()의 인스턴스가 되고 프로토타입 체이닝으로 hasOwnProperty()를 사용할 수 있게된다.

생성자 함수의 프로토타입 체이닝

function Who(name){
this.name = name
}
let joker = new Who(‘joker’)console.log(Who.prototype)
console.log(joker)

로그 출력값은 다음과 같이 확인할 수 있다.

객체리터럴 때에는 자바스크립트 엔진에 의해 생성자 함수가 실행되었지만 개발자가 직접 생성자 함수를 생성할 경우에는 생성자 함수 Who가 생성될 때 두가지 일이 실행된다.

  1. 생성자 함수를 참조하는 Constructor 자격을 가진 Who.prototyp객체생성
  2. 생성자 함수에 Constructor를 연결하는 prototype프로퍼티 생성

생성자 함수의 인스턴스인 joker

객체를 생성한 부모 객체와 같은 프로퍼티와 인자로 넘긴 값이 출력되고, __proto__는 부모 객체의 prototype이 되는 Object를 가리킨다.

생성자 함수를 제외한 모든 객체에는 자신의 실질적은 부모 역할을 하는 __proto__가 있는데 부모의 부모를 따라 올라가면 모든 객체의 종점인 Object가 있다.

let a = []console.log(a.__proto__) // constructor: ƒ Array()
console.log(a.__proto__.__proto__) // constructor: ƒ Object()

생성자 함수 Who()

생성자 함수를 생성하면 생성자 함수의 프로토타입을 가리키는 프로퍼티가 생성된다.

프로토타입 객체 Who.prototype

프로토타입을 동적으로 변경하는 것이 아니라면 생성자 함수 이름을 딴 Who.prototype 빈 객체가 생성된다.

기본적으로 생성자 함수를 이용해 객체를 생성하면 생성자 함수와 같은 프로퍼티를 갖은 객체가 생성되지만 프로토타입 객체에 있는 프로퍼티까지 함께 갖게 되는 것은 아니다.

Who.prototype에 있는 프로퍼티는 __proto__로 인한 프로토타입 체이닝으로 사용할 수 있는 부모 객체의 프로퍼티가 된다.

때문에 joker의 __proto__는 Object가 된다.

프로토타입 상속

프로토타입에 대해 간략이 알아봤다면 자바스크립트의 객체지향을 구현할 수 있는 프로토타입 상속에 대하여 알아보야할 시간이다!

자바스크립트에는 클래스가 없기 때문에 프로토타입의 특징을 이용하여 구현해야 하는데 그 방법을 알아보자.

프로토타입을 이용한 상속

아래 코드는 더글라스 크락포드가 자바스크립트 객체를 상속하는 방법으로 소개한 코드로 동일한 기능을 하는 함수 Object.create()가 ECMAScript5에서 지원되고 있다.

function createObj(obj){ // 1
function F(){} // 2
F.prototype = obj // 3
return new F() // 4
}
  1. 객체 `obj`를 인자로 받는 createObj 함수 생성
  2. 생성자 함수 `F()` 생성
  3. 생성자 함수가 참조할 프로토타입 값을 `obj`로 설정
  4. 프로토타입을 `obj`로 갖는 함수 반환
let info = { // 1
title : ‘말레피센트2’,
star : 9.03,
script : ‘고담시의 광대 아서 플렉은 코미디언을 꿈꾸는 남자. 하지만 모두가…’,
movieDay : function(){
return `${this.title} (Joker, 2019)`
}
};
console.log(info.title) // 2 // ‘말레피센트2’let movie = createObj(info) // 3
movie.title = ‘조커’ // 4
console.log(movie.movieDay()) // 5 // ‘조커 (Joker, 2019)’
  1. 상속 할 객체 생성
  2. 수정하기 전의 title
  3. info를 참조하는 movie 변수 생성
  4. 프로토타입 체이닝에 의해 접근할 수 있게된 title의 값 변경
  5. 변경된 title 값을 반영한 movieDay()호출

Object.assign()을 이용한 상속

프로토타입을 이용한 상속과 비슷한 상속 방법으로 Object.assign()으로 객체의 속성을 복사하여 상속한다.

let info = { // 1
title : ‘말레피센트2’,
star : 9.03,
script : ‘고담시의 광대 아서 플렉은 코미디언을 꿈꾸는 남자. 하지만 모두가…’,
movieDay : function(){
return `${this.title} (Joker, 2019)`
}
};
console.log(info.title) // 2 // ‘말레피센트2’let movie = Object.assign({}, info, {title : ‘조커’}) // 3
console.log(movie.movieDay()) // 4 // ‘조커 (Joker, 2019)’
  1. 상속 할 객체 생성
  2. 수정하기 전의 title
  3. Object.assing()을 사용하여 info를 복사하는 movie 변수 생성
  4. 변경된 title 값을 반영한 movieDay()호출

movie 객체를 확장하려면 jQuery의 extend()를 이용하는 것도 좋은 방법이다.

apply를 이용한 상속

function Info(arg){
this.title = arg
}
Info.prototype.movieDay = function(){
return `${this.title} 보러 왔다!`
}
function Movie(arg){}let guest01 = new Info(‘말레피센트2’)
Movie.prototype = guest01
let guest02 = new Movie(‘조커’)
console.log(guest02.movieDay())

콘솔에 찍히는 값을 확인해보면 ‘말레피센트2 보러 왔다!’가 실행된다. 이것은 Movie.prototype의 참조값을 설정할 때 Info가 제대로 호출되지 않았기 때문인데 이로인해 guest02객체의 초기화가 이루어지지 않았다.

let guest02 = new Movie(‘조커’)
console.log(guest02.movieDay()) // ‘말레피센트2 보러 왔다!’
guest.title = ‘조커’
console.log(guest02.movieDay()) // ‘조커 보러 왔다!’

이를 해결하려면 Movie 함수에 다음 코드를 추가해야 한다.

function Info(arg){
this.title = arg
}
Info.prototype.movieDay = function(){
return `${this.title} 보러 왔다!`
}
function Movie(arg){
Info.apply(this, arguments)
}
let guest01 = new Info(‘말레피센트2’)
Movie.prototype = guest01
let guest02 = new Movie(‘조커’)
console.log(guest02.movieDay())

thisthis로 바인딩된 새로 생성한 객체가 리턴되는 특징을 가지고 있다. 때문에 추가된 Info.apply(this, arguments)는 Movie로 생성할 객체에 arg가 들어온다면 생성자 Info를 호출하여 arg값을 전달한 객체 guest02를 생성하게 된다.

그리고 apply()를 call()로 변경하게 될 시 arguments를 Movie의 인자로 명시된 arg와 같은 값으로 지정해줘야 한다.

function Movie(arg){
Info.call(this, arg)
}

마지막으로 ‘프로토타입을 이용한 상속’을 응용해서 ‘apply()를 이용한 상속’을 보완하는 코드를 소개하고 싶었지만 아직 클래스에 대한 이해가 부족해서 다음번을 기약해본다 🙏

그리고 이번 글은 어려운 소재라고 생각이 들어서 그런지 처음부터 너무 힘이 들어갔었고 써내려가기도 버거웠다. 시간이 지나 읽어보게 되었을 때 부족한 점들을 보완할 수 있는 읽기 가벼운 글을 쓰고 싶다.

--

--