코어 자바스크립트 - 06. 프로토타입

Kay·2021년 5월 12일
0

학습목표: 프로토타입 개념을 제대로 이해할 수 있다.

01. 프로토타입의 개념 이해

constructor, prototype, instance

var instance = new Constructor()

  • 어떤 생성자 함수(Constructor)를 new 연산자와 함께 호출.
  • Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스(instance)가 생성.
  • 이때 instance에는 __proto__ 라는 프로퍼티가 자동으로 부여.
  • 이 프로퍼티는 Constructor의 prototype이라는 프로퍼티를 참조.

✔️ prototype이라는 프로퍼티와__proto__ 라는 프로퍼티의 관계가 프로토타입 개념의 핵심.

  • prototype은 객체. 이를 참조하는 __proto__ 역시 객체.
  • prototype 객체 내부에는 인스턴스가 사용할 메서드를 저장.
  • 인스턴스에서도 숨겨진 프로퍼티인 __proto__를 통해 이 메서드들에 접근할 수 있게 됨.

❗️ES5.1 명세에는 __proto__가 아니라 [[prototype]] 이라는 명칭으로 정의되어 있으며, instance.__prototype__과 같은 방식으로 직접 접근하는 것을 허용하지 않았음.
실무에서는 가급적 Object.getPrototypeOf()/ Object.create() 등을 이용할 것을 권장.

Person.prototype

var Person = function (name) {
	this._name = name;
};
Person.prototype.getName = function() {
	return this._name; 
};

Person의 인스턴스는 __proto__ 프로퍼티를 통해 getName을 호출할 수 있다.

var suzi = new Person('Suzi');
suzi.__prototype__.getName();  // undefined -> 에러가 발생하지 않았다는 점에 주목!

Person.prototype === suzi.__proto__  // ture

this에 바인딩된 대상이 잘못 지정됨.
getName 함수 내부에서 this가 suzi가 아닌 suzi.__proto__라는 객체.
이 객체 내부에는 name 프로퍼티가 없음.
찾고자 하는 식별자가 정의되어 있지 않으므로 Error대신 undefined를 반환.

만약 __proto__ 객체에 name 프로퍼티가 있다면?

var suzi = new Person('Suzi');
suzi.__proto__.name = 'SUZI__proto__';
suzi.__proto__.getName();  // SUZI__proto__

this를 인스턴스로!
= __proto__ 없이 인스턴스에서 곧바로 메서드 사용

var suzi = new Person('Suzi', 28);
suzi.getName();  // Suzi
var iu = new Person('Jieun', 28);
iu.getName();  // Jieun

__proto__가 생략 가능한 프로퍼티이기 때문!

suzi.__proto__.getName
-> suzi(.__proto__).getName
-> suzi.getName

new 연산자로 Constructor를 호출하면 instance가 만들어지는데 이 instance의 생략 가능한 프로퍼티인 __proto__는 Constructoor의 prototype을 참조한다.

__proto__ 프로퍼티는 생략이 가능하도록 구현되어 있다.
때문에, 생성자 함수의 prototype에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 해당 메서드나 프로퍼티에 접근할 수 있게 된다.

constructor 프로퍼티

생성자 함수의 프로퍼티인 prototype 객체 내부에는 constructor라는 원래의 생성자 함수(자기 자신)을 참조하는 프로퍼티가 있다.
인스턴스와의 관례에 있어 필요한 정보로, 인스턴스로부터 그 원형이 무엇인지를 알 수 있는 수단.

constructor 프로퍼티

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);  // 인스턴스에서 직접 constructor에 접근 가능 
console.log(arr2);  // [3,4]

constructor 변경

var NewConstructor = function () {
	console.log('this is new constructor!');
};
var dataType = [
	1,
  	'test',
  	true,
  	{},
  	[],
  	function () {},
  	/test/,
  	new Number(),
  	new String(),
  	new Boolean,
  	new Object,
  	new Array,
  	new Function(),
  	new RegExp(),
  	new Date(),
  	new Error()
];

dataTypes.forEach(function (d) {
	d.constructor = NewConstructor;
  	console.log(d.constructor.name, '&', d instanceof NewConstructor);
});
}

