[h01010] 프로토타입 체이닝(후반부)

ARGOS JavaScript·2021년 10월 13일
0
post-thumbnail

4.5.4 프로토타입 체이닝의 종점

자바스크립트에서 Object.prototype 객체는 프로토타입 체이닝의 종점이다.

앞에서 봤듯이 객체 리터럴 방식이나 생성자 함수를 이용한 방식이나 결국엔 Object.prototype에서 프로토타입 체이닝이 끝난다.
달리 해석하면, 객체 리터럴 방식이나 생성자 함수 방식에 상관없이 모든 자바스크립트 객체는 프로토타입 체이닝으로 Object.prototype 객체가 가진 프로퍼티와 메서드에 접근 및 공유가 가능하다는 의미이다.

때문에 자바스크립트 표준 빌트인 객체인 Object.prototype에는 hasOwnProperty()나 isPrototypeOf() 등과 같이 모든 객체가 호출 가능한 표준 메서드들이 정의되어 있다.

4.5.5 기본 데이터 타입 확장

Object.prototype에 정의된 메서드들은 자바스크립트의 모든 객체의 표준 메서드이다.

이와 같은 방식으로 자바스크립트의 숫자, 문자열, 배열 등에서 사용되는 표준 메서드들의 경우,
Number.prototype, String.prototype, Array.prototype 등에 정의되어있다.
물론 이러한 객체들 또한 Object.prototypedmf 자신의 프로토타입으로 가지고 있어서 프로토타입 체이닝으로 연결된다.

표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메서드들을 추가할 수 있다.

String.prototype.testMethod = function () {
	console.log("hihi");
};

var str = "this is test";
str.testMethod();

console.dir(String.prototype);

위 그림에도 나와있듯이 String.prototype 또한 자신의 프로토타입이 있고, 그것이 바로 Object.prototype이라는 것이다.
따라서 문자열 또한 프로토타입 체이닝으로 hasOwnProperty() 같은 Object.prototype 메서드를 호출 할 수 있다.

4.5.6 프로토타입도 자바스크립트 객체다

함수가 생성될 때, 자신의 prototype 프로퍼티에 연결되는 프로토타입 객체는 디폴트로 constructor 프로퍼티만을 가진 객체다.
당연히 프로토타입 객체 역시 자바스크립트 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는 것이 가능하다.

function Person(name) {
    this.name = name;
}

var foo = new Person('foo');

//foo.sayHello();				// -----------1)

Person.prototype.sayHello = function(){		// ---------2)
    console.log('Hello');
}

foo.sayHello();					// Hello --------3)

1) 아직 sayHello() 메서드가 정의되어있지 않아 에러가 발생한다.
2) foo 객체의 프로토타입 객체인 Person.prototype 객체에 동적으로 sayHello() 메서드를 추가했다.
3) foo에는 sayHello() 메서드가 없지만, 프로토타입 체이닝으로 Person.prototype 객체에서 sayHello() 메서드를 검색한다.

4.5.7 프로토타입 메서드와 this 바인딩

프로토타입 객체는 메서드를 가질 수 있다(짧게는 프로토타입 메서드라고도 부른다).

만약 프로토타입 메서드 내부에서 this를 사용하면 어떻게 될까?
이는 this 바인딩 규칙을 따른다. 결국, 메서드 호출 패턴에서의 this는 그 메서드를 호출한 객체에 바인딩된다는 것을 기억하자.

function Person(name) {
    this.name = name;
}

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

var foo = new Person('foo');

console.log(foo.getName());					// foo -----2)

Person.prototype.name = 'person';

console.log(Person.prototype.getName());	----------3)

1) getName() 메서드는 내부에 this를 포함하고 있다.
2) foo 객체에서 getName() 메서드를 호출하면, foo 객체에서 찾을 수 없으므로 프로토타입 체이닝이 발생한다. foo 객체의 프로토타입 객체인 Person.prototype에서 getName() 메서드가 있으므로, 이 메서드가 호출된다. 이때 getName() 메서드를 호출한 객체는 foo이므로, this는 foo 객체에 바인딩된다.
3) Person.prototype.getName() 메서드와 같이 프로토타입 체이닝이 아니라, 바로 객체에 접근해서 getName() 메서드를 호출하면, 이때는 getName() 메서드를 호출한 객체가 Person.prototype이므로 this도 여기에 바인딩 된다.

