본문 바로가기

프로그래밍/js

lessons 14. 프로토타입

2023.02.12

자바스크립트 스터디 5회차

공부 사이트: https://poiemaweb.com/

 

14. 프로토타입

1) 프로토타입 객체

클래스 기반 객체지향 프로그래밍: 객체 생성 이전에 클래스를 정의, 이를 통해 객체(인스턴스) 생성

프로토타입 기반 객체지향 프로그래밍: 클래스 없이도 객체 생성 가능

 

① 자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있다.

- 상속 개념과 유사하게 부모 객체의 프로퍼티/메소드를 상속받아 사용할 수 있게 한다.

- 이러한 부모 객체를 프로토타입 객체 또는 프로토타입이라고 한다.

 

var student = {
	name: '홍길동',
	score: 88
};

console.log('student 객체는 \'name\' 프로퍼티를 가지고 있는가? >' + student.hasOwnProperty('name')) // true 
console.dir(student);

student 객체의 프로토토타입인 Object 확인가능

 

 

② 자바스크립트의 모든 객체는 [[Prototype]] 내부슬롯을 가진다.

- [[Prototype]] 값은 null 또는 객체이며, 상속 구현 시 사용한다. 

- [Prototype]] 객체의 데이터 프로퍼티는 get 액세스를 위해 상속되어 자식 객체의 프로퍼티처럼 사용할 수 있다. (set 액세스는 허용X)

- __proto__ 접근자 프로퍼티를 통해 객체에 접근할 수 있는데, 이는 내부적으로 Object.getPrototypeOf가 호출되어 프로포토타입 객체를 반환하기 떄문이다.

var student = {
	name: '홍길동',
	score: 88
};
console.log(student.__proto__ === Object.prototype); // true

__proto__ 접근자 프로퍼티를 통해 프로토타입 객체에 접근 가능하다.

객체를 생성할 때 프로토타입이 결정되며, 결정된 프로토타입 객체는 임의의 객체로 변경할 수 있다.

→ 부모 객체인 프로토타입을 동적으로 변경 가능.

→ 이를 통해 객체의 상속 구현

 

 

 

 

2) [[Prototype]]과 prototype 프로퍼티

둘 다 프로토타입 객체를 가리킨다는 것이 같으나, 관점의 차이가 있다.

 

① [[Prototype]]  내부슬롯

- 자신의 프로토타입 객체를 가리킨다. 모든 객체가 가지고 있다.

- 상속을 위해 사용된다.

- 객체의 입장에서 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다.  함수 객체의 경우 Function.prototype을 가리킨다.

- __proto__가 붙어 있으면 인스턴스이다.

인스턴스.__proto__
Object.__proto__    //여기서 Object는 인스턴스

 

 

② prototype  프로퍼티

- 자신의 프로토타입 객체를 가리킨다. 함수 객체만 가지고 있다.

- 생성자 함수 입장에서, 함수를 통해 생성한 인스턴스의 프로토타입 객체를 가리킨다.

→ 생성자 함수의 prototype 프로퍼티가 가리키는건, 인스턴스의 프로토타입 객체(부모)이다. 즉, 생성자 함수 자기자신을 의미함.

- prototype 프로퍼티가 붙어 있으면 생성자 함수이다.

생성자함수.prototype
Object.prototype     //여기서 Object는 생성자함수

[TIP] 주의할점

생성자 함수 F의 프로토타입을 의미하는 F.prototype

여기서 prototype은 F에 정의된 일반 프로퍼티이다. 프로토타입(부모 객체)이 아니다.

생성자 함수 prototype 프로퍼티는 개발자가 할당하지 않아도 생성되는 디폴트 프로퍼티이다. 

function Person() { }
Person.prototype;     //{constructor : Person}


 

③ 예제

// 생성자 함수 Person()
function Person(name) {
	this.name = name;
}

var person = new Person('홍길동');

// 객체(인스턴스)의 입장에서, 객체의 __proto__는 Function.prototypoe을 가리킨다.
console.log(Person.__proto__ === Function.prototype);  //true
// 생성자 함수의 입장에서, 함수의 prototype 프로퍼티는 인스턴스의 프로토타입을 가리킨다.
console.log(Person.prototype === person.__proto__);   //true

[알 수 있는 것]

- 생성자 함수 객체의 프로토타입 객체는 Function.prototype

- 생성자 함수 객체의 prototype 프로퍼티는 인스턴스의 프로토타입 객체(생성자 함수 자기 자신)이다.

 

✔ 생성자 함수의 prototype 프로퍼티는 자기 자신을 의미, 생성자 함수의 __proto__는 프로토타입 객체.

Person 함수 객체의 부모는 Function.prototype이고,  Person 함수 객체의 prototype 프로퍼티는 Person

 

 

 

 

3) constructor 프로퍼티

- 프로토타입 객체가 가지는 프로퍼티

- 인스턴스 입장에서 자신을 생성한 객체를 가리킨다.

▶ "어떤 생성자 객체를 통해 생겨난 인스턴스인지를 알려준다."

 

[Example]

