[JavaScript] 프로토타입

화나·2020년 9월 2일
0
post-thumbnail

💡 자바스크립트는 다른 객체지향 프로그래밍 언어와는 다른 프로토타입 기반의 객체지향 프로그래밍을 지원한다.

💡 자바스트립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 프로토타입객체(부모객체)로 취급한다.

1. 프로토타입의 두가지 의미 - [[Prototype]] vs prototype프로퍼티

  • [Prototype]] (= __proto__) : 객체 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티로, 함수를 포함한 모든 객체가 가지고 있다.
  • prototype 프로퍼티 : 함수 객체의 프로퍼티로, 생성자 함수의 입장에서 자신과 링크된 부모 역할을 하는 프로토타입을 가리킨다.

🔸 자바스크립트에서 모든 객체는 자신을 생성한 생성자함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 부모 객체로 설정하는 [[Prototype]] 링크로 연결한다.

//예제
function Person(name){
	this.name = name;
}

var foo = new Person('Kim');

console.dir(Person); //prototype 프로퍼티 있음 : 생성자 함수이기 때문
console.dir(foo); //prototype 프로퍼티 없음 : 객체이기 때문
  • console.dir(Person) 결과값
  • console.dir(foo) 결과값

  • Person() 생성자 함수는 prototype 프로퍼티로 자신의 프로토타입 객체(Person.prototype)을 가리킨다.
  • foo 객체(Person() 생성자 함수로 생성됨)는 Person() 함수의 프로토타입 객체(Person.prototype)를 [[Prototype]] 링크로 연결한다.
  • 프로토타입 객체(Person.prototype)의 constructor 프로퍼티는 Person() 생성자 함수를 가리키고 있다.

결국, 자바스크립트에서 객체를 생성하는 역할생성자 함수가 하지만, 생성된 객체의 실제 부모역할을 하는 건 생성자 자신이 아닌 생성자의 prototype 프로퍼티가 가리키는 프로토타입 객체이다.

2. 프로토타입 체이닝

  • 객체가 자기 자신의 프로퍼티뿐만 아니라, 프로토타입 객체의 프로퍼티도 자신의 것처럼 접근하는 것
  • 자바스크립트에서 특정 객체의 프로퍼티(또는 메소드)에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티(또는 메소드)가 없다면 [[prototype]]링크를 따라 프로토타입 객체의 프로퍼티를 차례대로 검색하는 것

2-1. 객체 리터럴 방식으로 생성된 객체의 프로토타입 체이닝

//예제
var person = {
	name : 'Kim',
	gender : 'female',
	helloName : function() {
		console.log('Hello! my name is ' + this.name);
	}
};

person.helloName ();
console.log(person.hasOwnProperty('name')); // ①
  • 실행결과

❓ 어떻게 ①에서 person 객체에 hasOwnProperty() 메소드가 없음에도 정상적으로 결과가 출력되었을까?

  1. 객체리터럴로 생성한 객체는 Object()라는 내장 생성자 함수로 생성된 것이다.

  2. Object() 생성자함수도 함수객체이므로 prototype 프로퍼티 속성이 있다.

  3. 객체 리터럴 형태의 person객체는 Object() 생성자 함수의 prototype 프로퍼티가 가리키는 Object.prototype 객체를 자신의 프로토타입 객체로 연결한다.

  4. 자바스크립트는 person객체에 hasOwnProperty() 메소드가 없기 때문에, [[prototype]]링크를 따라 객체의 부모 역할을 하는 프로토타입 객체 내에 hasOwnProperty() 메소드가 있는지 검색한다. (프로토타입 체이닝)

  5. 결국, 프로토타입 체이닝 덕분에 정상적인 결과가 출력되었다.

  • Object.prototype객체는 자바스크립트 모든 객체의 조상 역할을 하는 객체
  • 자바스크립트가의 모든 객체가 호출할 수 있는 toString(), hasOwnProperty() 등과 같은 표준 메서드를 제공하고 있음

2-2. 생성자 함수로 생성된 객체의 프로토타입 체이닝

//예제
function Person(name, age, hobby){
	this.name = name;
	this.age = age;
	this.hobby = hobby;
}

var foo = new Person('kim', 99, 'swimming');

console.dir(foo.hasOwnProperty('name'));
console.dir(Person.prototype);
  • 실행결과

❓ 어떻게 foo 객체에 hasOwnProperty() 메소드가 없음에도 정상적으로 결과가 출력되었을까?

  1. foo객체의 프로토타입 객체 = Person()(foo객체를 생성한 생성자함수)의 prototype프로퍼티가 가리키는 Person.prototype이 된다.

  2. foo.hasOwnProperty()메소드를 호출했지만 foo객체는 hasOwnProperty() 메소드가 없음 ➡️ 프로토타입 체이닝으로 foo 부모 객체인 Person.prototype 객체에서 hasOwnProperty() 메소드를 찾는다.

  3. 하지만 함수에 연결된 프로토타입 객체는 constructor 프로퍼티만을 가진 객체 ➡️ hasOwnProperty()메소드가 없다.

  4. 이대로 끝나는게 아니라, Person.prototype도 자바스크립트의 객체이므로 Object.prototype을 프로토타입 객체로 가진다.

  5. 프로젝트 체이닝은 Object.prototype 객체로 계속 이어지고 Object.prototype 객체의 hasOwnProperty() 메소드가 실행되므로 에러가 발생하지 않는다.

2-3. 프로토타입 체이닝의 종점

  • 객체 리터럴 방식이나 생성자 함수를 이용한 방식 모두 Object.prototype에서 프로토타입의 체이닝이 끝난다.
  • 모든 자바스크립트의 객체는 프로토타입 체이닝으로 Object.prototype객체가 가진 프로퍼티와 메소드에 접근하고 서로 공유가 가능하다.

