2. 프로토타입 체인
1) 메서드 오버라이드
- __proto__는 생략 가능한 프로퍼티이므로 __proto__를 생략하면 인스턴스는 생성자 함수의 prototype에 정의된 프로퍼티나 메서드를 자기 것처럼 사용 가능
- 이때, 인스턴스가 통일한 이름의 프로퍼티나 메서드를 가진다면?
// 생성자 함수 Person과 Person의 prototype으로 getName을 생성
var Person = function(name) {
this.name = name;
};
Person.prototype.getName = function() {
return this.name;
};
// 생성자 함수 Person에 의해 만들어진 인스턴스인 iu와 iu의 메서드로 getName 생성
var iu = new Person('지금');
iu.getName = function() {
return '바로' + this.name;
};
// iu.getName을 했을 때
// iu 객체의 생성자인 Person의 prototype을 참조하는 iu.__proto__.getName과 iu의 자체 메서드인 iu.getName중
// iu의 자체 메서드인 iu.getName이 출력됨
console.log(iu.getName()); // 바로 지금
→ 자바스크립트 엔진에서 getName이라는 메서드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 먼저 탐색
→ 자신의 프로퍼티 중 getName이 없으면 그 다음으로 가까운 대상인 __proto__를 검색하는 순서로 진행
→ iu객체에는 자신의 프로퍼티 중 getName이라는 메서드가 있으므로 바로 출력, 이 현상이 메서드 오버라이드
→ 메서드 오버라이드는 iu의 자체 메서드가 있을 때, __proto__의 메서드가 없어지고 교체되는 것이 아닌 덮어씌워지는 것
→ 따라서, __proto__의 getName 메서드도 호출할 수 있음
// __proto__를 명시해주기
// iu.__proto__가 this 객체가 되었는데, iu.__proto__에 name 객체가 없어 undefined 되었지만
// getName이 iu의 자체 메서드가 아닌 iu.__proto__의 메서드로 인식되었음을 확인할 수 있음
console.log(iu.__proto__.getName()) // undefined
// Person.prototype에 name 프로퍼티를 지정해주면
// iu.__proto__가 이를 참조하여 iu.__proto__에도 같은 값을 갖는 name프로퍼티가 생성됨
// 따라서, 이번에는 getName의 this 객체가 iu.__proto__가 되고 this.name은 iu.__proto__의 name 프로퍼티로 잘 인식됨
Person.prototype.name = '이지금';
console.log(iu.__proto__.getName()); // 이지금
// call 메서드를 사용하면 this가 바라보는 것을 Person의 prototype에서 iu 인스턴스 자체로 바꿔줄 수 있음
// iu 인스턴스를 생성할 때, 생성자 함수 Person에 '지금'을 넣으며 생성했고 따라서 iu 인스턴스의 자체 name은 '지금'
console.log(iu.__proto__.getName.call(iu)); // 지금
2) 프로토타입 체인
- 배열 [1, 2]의 디렉터리 구조를 보면 [[prototype]] 내에 또 하나의 [[prototype]]이 존재
- prototype 객체가 '객체'이기 때문에 '객체(Object)'의 prototype을 참조하기 때문
- 따라서, 배열 [1, 2]는 배열(Array)의 prototype내의 메서드도 사용 가능하고, 객체(Object)의 prototype 내의 메서드도 사용 가능
- 이처럼 어떤 데이터의 __proto__ 프로퍼티 내부에 다시 __proto__프로퍼티가 연쇄적으로 이어진 것이 프로토타입 체인
- 프로토타입 체인을 따라가며 검색하는 것이 프로토타입 체이닝
- 프로토타입 체이닝은 메서드 오버라이드와 비슷한 과정으로 검색함
→ 메서드를 호출했을 때, 자신의 __proto__에 해당 메서드가 없다면 그 위의 __proto__에서 메서드 검색
var arr = [1, 2];
// 1)
// arr을 Array의 prototype 내의 메서드 toString으로 불러왔을 때
Array.prototype.toString.call(arr); // 1 2
// arr을 Object의 prototype 내의 메서드 toString으로 불러왔을 때
Object.prototype.toString.call(arr); // [object Array]
// 2)
// arr 자체 메서드가 없을 때 toString 검색과정
// arr 자체 메서드에서 toString을 검색 → 없음
// arr 자체 메서드 위에 있는 Array의 prototype 내에서 toString 메서드 검색 → 있음 → 사용
arr.toString(); // 1 2
// arr 자체 메서드로서 toString 생성
arr.toString = function () {
return this.join('_');
};
// arr 자체 메서드가 있을 때 toString 검색과정
// arr 자체 메서드에서 toString을 검색 → 있음 → 사용
arr.toString(); // 1_2
- 위의 경우처럼 Array 뿐아니라, Number, String, Boolean 등 자바스크립트 데이터는 모두 그 위에 Object의 prototype을 참조함
3) 객체 전용 메서드의 예외사항
- 객체(Object) 내부에 메서드를 정의하면 다른 모든 자바스크립트 데이터들은 이 메서드를 사용할 수 있음
→ 따라서 객체에서만 사용할 메서드는 객체(Object)의 prototype 내에 정의할 수 없음
Object.prototype.getEntries = function() {
var res = [];
for (var prop in this) {
if (this.hasOwnProperty(prop)) {
res.push([prop, this[prop]]);
}
}
return res;
};
var data = [
['object', {a: 1, b: 2, c: 3}], // [["a", 1], ["b", 2], ["c", 3]]
['number', 345], // []
['string', 'abc'], // [["0", "a"], ["1", "b"], ["2", "c"]]
['boolean', false], // []
['func', function() {}], // []
['array', [1, 2, 3]] // [["0", 1], ["1", 2], ["2", 3]]
];
data.forEach(function (datum) {
console.log(datum[1].getEntries());
});
→ 객체(Object)의 메서드 getEntries는 hasOwnProperty를 사용하여 객체가 특정 프로퍼티(prop)를 자신만 소유하는 지 확인
→ hasOwnProperty를 this에 대해 실행하여 객체인지 아닌지 판단하고, 객체인 것에 대해서만 출력하려고 함
→ 모든 데이터가 객체를 참조하여, getEntries를 자신의 것처럼 사용하여, getentries의 this가 Object로 고정되지 않고, 매 데이터마다 해당 데이터로 바뀜
→ 따라서, 객체에서만 동작하는 객체 전용 메서드는 Object.prototype이 아닌, 스태틱 메서드로 부여해야함
→ 또한, Object와 인스턴스 사이의 this 연결이 안되므로 this를 그냥 사용하면 메서드명 앞의 객체가 this가 되는 것 대신에 this 사용을 포기하고 인스턴스 자체를 인자로 사용해야함
→ ex) Object.frees(instance) 대신, instance.freeze() / Object.getPrototypeOf(instance) 대신, instance.getPrototype()
- 프로토타입의 가장 위에는 항상 Object.prototype이 있어 참조할 수 있지만, 예외적으로 Object.prototype에 접근할 수 없도록 할 수 있음
var _proto = Object.create(null)
_proto.getValue = function(key) {
return this[key];
};
var obj = Object.create(_proto);
obj.a = 1;
console.log(obj.getValue('a'));
console.dir(obj);
→ Object.create(null)은 __proto__가 없는 객체를 생성하여, Object.prototype을 참조하지 않음
→ 따라서, 디렉터리 구조를 확인한 결과, Object.create(null)로 생성된 _proto내부에는, 직접 생성한 getValue 메서드 이외에 다른 메서드는 없음(Object.prototype을 참조하지 않아서 Object의 메서드를 사용할 수 없음)
→ 기본 기능에 제약이 생기지만, 객체의 무게가 가벼워짐
4) 다중 프로토타입 체인
- 프로토타입 체인은 보통 1단계(객체) - 2단계(나머지)로 끝나지만, __proto__를 연결해나가기만 하면 무한대로 체인관계를 이어갈 수 있음
- 이를 이용해 다른 언어의 클래스롸 비슷하게 동작하는 구조 생성 가능
- __proto__를 연결하는 방법은 __proto__가 가리키는 대상(생성자 함수의 prototype)이 연결하고자하는 상위 생성자 함수의 인스턴스를 바라보게끔 하면 됨
// Grade는 여러 인자를 받아서, 순서대로 인덱싱하여 저장하고, length 프로퍼티도 부여
// 배열의 형태로 저장하지만 배열(Array)의 메서드를 사용하지는 못하는 유사 배열 객체
var Grade = function () {
var args = Array.prototype.slice.call(arguments);
for (var i = 0; i < args.length; i++) {
this[i] = args[i];
}
this.length = args.length;
};
var g = new Grade(100, 80);
→ g는 Grade의 인스턴스
→ Grade는 배열의 형태로 저장하지만 배열(Array)의 메서드를 사용할 수는 없는 상태
→ Grade가 배열의 메서드를 사용할 수 있도록, Grade.prototype이 Array를 참조할 수 있도록 해야함
Grade.prototype = [];
→ 위의 코드를 작성하면 Grade.prototype이 (대괄호로 감싸진)배열의 형태를 갖게되며, Array의 프로토타입 삼각형에 연결됨
→ 이에 따라서, Grade로 생성된 g 또한 Array의 메서드를 사용할 수 있게 됨
console.log(g); // Grade(2) [100, 80]
g.pop(); // 배열의 끝 값을 없애며 다른 데 저장하는 배열 메서드 pop() 적용 가능
console.log(g); // Grade(1) [100]
g.push(90); // 배열의 새로운 값을 저장하는 배열 메서드 push() 적용 가능
console.log(g); // Grade(2) [100, 90]
'Front-end > Javascript' 카테고리의 다른 글
[코어 자바스크립트] 07. 클래스(2) (0) | 2023.01.02 |
---|---|
[코어 자바스크립트] 07. 클래스(1) (0) | 2022.12.29 |
[코어 자바스크립트] 06. 프로토타입(1) (0) | 2022.12.26 |
[코어 자바스크립트] 05. 클로저(3) (0) | 2022.12.21 |
[코어 자바스크립트] 05. 클로저(2) (0) | 2022.12.21 |