Person() 생성자 함수의 prototype 프로퍼티는 디폴트값인 {constructor : Person}

Person() 생성자 함수의 constructor 프로퍼티는 자신을 생성한 객체인 Function()

person 인스턴스의 construnctor 프로퍼티는 자신을 생성한 객체인 Person

// 생성자 함수 Person()
function Person(name) {
	this.name = name;
}

var person = new Person('홍길동');

// 생성자 함수 Person의 prototype은 {constructor : Person}
console.log(Person.prototype.constructor === Person); 

// person 인스턴스의 constructor는 자신을 생성한 객체
console.log(person.constructor === Person);

// 생성자 함수의 constructer는 자신을 생성한 객체. Function()
console.log(Person.constructor === Function);

 

 

 

4) Prototype chain

- 특정 객체의 프로퍼티/메소드에 접근할 때, 그 객체의 프로퍼티/메소드가 없다면 [[Prototype]] 이 가리키는 링크를 따라간다.

- 프로토타입 객체의 프로퍼티/메소드를 차례대로 검색한다. → 프로토타입 체인

 

var person = {
	name: '홍길동',
	age: 123
};

// person 객체에 hasOwnProperty() 메소드 없어도 에러 안뜸.
// 프로토타입 객체인 Object.prototype의 메소드를 호출한 것.
console.log(person.hasOwnProperty('name')); //ture

console.log(person.__proto__ === Object.prototype);  //ture
console.log(Object.prototype.hasOwnProperty('hasOwnProperty'));   //true

 

[객체를 생성하는 방법]

- 객체 리터럴

- 생성자 함수

- Object() 생성자 함수

 

 

① 프로토타입 체인 - 객체 리터럴 방식

객체 리터럴 { ] 로 객체 생성 시 내부적으로 Object() 생성자 함수를 사용해서 객체를 생성한다.

var person = {
	name: '홍길동',
	age: 123,
	sayHello: function(){
		console.log('Hi. my name is ' + this.name);
	}
};

// person 인스턴스의 프로토타입 객체 
console.log(person.__proto__ === Object.prototype);

// Object() 생성자 함수의 prototype 프로퍼티는 {constructor : Object, ...}
console.log(Object.prototype.constructor === Object);

// Object 인스턴스의 프로토타입 객체는 Function.prototype
console.log(Object.__proto__ === Function.prototype);

// Function.prototype의 프로토타입 객체는 OBject.prototype이다.
console.log(Function.prototype.__proto__ === Object.prototype);

생성자 함수들의 프로토타입 객체 → Function.prototype

 Function.prototype과 Array.prototype의 프로토타입 객체 → Object.prototype (최상위)

lessons 5 포스팅

 

 

② 프로토타입 체인 - 생성자 함수

생성자 함수로 객체를 생성하기 위해서는 생성자 함수를 정의해야 한다.

 

[함수 정의 방법]

- 함수 표현식 

- 함수 선언식

- Function() 생성자 함수

// 함수 표현식 → 함수 리터럴 방식 
var square = function square(num){
	return num * num;
}

// 함수 선언식 → 내부적으로 자바스크립트 엔진이 기명 함수 표현식으로 변환함. 즉, 함수 리터럴 방식임
function square(num){
	return num * num;
}

// Function 생성자 함수
var square = new Function('num', 'return num * num');

함수 표현식, 함수 선언식 → 함수 리터럴 방식 사용

함수 리터럴 방식 → Function() 생성자 함수로 함수 생성하는 것을 단순화

결론: 모두 Function() 생성자 함수 통해 객체를 생성한다.

 

어떤 방식으로 함수 객체를 생성해도.. 모든 함수 객체의 프로토타입 객체는 Function.prototype이다.

 

 

[객체 생성 방법]

객체 생성 방식 자바스크립트 엔진 동작 인스턴스의 프로토타입 객체
객체 리터럴 Object() 생성자 함수 Object.prototype
Object() 생성자 함수 Object() 생성자 함수 Object.prototype
그 외 생성자 함수 생성자 함수 생성자함수이름.prototype

 

[총정리 코드]

function Person(name, age){
	this.name = name;
	this.age = age;
	this.sayHello = function(){
		console.log('hello. my name is ' + this.name);
	};
}

var person = new Person('홍길동', 12);

// person 인스턴스의 프로토타입 객체는 Person.prototype
console.log(person.__proto__ ===  Person.prototype);

// Person.prototype의 프로토타입 객체는 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype);

// Person 생성자 함수의 constructor는 자신
console.log(Person.prototype.constructor === Person);

// Person 인스턴스의 프로토타입 객체는 Person.prototype
console.log(Person.__proto__ === Function.prototype);

// Function.prototype의 프로토타입 객체는 Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype);

Object.prototype 객체를 프로토타입 체인의 종점이라고 한다.

 

 

 

5) 프로토타입 객체의 확장

프로토타입 객체도 일반 객체와 같이 프로퍼티를 추가/삭제할 수 있다.

추가/삭제한 프로퍼티는 즉시 프로토타입 체인에 반영된다.

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

