«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

방춘덕(고양이 키우면 지을 이름)의 개발 블로그입니다.

prototype과 prototype chain 본문

JS

prototype과 prototype chain

방춘덕 2019. 12. 16. 04:06

목차

  1. prototype 이란?
  2. prototype, [[Prototype]], __proto__
    1. [[prototype]]
    2. prototype
    3. __proto__
  3. constructor property
  4. prototype chain

1). prototype 이란?


출처: http://www.mollypages.org/tutorials/js.mp

JS는 prototype 기반의 객체지향 언어로써, prototype을 사용하여 다른 객체지향 언어들(C++, Java 등)의 class처럼 객체지향적이고 확장 가능한 코드를 작성하게 만들어준다. (물론 ES6부터 JS도 class 문법을 지원하긴 하지만, 런타임에서 prototype으로 변환되어 실행된다.)

 

대표적인 예시로는 우리가 이미 코드에서 흔하게 사용 중인 Array, Object 등에서 사용하는 메서드들은 모두 prototype으로 구현되어 있다. (Ex. splice, push, pop 등)

 

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const wangchunsik = new Person('wang', 'chunsik')
console.log ('왕춘식은 뇌가 있는가?:', wangchunsik.hasBrain)

Person.prototype.hasBrain = true

const wangchunja = new Person('wang', 'chunja')
console.log('왕춘자는 뇌가 있는가?:', wangchunja.hasBrain)

console.log(Person.prototype,
            'chunsik:', wangchunsik.hasBrain,
            'chunja:', wangchunja.hasBrain)

이 코드를 chrome의 console에서 실행시켜보면 아래와 같은 결과가 나온다.

 

Person객체의 prototype에 hasBrain이라는 변수를 선언하기 전에는 undefined가 나왔지만, 이후에는 true가 나왔다.

또한 마지막으로 결과를 봤을 때는 두 변수 모두 true라는 값이 나왔다.

 

위의 코드의 결과처럼, prototype에 변수 혹은 함수를 선언하면 그 객체의 생성자로 만들어진 모든 변수는 해당 prototype의 값을 가지고 있다는 것을 알 수 있다. 바로 이러한 방법으로 다른 언어의 상속과 비슷한 효과를 내는 것이다.

2). prototype, [[prototype]], __proto__


구글에 JS prototype에 대해서 찾아보면 가장 많이 보이는 단어들이다. 각각 어떤 녀석들이고, 무엇을 하는지 알아보자.

2-1). [[prototype]]


JS의 모든 객체는 [[prototype]]이라는 internal property를 가진다. 이 값은 null 혹은 다른 prototype을 가리키는 객체가 된다.

다른 prototype에서 접근하기 위해 get은 허용되지만, set은 허용되지 않는다.

 

객체 입장에서 부모 역할을 하는 prototype 객체를 가리키며, 함수는 Function.prototype을 가리킨다.

2-2). prototype


함수가 선언될 때 생성되며, 함수 객체만 가지고 있는 프로퍼티다. 함수 객체가 생성자로 사용된다면, 이 함수를 통해 생성될 객체의 부모 역할을 하는 프로토타입 객체를 가리킨다.

 

여기서 드는 의문은 위 설명만 보면 [[prototype]]과 prototype은 별반 다를 게 없는 녀석들이다.

이 둘의 차이는 [[prototype]]은 prototype chain(아래에서 설명)이 일어날 때 prototype 객체의 프로퍼티를 확인하기 위해 사용되고 prototype은 new로 객체를 생성할 때 [[prototype]]을 만드는 데 사용되는 객체다.

 

아직 잘 이해가 안 된다면, 아래 코드가 이해를 도와줄 것이다.

console.log(( new Person ).__proto__ === Person.prototype) // true
console.log(( new Person ).prototype === undefined) // true

 

2-3). __proto__ 


__proto__는 ECMAScript의 표준은 아니지만, 대부분의 브라우저에서 구현되어, 사실상 표준인 객체다. 그 이유는 object.[[prototype]]이라는 것은 대부분의 환경에서 에러를 유발할 수 있기 때문에 따로 __proto__라는 [[prototype]]의 getter를 만든 것이다. (따라서 같은 주소의 오브젝트를 리턴하므로, 사실상 같은 객체다.)

 

바로 위에서 설명했던 것처럼 [[prototype]]의 prototype 객체에 접근할 때 사용된다. 내부적으로는 Object.getPrototypeOf()를 호출하여 prototype을 반환한다. 

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
Person.prototype.hasBrain = true

const wangchunsik = new Person('wang', 'chunsik')
console.log(Person.prototype === wangchunsik.__proto__) // true

따라서 위와 같은 코드가 true를 반환한다.

3). constructor property


prototype 객체는 constructor 프로퍼티를 갖는다. 이 프로퍼티는 객체의 입장에서 자신을 생성한 객체를 가리킨다.

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
const wangchunsik = new Person('wang', 'chunsik')

console.log(Person.prototype.constructor === Person) // true
console.log(wangchunsik.constructor === Person) // true
console.log(Person.constructor === Function) // true

따라서 위와 같은 결과를 보여준다.

 

그렇다면 아래 코드의 결과는 어떨까?

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const wangchunsik = new Person('wang', 'chunsik')
console.log(wangchunsik.constructor === Person) // ?

Person.prototype = {}

const wangchunja = new Person('wang', 'chunja')
console.log(wangchunja.constructor === Person) // ?

정답은 true 그리고 false다. 그 이유는 뭘까? 한 줄로 설명해보자면 Person.prototype = {} 이 한 줄이 prototype chain을 망가트렸기 때문이다. (이는 MDN 공식 문서에도 언급이 돼있다.)

 

