function Cat() {
this.lives = 9;
this.sayName = function () {
console.log(`Meow! My name is ${this.name}`);
};
}
위의 코드에서, 새롭게 생성된 'Cat' object의 'sayName' attribute에 함수를 추가함으로써, sayName() 메소드가 모든 'Cat' Object에 추가된다.
문제 없이 잘 동작하는 코드이지만, 만약 이 constructor로 더 많은 'Cat' object들을 초기화하고 싶다면? 각각의 'Cat' object의 sayName을 위한 함수를 매번 생성해야 할 것이다. 뿐만 아니라, 메소드를 수정해야 할 경우 각각의 object들을 일일이 고쳐야 한다.
이 경우, 'Cat' constructor 함수에 의해 생성된 모든 object들이 단 하나의 sayName() 메소드를 같이 공유하도록 할 수 있다면 좋을 것이다.
메모리를 아끼고 반복을 줄이기 위해서, constructor 함수의 "prototype property"에 메소드를 추가할 수 있다. 이 prototype은 하나의 object이고, constructor 함수에 의해 생성된 모든 object들은 prototype에 대한 참조를 계속 유지하며, prototype의 property들을 마치 자신의 것처럼 사용할 수 있다.
JavaScript에서는, 상속을 구현하기 위해 object와 prototype 사이의 이 '비밀 링크'를 활용한다.
각각의 함수들은 모두 'prototype' property를 갖고 있다는 점을 기억하자(prototype도 결국 하나의 object다). 함수가 new 연산자와 함께 constructor로써 호출될 때, 새로운 object를 생성하고 리턴한다. 이 object는 constructor의 prototype과 비밀스럽게 연결되어 있고, 이는 object가 prototype의 property들과 메소드들을 마치 자신의 것처럼 접근할 수 있도록 해준다.
prototype property가 object를 가리킨다는 점으로 미뤄 보아, object도 자신의 prototype을 향한 어떤 링크가 있을 것임을 알 수 있다. 그리고 그 prototype은 또 다시 자신의 prototype을 참조하고… 이런 식으로 prototype chain이 형성되는 것이다.
Prototype Chain에서 property & method 찾기
property에 접근하거나 method를 호출할 때, Javascript interpreter는 prototype chain을 일정한 순서에 따라 탐색하게 된다.
1. 첫번째로, Javascript engine은 object 자신의 property를 찾는다. 즉 object 자신에게 직접 정의된 property와 메소드는 이름이 같은 property와 메소드들이 있다 할지라도 첫번째 우선순위를 갖는다(scope chain에서의 variable shadowing과 유사).
2. 위의 경우에서 property를 찾지 못했을 경우, object의 constructor의 prototype을 찾는다.
3. prototype에 property가 없을 경우, chain을 따라 위로 올라가면서 찾기 시작한다.
4. chain의 가장 끝은 Object() object(혹은 top-level 부모)다. 여전히 property를 찾지 못했을 경우, property는 undefined다.
constructor function에 직접 메소드를 정의한 예시와, constructor의 prototype에 메소드를 정의한 예시를 각각 살펴 보자.
// (A)
function Dalmatian (name) {
this.name = name;
this.bark = function() {
console.log(`${this.name} barks!`);
};
}
// (B)
function Dalmatian (name) {
this.name = name;
}
Dalmatian.prototype.bark = function() {
console.log(`${this.name} barks!`);
};
💡 prototype
Object 대체하기 💡
함수의 prototype object를 다른 것으로 대체시킨다면, 그 함수에 의해 생성된 object에는 어떤 일이 발생할까? 아래의 예시를 보자.
function Hamster() {
this.hasFur = true;
}
let waffle = new Hamster();
let pancake = new Hamster();
먼저, Hamster의 object를 2개 만들었다(waffle, pancake). 다음으로 Hamster의 prototype에 property를 추가해보자. waffle과 pancake에서 해당 property에 접근하는 것이 가능하다.
Hamster.prototype.eat = function () {
console.log('Chomp chomp chomp!');
};
waffle.eat();
// 'Chomp chomp chomp!'
pancake.eat();
// 'Chomp chomp chomp!'
이제 Hamster의 prototype object를 완전히 다른 것으로 바꿔버린다.
Hamster.prototype = {
isHungry: false,
color: 'brown'
};
기존의 object들은 갱신된 prototype의 property들에 접근할 수 없게 되었다. 이전에 있던 prototype과의 링크만 살아있을 뿐이다:
console.log(waffle.color);
// undefined
waffle.eat();
// 'Chomp chomp chomp!'
console.log(pancake.isHungry);
// undefined
즉, 이후에 새로 생성되는 모든 Hamster object들은, 갱신된 prototype만 사용할 수 있는 것이다:
const muffin = new Hamster();
muffin.eat();
// TypeError: muffin.eat is not a function
console.log(muffin.isHungry);
// false
console.log(muffin.color);
// 'brown'
Object의 property들 확인하기
앞서 살펴봤듯이, object 자신의 property를 직접 갖고 있지 않을 경우, prototype chain을 따라 찾아가게 된다. 이 과정에서 때로는 특정 property가 어디에서 온 것인지 파악하기 까다로울 때가 있다. 이럴 때 유용한 몇 가지 메소드를 소개한다.
hasOwnProperty()
hasOwnProperty()는 특정 property가 어디에서 온 것인지 찾아준다. 찾으려는 property의 이름을 string값으로 넘겨주면, 그 property가 object 자체에 속해있는지 아닌지 boolean값을 리턴한다.
"object이름".hasOwnProperty('찾으려는 property 이름')
다음의 예시에서, 'Phone' constructor 함수 내부에 직접 선언된 하나의 property와, 'Phone'의 prototype object에 있는 또 하나의 property를 보자.
function Phone() {
this.operatingSystem = 'Android';
}
Phone.prototype.screenSize = 6;
이제 myPhone이라는 새로운 object를 생성하고, 그 object가 'operatingSystem'이라는 property를 직접 소유하고 있는지 확인해보자. (즉 prototype으로부터 온 것이 아니라는 의미일 것이다):
const myPhone = new Phone();
const own =
myPhone.hasOwnProperty('operatingSystem');
console.log(own);
// true
true가 리턴된다. 그럼 Phone object의 prototype에 있는 screenSize는 어떨까?
const inherited =
myPhone.hasOwnProperty('screenSize');
console.log(inherited);
// false
false가 리턴된다. 이렇게 hasOwnProperty()를 사용해서, 특정 property의 기원을 파악할 수 있다.
isPrototypeOf()
isPrototypeOf() 메소드는, 어떤 object가 다른 object의 prototype chain에 존재하는지 체크할 때 사용한다. 이 메소드를 사용해서, 특정 object가 다른 object의 prototype의 역할을 하는지 확인할 수 있다.
'rodent' object를 살펴보자.
const rodent = {
favoriteFood: 'cheese',
hasTail: true
};
다음 Mouse() constructor 함수를 만들고, 그것의 prototype에 rodent를 할당한다.
function Mouse() {
this.favoriteFood = 'cheese';
}
Mouse.prototype = rodent;
새로운 Mouse object를 만들었다면, 그것의 prototype은 'rodent' object여야 한다. 확인해 보자.
const ralph = new Mouse();
const result =
rodent.isPrototypeOf(ralph);
console.log(result);
// true
이렇게 isPrototypeOf()를 통해 어떤 object가 다른 object의 prototype chain에 존재하는지 여부를 확인할 수 있다.
Object.getPrototypeOf()
앞서 살펴본 isPrototypeOf()를 사용하기 위해서는, prototype object가 명확히 있어야만 한다. 만약 object의 prototype이 무엇인지 모를 때는? Object.getPrototypeOf()를 사용하자.
Object.getPrototypeOf()의 리턴값을 myPrototype이라는 변수에 저장하고 결과를 확인해 보자.
const myPrototype = Object.getPrototypeOf(ralph);
console.log(myPrototype);
// { favoriteFood: 'cheese', hasTail: true }
'ralph'의 prototype을 보여준다. Object.getPrototypeOf()는 주어진 object의 prototype를 얻어오는 데 사용하면 좋다.
The constructor
Property
object가 생성될 때마다, 'constructor'라는 특별한 property가 할당된다. object의 constructor property에 접근하면, 처음 object를 생성했던 constructor 함수에 대한 참조값이 리턴된다.
'Longboard' constructor 함수를 이용해 새로운 object를 만들고, board라는 변수에 저장해 보자.
function Longboard() {
this.material = 'bamboo';
}
const board = new Longboard();
board의 contructor property에 접근하면, 본래 constructor 함수를 볼 수 있다.
console.log(board.constructor);
// function Longboard() {
// this.material = 'bamboo';
// }
만약 object가 literal notation을 사용해 생성됐다면, 그것의 contructor는 Object() constructor 함수 안에 내장되어 있음을 유의하자.
const rodent = {
favoriteFood: 'cheese',
hasTail: true
};
console.log(rodent.constructor);
// function Object() { [native code] }
참조 링크
자바스크립트 클래스를 정의하는 3가지 방법 (3 ways to define a JavaScript class)
'Javascript' 카테고리의 다른 글
[Javascript] Global 변수, Global 함수가 좋지 않은 이유 (0) | 2018.06.26 |
---|---|
[Javascript] 기본 매개변수(Default Function Parameters) (0) | 2018.06.12 |
[Javascript] Closure, 그리고 IIFE의 활용 (2) | 2018.05.03 |
Javascript의 스코프(Scope)에 대한 이해 (0) | 2018.04.29 |
[Javascript] Callbacks: forEach, map, filter (0) | 2018.04.14 |
댓글