var person = new Person('홍길동');

Person.prototype.sayHello = function(){
	console.log('hi my name is ' + this.name);
};

person.sayHello();   //바로 사용 가능

 

 

 

6) 원시 타입의 확장

문자열은 원시 타입이지만 객체와 유사하게 동작할 수 있다.

//원시 타입 문자열
var str = 'test';
console.log(typeof str);
console.log(str.constructor === String);

//Stirng() 생성자 함수로 생성한 문자열 객체
var strObj = new String('TEST');
console.log(typeof strObj);
console.log(strObj.constructor === String);

원시 타입 문자열과 문자열 객체의 타입이 다르다. (당연함)

원시 타입은 객체가 아니므로 프로퍼티/메소드를 가질 수 없지만, 원시 타입은 프로퍼티/메소드 호출 시 원시 타입과 연관된 객체로 일시적으로 변환된다. → 일시적으로 프로토타입 객체를 공유

var str = 'test';
var strObj = new String('test');

// String 객체의 toUpperCase 메소드. 원시 타입도 사용 가능
console.log(str.toUpperCase());    
console.log(strObj.toUpperCase());

 

물론 원시 타입은 객체가 아니므로, 프로퍼티/메소드 추가는 불가능하다.

그러나 String 객체의 프로토타입 객체인 String.prototype에 프로퍼티/메소드를 추가하면 원시타입과 객체 모두 사용 가능하다.

var str = 'test';

str.myMethod1 = function() {
	console.log('myMethod1');
}

String.prototype.myMethod2 = function () {
   console.log('myMethod2');
};

str.myMethod1(); // not a function 에러
str.myMethod2();  //myMethod2
'abcd'.myMethod2();  //myMethod2

 

 

[총정리 코드]

var str = 'test';

String.prototype.myMethod = function () {
   console.log('myMethod');
};

// str 인스턴스의 프로토타입 객체는 Sring.prototype
console.log(str.__proto__ === String.prototype);

// String.prototype의 프로토타입 객체는 당연히 Object.prototype
console.log(String.prototype.__proto__ === Object.prototype)

// String 생성자 함수의 constructor는 자기자신
console.log(Stirng.prototype.constructor === String);

// String 인스턴스의 프로토타입 객체는 Function.prototype
console.log(String.__proto__ === Function.prototype);

// Function.prototype의 프로토타입 객체는 Obejct.prototype
console.log(Function.prototype.__proto__ === Object.prototype);

 

 

 

7) 프로토타입 객체의 변경

객체를 생성하면 프로토타입이 결정된다. 결정된 프로토타입은 다른 임의 객체로 변경 가능하다.

→ 부모 객체인 프로토타입을 동적으로 변경 가능. → 객체 상속 구현

 

※ 주의할 점

프로토타입 객체 변경 시점 이전에 생성된 객체는 기존 프로토타입 객체를 [[Prototype]] 소켓에 바인딩한다.

프로토타입 객체 변경 시점 이후에 생성된 객체는 변경된 프로토타입 객체를 [[Prototype]] 소켓에 바인딩한다.

 

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

var person1 = new Person('홍길동');

// 프로토타입 객체 변경 
Person.prototype = { age: 12 };

var person2 = new Person('고길동');

console.log(person1);
console.log(person2);

console.log(person1.age);
console.log(person2.age);

console.log(person1.constructor);
console.log(person2.constructor);

① constructor 프로퍼티는 Person() 생성자 함수를 가리킨다.

② 프로토타입 객체 변경 후, Person() 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 변경하면서, constructor 프로퍼티가 삭제됨.

③ 프로토타입 체인에 의해 person2.constructor의 값은 Object.prototype.constructor가 된다. 

 

 

 

8) 프로토타입 체인 동작 조건

① 객체의 프로퍼티/메소드를 참조하는 경우

② 해당 객체에 프로퍼티/메소드가 없는 경우

 

객체의 프로퍼티에 값을 할당하는 경우에는 프로토타입 체인이 동작하지 않는다.

객체에 프로퍼티가 이미 존재한다면 값을 재할당하고, 존재하지 않는다면 해당 객체에 프로퍼티를 동적으로 추가하기 떄문이다.

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

Person.prototype.age = 5;
var person1 = new Person('홍길동');
var person2 = new Person('고길동');

console.log(person1.age);    // 프로토타입 체인 발동
console.log(person2.age);    // 프로토타입 체인 발동

// person1 객체에 age 프로퍼티가 없으면 프로퍼티 동적 추가, 있으면 값 할당
person1.age = 17;

console.log(person1);
console.log(person2);   

console.log(person1.age);
console.log(person2.age);    // 프로토타입 체인 발동

 

 

'프로그래밍 > js' 카테고리의 다른 글

lessons 16. Strict mode  (0) 2023.02.12
lessons 15. 스코프  (0) 2023.02.12
lessons 13. 타입 체크  (0) 2023.02.08
lessons 12. 함수  (0) 2023.02.07
lessons 11. 객체와 변경불가성(Immutability)  (0) 2023.02.07