3. 프로토타입의 확장

  • 프로토타입 객체 역시 자바스크립트 객체이므로 프로퍼티를 추가/삭제하는것이 가능하다.
    ➡️변경된 프로퍼티는 실시간으로 프로토타입 체이닝에 반영된다.

3-1. 기본 데이터 타입의 확장

  • 자바스크립트의 숫자, 문자열, 배열에서 사용되는 표준 메소드들의 경우 이들의 프로토타입인 Number.prototype, String.prototype, Array.prototype에 정의되어 있다.
  • 이러한 표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메소드들을 추가할 수 있다.
//예제
String.prototype.test = function(){
	console.log('This is the testMethod()');
};

var str = "test value";
str.test();

console.dir(String.prototype);

3-2. 프로토타입 객체의 동적 메소드 생성

  • 프로토타입 객체 역시 자바스크립트 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는것이 가능하다.
  • 이렇게 변경된 프로퍼티는 실시간으로 프로토타입 체이닝에 반영된다.
//예제
function Person(name){
	this.name = name;
}

var foo = new Person('Kim');

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

foo.sayHello();

4. 프로토타입 객체의 변경

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

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

//예제
function Person(name){
	this.name = name;
}

console.log(Person.prototype.constructor); // ①

var foo = new Person('foo');
console.log(foo.country); //②

//디폴트 프로토타입 객체 변경
Person.prototype = {
	country : 'korea'
};

console.log(Person.prototype.constructor); //③

var bar = new Person('bar');
console.log(foo.country); //④
console.log(bar.country); //⑤
console.log(foo.constructor); //⑥
console.log(bar.constructor); //⑦ 
  • 실행결과
  1. Person.prototype 객체는 자신과 연결된 Person() 생성자 함수를 가리키는 constructor 프로퍼티만을 가진다. 그래서 ①의 결과는 Person() 생성자 함수가 출력된다.

  2. foo 객체를 생성하면 객체 생성 규칙에 따라 Person.prototype 객체를 자신의 프로토타입 객체로 연결한다.
    그러나 foo객체, Person.prototype 객체 모두 country 프로퍼티를 가지고 있지 않으므로 프로토타입 체이닝이 일어난다고해도 undefined가 출력된다. //②의 값

  3. 디폴트 프로토타입을 객체 리터럴 방식으로 country 프로퍼티를 가진 객체로 변경했다.
    🤔③의 값은 어떻게 출력이 될까?
    변경한 디폴트 프로토타입은 country 프로퍼티만을 가진 객체로 변경 됐으므로 다른 디폴트 프로토타입처럼 constructor 프로퍼티는 더이상 존재하지 않는다.
    하지만 이 경우도 프로토타입 체이닝이 발생하기 때문에 객체 리터럴 방식으로 변경해준 디폴트 프로토타입 객체는 Object.prototype을 [[Prototype]]링크로 연결한다.
    Object.prototype도 Object() 생성자 함수와 연결된 빌트인 프로토타입 객체여서, Object() 생성자 함수를 constructor 프로퍼티에 연결하고 있다.
    그래서 ③의 값은 Object() 생성자 함수가 출력된다.

  4. bar 객체를 생성했다. 이때 Person() 생성자 함수의 prototype 프로퍼티는 디폴트 프로토타입 객체가 아니라 변경된 프로토타입 객체를 가리키고 있다.
    그래서 bar객체는 새로 변경된 프로토타입 객체를 [[Prototype]] 링크로 가리키게 된다.

  5. foo 객체 : 디폴트 프로토타입 객체를 [[Prototype]] 링크로 연결
    bar 객체 : 새로 변경된 프로토타입 객체를 [[Prototype]] 링크로 연결
    ④의 결과 : 디폴트 프로토타입에는 country 프로퍼티가 없으므로 undefined 출력
    ⑤의 결과 : 새로 변경된 프로토타입에는 country 프로퍼티가 있으므로 'korea' 출력
    ⑥의 결과 : 디폴트 프로토타입의 constructor인 Person() 생성자 함수 출력
    ⑦의 결과 : 프로토타입 체이닝에 의해 Object.property의 constructor인 Object() 생성자 함수 출력

5. 프로토타입 체이닝의 동작

  • 객체의 특정 프로퍼티를 읽으려고 할때, 특정 프로퍼티가 해당 객체에 없는 경우만 프로토타입 체이닝이 발생한다.
  • 자바스크립트는 객체에 없는 프로퍼티 값을 쓰려고 할 경우 동적으로 객체에 프로퍼티를 추가한다.
//예제
function Person(name){
	this.name = name;
}

Person.prototype.country = 'korea';

var foo = new Person('foo');
var bar = new Person('bar');

console.log(foo.country);
console.log(bar.country);
foo.country = 'china';

console.log(foo.country);
console.log(bar.country);
  • 실행결과
  1. foo객체는 name 프로퍼티밖에 없으므로 foo.country에 접근하였을 때 프로토타입 체이닝이 이루어지면서 Person.prototype의country 프로퍼티 값이 출력된다.

  2. foo.country 값에 china를 저장하면 Person.prototype의 country 프로퍼티가 변경되는것이 아니라 foo 객체에 country 값이 동적으로 생성된다.

  3. 그래서 foo.country는 country 프로퍼티 값이 있으므로 'china'가 출력되는 반면에, bar.country는 country 프로퍼티가 존재하지 않으므로 프로토타입 체이닝이 이루어져서 'korea'가 출력되게 된다.


출처 : 인사이드 자바스크립트

0개의 댓글