모든 데이터가 d instanceof NewConstructor 명령에 대해 false를 반환.
constructor를 변경하더라도 참조하는 대상이 변경될 뿐 이미 만들어진 인스턴스의 원형이 바뀐다거나 데이터 타입이 변하는 것은 아님.
어떤 인스턴스의 생성자 정보를 알아내기 위해 constructor 프로퍼티에 의전하는 것이 항상 안전하지는 않음.

02. 프로토타입 체인

메서드 오버라이드

var Person = function(name) {
	this.name = name;
};
Person.prototype.getName = function () {
	return this.name;
};

var iu = new Person('지금');
iu.getName = function () {
	return '바로' + this.name'
};
console.log(iu.getName());  // 바로 지금

✔️ 메서드 오버라이드: 메서드 위에 메서드를 덮어씌웠다.
원본이 그대로 있는 상태에서 다른 대상을 그 위에 얹는 이미지

자바스크립트 엔진이 getName이라는 메서드를 찾는 방식은 가장 가까운 대상인 자신의 프로퍼티를 검색 -> 없으면 그 다음으로 가까운 대상인 __proto__를 검색 하는 순서로 진행

console.log(iu.__proto__.getName());  // undefined

Person.prototype.name = '이지금';
console.log(iu.__proto__.getName);  // 이지금

console.log(iu.__proto__.getName.call(iu));  // 지금

일반적으로 메서드가 오버라이드 된 경우, 자신으로부터 가장 가까운 메서드에만 접근할 수 있지만 그 다음으로 가까운 __proto__의 메서드로도 우회적인 방법을 통해서 접근 할 수도 있음.

프로토타입 체인

어떤 데이터의 __proto__프로퍼티 내부에 다시 __proto__ 프로퍼티가 연쇄적으로 이어진 것을 프로토타입 체인이라 하고, 이 체인을 따라가며 검색하는 것을 프로토타입 체이닝이라 한다.

객체 전용 메서드의 예외사항

어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재.
따라서 객체에서만 사용할 메서드는 다른 여느 데이터 타입처럼 프로토타입 객체 안에 정의할 수가 없음.
객체에서만 사용할 메서드를 Object.prototype 내부에 정의한다면 다른 데이터 타입도 해당 메서드를 사용할 수 있기 때문!

다중 프로토타입 체인

두 단계 이상의 체인을 지니는 다중 프로토타입 체인도 가능하며, 이로부터 다른 언어의 클래스와 비슷하게 동작하는 구조를 만들 수 있다.

var Grade = function () {
 	var args = Array.prototype.slice.call(arguments);
    for (var i = 0'; i < args.length; t++) {
    	this[i] = args[i];
    }
    this.length = args.length;
};
var g = new Grade(100, 80);

Grade.prototype = [];

03. 정리

어떤 생성자 함수를 new 연산자와 함께 호출하면 Constructor에서 정의된 내용을 바탕으로 새로운 인스턴스가 생성된다.
이 인스턴스에는 __proto__ 라는, Constructor의 prototype 프로퍼티를 참조하는 프로퍼티가 자동으로 부여된다.
__proto__는 생략 가능한 속성이어서, 인스턴스는 Constructor.prototype의 메서드를 마치 자신의 메서드인 것처럼 호출할 수 있다.

Constructor.prototype에는 constructor라는 프로퍼티가 있는데, 이는 다시 생성자 함수 자신을 가리킨다.
이 프로퍼티는 인스턴스가 자ㅣㄴ의 생성자 함수가 무엇인지를 알고자 할 때 필요한 수단이다.

직각삼각형의 대각선 방향, 즉 __proto__ 방향을 계속 찾아가면 최종적으로는 Object.prototype에 당도하게 된다. 이런 식으로 __proto__안에 다시 __proto__를 찾아가는 과정을 프로토타입 체이닝이라고 하며, 이 프로토타입 체이닝을 통해 각 프로토타입 메서드를 자신의 것처럼 호출할 수 있다. 이때 접근 방식은 자신으로부터 가장 가까운 대상부터 점차 먼 대상으로 나아가며, 원하는 값을 찾으면 검색을 중단한다.

object.prototype에는 모든 데이터 타입에서 사용할 수 있는 범용적인 메서드만이 존재하며, 객체 전용 메서드는 여느 데이터 타입과 달리 Object 생성자 함수에 스태틱하게 담겨있다.

프로토타입 체인은 반드시 2단계로만 이뤄지는 것이 아니라 무한대의 단계를 생성할 수도 있다.

profile
new blog✨ https://kay-log.tistory.com/

0개의 댓글