정확히는 Person.prototype = {} 코드가 실행될 때, {}는 따로 constructor가 없으므로 wangchunja의 constructor는 undefined가 돼버린 것이다. 그렇다면 prototype chain을 망가뜨린 후 생성된 객체에게 어떤 일이 일어났는지 instanceof를 이용해 객체에 어떤 일이 일어났는지 확인해보자

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
Person.prototype = {}

const wangchunja = new Person('wang', 'chunja')
console.log(wangchunja instanceof Person, wangchunja instanceof Object) // true true

위 코드가 둘 다 true를 반환한 이유는, instanceof는 해당 객체의 __proto__를 반복적으로 타고 올라가 값을 확인하기 때문이다. 즉, 생성자 함수에 영향을 받지 않는다.

 

마지막으로 prototype chain을 망가뜨리기 전에 생성된 객체에게는 어떤 일이 일어났는지 확인해보자

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const wangchunsik = new Person('wang', 'chunsik')

Person.prototype = {}

console.log(wangchunsik instanceof Person, // false
            wangchunsik.constructor === Person, // true
            wangchunsik instanceof Object) // true

이제는 더 이상 wangchunsik 객체가 Person에 속하지 않는다는 것을 알게 되었다. 그 이유는 wangchunsik의 __proto__는 똑같이 Person을 가리키고 있지만, Person.prototype이 Object를 가리키고 있기 때문에, wangchunsik instanceof Person이 false가 되는 것이다.

 

따라서 ~~.prototype = {}는 정말 위험한 행동이며 prototype chain을 파괴할 수 있으므로 조심하는 것이 좋다.

4). prototype chain


prototype chain이란 객체에서 접근하려는 메서드 혹은 변수를 찾을 수 없을 때, 가리키고 있는 [[prototype]]을 재귀적으로 탐색하며 해당 식별자를 찾는 것을 말한다. 

const wangchunsik = {
  firstName: 'wang',
  lastName: 'chunsik'
}

// Object.prototype.hasOwnProperty에 선언되어 있음
console.log(wangchunsik.hasOwnProperty('firstName')) // true

// wangchunsik 객체와 Object.prototype내에 sound라는 식별자가 존재하지 않음
console.log(wangchunsik.sound) // undefined

따라서 위와 같이 wangchunsik 객체 내에, hasOwnProperty라는 메서드는 없지만, 무리 없이 실행되는 것을 알 수 있다. 반대로 sound라는 메서드는 Object.prototype, wangchunsik 객체 내에도 없으므로 undefined라는 값을 return 해주는 것을 알 수 있다.

 

객체의 __proto__를 계속해서 참조하며, 값을 찾으면 해당 값을 리턴하고 null을 만나면 undefined를 리턴한다.

한번 위의 코드의 wangchunsik.sound를 찾는 과정을 순서대로 알아보자.

  1. wangchunsik 객체에서 sound 식별자를 찾는다.
  2. wangchunsik 객체에서 __proto__ (aka [[prototype]]) 가 null인지 확인한다.
  3. wangchunsik.__proto__에서 sound라는 식별자를 찾는다. (만약 찾는다면 여기서 그 값을 리턴함)
  4. wangchunsik.__proto__.__proto__가 null인지 확인한다.
  5. undefined를 리턴하고 prototype chain을 끝낸다.

 

이 글을 작성할 때 읽은 감사한 자료들.


 

 

Inheritance in JavaScript

OOJS에 대한 온갖 잡지식을 설명했으니, 이 글에서는 부모 클래스에서 자식 클래스를 상속하는 방법을 알아봅니다. 덤으로 OOJS를 구현하는데 몇 가지 참고사항도 있습니다.

developer.mozilla.org

 

 

Object prototypes

Javascript에서는 객체를 상속하기 위하여 프로토타입이라는 방식을 사용합니다. 본 문서에서는 프로토타입 체인이 동작하는 방식을 설명하고 이미 존재하는 생성자에 메소드를 추가하기 위해 프로토타입 속성을 사용하는 법을 알아봅니다.

developer.mozilla.org

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

 

상속과 프로토타입

Java 나 C++ 같이 클래스 기반의 언어를 사용하던 프로그래머는 자바스크립트가 동적인 언어라는 점과 클래스가 없다는 것에서 혼란스러워 한다. (ES2015부터 class 키워드를 지원하기 시작했으나, 문법적인 양념일 뿐이며 자바스크립트는 여전히 프로토타입 기반의 언어다.)

developer.mozilla.org

https://poiemaweb.com/js-prototype

 

Prototype | PoiemaWeb

자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어 있다. 그리고 이것은 마치 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게 한다. 이러한 부모 객체를 Prototype(프로토타입) 객체 또는 줄여서 Prototype(프로토타입)이라 한다.

poiemaweb.com

https://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript

 

__proto__ VS. prototype in JavaScript

This figure again shows that every object has a prototype. Constructor function Foo also has its own __proto__ which is Function.prototype, and which in turn also references via its __proto__

stackoverflow.com

https://zeekat.nl/articles/constructors-considered-mildly-confusing.html

 

Constructors Considered Mildly Confusing

In a class-based object system, typically classes inherit from each other, and objects are instances of those classes. Methods and properties that are shared between instances are (at least conceptually) properties of a class. Properties (and for some lang

zeekat.nl

 

'JS' 카테고리의 다른 글

event bubbling, capturing, delegation  (0) 2019.12.27
closure  (0) 2019.12.20
scope  (1) 2019.12.13
event loop  (0) 2019.12.11
hoisting  (0) 2019.12.07