자바스크립트는 다른 언어들:클래스 기반
과는 다르게
프로토타입
기반 언어이다.
이 글을 작성하는데 참고한 책에서는 프로토타입을
아래의 그림만 이해하면 모두 이해 한 것이라고 설명한다.
위의 추상도는 아래의 코드를 기반으로 그린 것이라는데
var instance = new Constructor();
해당 코드를 기반으로 그림을 조금 더 구체화 시키면 아래와 같게 된다.
즉 어떤 생성자 함수를 new
연산자와 함께 호출하면
생성자 함수에서 정의된 내용을 바탕으로 새로운 instance
가 생성되는데
이때 instance
에는 __proto__
라는 프로퍼티가 자동으로 할당되며
이는 생성자 함수의 프로퍼티인 prototype
을 참조한다 라는 뜻이다.
코드로 나타내면 아래와 같다.
var instance = new Constructor();
var isTrue = instance.__proto__ === Constructor.prototype;
console.log(isTrue); // true
그럼 생성자 함수A
의 prototype
에 getName
메소드를 정의하면
A
의 인스턴스인 instance
는 prototype
을 참조하는 __proto__
를 통해서
getName
을 사용할 수 있을까?
var Person = function(name){
this.name = name;
};
Person.prototype.getName = function(){
return this.name;
};
var gildong = new Person('홍길동');
gildong.__proto__.getName(); // undefined
사용을 해본 결과 undefined
가 출력된다.
만약 getName
자체가 정의되지 않은 상태라면
undefined
가 아닌 typeError
를 반환해야 맞다.
그렇다면 getName
은 __proto__
의 프로퍼티에 존재하고
출력된 this.name
의 값이 undefined
라고 보는 것이 맞다.
순서대로 파악을 해보면 gildong.__proto__.getName
을 호출할 때의 주체는
엄밀히 따지고 보면 gildong
이 아닌 gildong.__proto__
이다
해당 객체의 프로퍼티에는 name
이 정의되어있지 않기 때문에 undefined
가 반환된다.
gildong.__proto__.name = "홍길동"
gildong.__proto__.getName(); // "홍길동"
name
을 지정 해주면 제대로 반환된다.
즉 우리가 생각한 this
와 프로토타입의 this
가 다르다는걸 알 수 있다.
그럼 우리가 생각한 this
를 바인딩하여 실행하는 방법이 뭘까?
답은 간단히도 .__proto__
를 생략하면 된다.
var gildong = new Person('홍길동');
gildong.getName(); // '홍길동'
사실 .__proto__
는 원래부터 생략이 가능하도록 정의 되어있다.
그 이유는 딱히 없다.
자바스크립트를 만들고 전체 구조를 설계한 브랜든 아이크의 머릿속에서 나온 개념이다.
책에서도 "그러니 그냥 그런가보다 하는 수 밖에 없다"고 설명한다.
이렇게 생략을 하게되면 위에서 설명한 그림이 아래와 같이 바뀐다.
즉 .__proto__
를 생락하면
원본 생성자 함수의 prototype
에 정의된 메서드나 프로퍼티에 접근할 수 있게된다.
그럼 아래의 코드를 보자
var arr = [1,2];
console.log(arr.__proto__ === Array.prototype) // true
평소처럼 배열을 만들면 자연스럽게 Array
의 instance
로써 만들어지는걸 알 수있다.
그럼 Array.prototype
의 메서드는 당연히 arr
이 본인의 것 처럼 사용할 수 있어야 맞다.
그런데 Array
의 메소드중 isArray
를 보면 사용하는 방법이 조금 다르다.
var arr = [1,2];
console.log(Array.isArray(arr)); // true
isArray
를 본인의 메서드인듯 사용할 수 있었다면
arr.isArray()
이렇게 사용해야 맞다.
뭐가 다른걸까?
이유는 isArray
는 Array
의 정적 메소드이기 때문에 prototype
의 프로퍼티가 아니다.
instance.__proto__
가 참조하는 것은 원본 생성자의 prototype
뿐이다.
prototype
의 프로퍼티보다 상위에서,
즉 Array
의 프로퍼티로 선언된 정적 메소드는 사용법이 다를 수 밖에 없다.
생성자 함수.prototype 의 프로퍼티중에는 constructor
라는 프로퍼티가 있다.
물론 prototype
을 참조하는 __proto__
에도 있다.
이 프로퍼티는 단어 그대로 원본 생성자 자신을 참조하고 있다.
그리고 그 역할은 instance
가 자신의 원본이 무엇인지 알 수있는 수단이다.
var arr = [1,2];
Array.prototype.constructor === Array // true
arr.__proto__.constructor === Array // true
arr.constructor === Array // true
var arr2 = new arr.constructor(3,4);
console.log(arr2); //[3,4]
arr2
를 보면 arr.constructor
즉 Array
를 참조하고 있다.
그리고 new
연산자로 instance
를 만든 형태이기 때문에
var arr2 = new Array(3,4);
위와 동일하다고 볼 수 있다.
var Person = function(name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
var gildong = new Person('홍길동');
gildong.getName = function(){
return '나는' + this.name;
}
console.log(gildong.getName()); // 나는홍길동
원래라면 gildong
은 Person
의 instance
이므로
gildong.getName()
은 Person.prototype
을 참조하여
Person.prototype.getName()
을 호출하지만
__proto__
프로퍼티와 같은 레벨에서 getName
을 오버라이드 했기 때문에
Person.prototype.getName()
이 아닌 gildong.getName()
을 호출한다.
때문에 this
가 instance === gildong
으로 바인딩되며
'나는' + this.name
=== '나는' + gildong.name
=== '나는' + '홍길동'
최종적으로 "나는홍길동" 이 출력된다.
사실 어떤 생성자의 prototype
안을 살펴보면 __proto__
가 한번 더 나오는걸 볼 수 있다.
그 이유는, 모든 protyotype
은 객체이기 때문이다.
사진을 보면 어떤 생성자 함수의 prototype
은 결국
Object.prototype
을 참조하는 일종의 __proto__
와 같다는걸 알 수 있다.
그렇다면 Object.prototype
에 사용자 지정 메서드를 추가하거나 오버라이드 하면
결국 최종적으로 프로토 타입 체인을 통해서 해당 메서드에 접근하여 사용할 수 있는가?
사용할 수 있다.
Array
의 instance
를 var a
에 할당하여
Array.prototype.__proto__.abc.call(a)
의 형태로 호출된것.
var Grade = function(){
var args = Array.prototype.slice.call(arguments);
for(var i = 0; i < args; i++){
this[i] = args[i];
}
this.length = args.length;
}
var g = new Grade(100,80);
이렇게 되면 g.length
또는 g[number]
와 같은
Grade.prototype
에 접근하여 실행할 수 있는 메서드나 프로퍼티는 접근이 가능하지만
Array
와는 연결 되어있지 않기 때문에
Array.prototype
의 메서드나 프로퍼티에는 접근할 수 없게된다.
그러기 위해서는 g.__proto__
즉 Grade.prototype
이
Array
의 instance
를 바라보게 하면 된다.
Grade.prototype = [];
그럼 Array.prototype
의 메서드에 접근할 수 있게 된다.
g.push(1); // [100,80,1]
g.shift(); // [80,1];
g.unshift(2); // [2,80,1];
즉 아래와 같은 형태로 프로토타입 체인이 연결된 것.
최종적으로는 아래와 같은 형태가 된다.
!(다중 프로토타입 체인2)[https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fk.kakaocdn.net%2Fdn%2FbnpsKc%2FbtqRQSKjkEP%2Fsu5x8x8eXqKMeZj74Kzci0%2Fimg.png]