4.5.8 디폴트 프로토타입은 다른 객체로 변경이 가능하다

디폴트 프로토타입 객체는 함수가 생성될 때 같이 생성되며, 함수의 prototype 프로퍼티에 연결된다.
자바스크립트에서는 이렇게 함수를 생성할 때 해당 함수와 연결되는 디폴트 프로토타입 객체를 다른 일반 객체로 변경하는 것이 가능하다. 이러한 특징을 이용해서 객체지향의 상속을 구현한다.
(* 6장에서 더 나옴)

하지만, 생성자 함수의 프로토타입 객체가 변경되면, 변경된 시점 이후에 생성된 객체들은 변경된 프로토타입 객체로 [[Prototype]] 링크를 연결한다는 점을 기억해야 한다.
이에 반해 생성자 함수의 프로토타입이 변경되기 이전에 생성된 객체들은 기존 프로토타입 객체로의 [[Prototype]] 링크를 그대로 유지한다.

function Person(name) { 					
    this.name = name;
}

console.log(Person.prototype.constructor);	// Person(name) ----------1)

var foo = new Person('foo');
console.log(foo.country);					// undefined -------------2)

Person.prototype = {						-------------------------3)
    country: 'kor',
};

console.log(Person.prototype.constructor);	// Object() ---------------4)

var bar = new Person('bar');
console.log(bar.country);					// kor ---------------5)

1) Person() 함수를 생성할 때 디폴트로 같이 생성되는 Person.prototype 객체는 자신과 연결된 Person() 생성자 함수를 가리키는 constructor 프로퍼티만을 가진다. 때문에 Person.prototype.constructor는 Person() 생성자 함수를 가리킨다.
2) foo 객체를 생성하면 foo 객체는 Person.prototype 객체를 자신의 프로토타입으로 연결한다. 그러나 foo 객체는 country 프로퍼티가 없고 또한, 디폴트 프로토타입 객체 Person.prototype도 country 프로퍼티가 없으므로 프로토타입 체이닝이 일어나도 결국 undefined 값이 출력된다.
3) 프로토타입 객체를 country 프로퍼티를 가진 객체로 변경하였다.
4) Person.prototype.constructor이 없기때문에 이 경우도 프로토타입 체이닝이 발생한다. 변경한 프로토타입 객체는 객체 리터럴 방식으로 생성했으므로 Object.prototype을 [[Prototype]] 링크로 연결한다. 따라서 Object.prototype 객체로 프로토타입 체이닝이 발생한다.
5) 이제 bar는 새로 변경된 프로토타입 객체를 [[Prototype]]로 연결한다.

4.5.9 객체의 프로퍼티 읽기나 메서드를 실행할 때만 프로토타입 체이닝이 동작한다

객체의 특정 프로퍼티를 읽으려고 할 때, 프로퍼티가 해당 객체에 없는 경우 프로토타입 체이닝이 발생한다. 반대로 객체에 있는 특정 프로퍼티에 값을 쓰려고 한다면 이때는 프로토타입 체이닝이 일어나지 않는다. (당연한 얘기!!)

자바스크립트는 객체에 없는 프로퍼티에 값을 쓰려고 할 경우 동적으로 객체에 프로퍼티를 추가하기 때문이다.

function Person(name) { 					
    this.name = name;
}

Person.prototype.country = 'Korea';

var foo = new Person('foo');
var bar = new Person('bar');
console.log(foo.country);			// Korea	
console.log(bar.country);			// Korea	-------1)

foo.country = 'USA';				// ----------------2)

console.log(foo.country);			// USA
console.log(bar.country);			// Korea	-------3)

foo와 bar 객체는 둘 다 Person.prototype 객체를 프로토타입으로 가진다.

1) foo.country에 접근하려 했을 때 foo 객체는 name 프로퍼티밖에 없으므로 프로토타입 체이닝이 이뤄지면서 foo의 프로토타입 객체인 Person.prototype의 country 프로퍼티인 'Korea'가 출력된다.
2) 1과 반대로 'USA'를 저장하면, 프로토타입 체이닝이 동작하지 않고 foo 객체에 country 프로퍼티값이 동적으로 생성된다.
3) bar 객체만 프로토타입 체이닝을 거쳐 'Korea'가 출력된다.

profile
ARGOS JavaScript 정복하기

0개의 댓글