방춘덕(고양이 키우면 지을 이름)의 개발 블로그입니다.
prototype과 prototype chain 본문
목차
- prototype 이란?
- prototype, [[Prototype]], __proto__
- [[prototype]]
- prototype
- __proto__
- constructor property
- prototype chain
1). prototype 이란?
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를 찾는 과정을 순서대로 알아보자.
- wangchunsik 객체에서 sound 식별자를 찾는다.
- wangchunsik 객체에서 __proto__ (aka [[prototype]]) 가 null인지 확인한다.
- wangchunsik.__proto__에서 sound라는 식별자를 찾는다. (만약 찾는다면 여기서 그 값을 리턴함)
- wangchunsik.__proto__.__proto__가 null인지 확인한다.
- undefined를 리턴하고 prototype chain을 끝낸다.
이 글을 작성할 때 읽은 감사한 자료들.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain
https://poiemaweb.com/js-prototype
https://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
https://zeekat.nl/articles/constructors-considered-mildly-confusing.html
'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 |