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);
② 자바스크립트의 모든 객체는 [[Prototype]] 내부슬롯을 가진다.
- [[Prototype]] 값은 null 또는 객체이며, 상속 구현 시 사용한다.
- [Prototype]] 객체의 데이터 프로퍼티는 get 액세스를 위해 상속되어 자식 객체의 프로퍼티처럼 사용할 수 있다. (set 액세스는 허용X)
- __proto__ 접근자 프로퍼티를 통해 객체에 접근할 수 있는데, 이는 내부적으로 Object.getPrototypeOf가 호출되어 프로포토타입 객체를 반환하기 떄문이다.
var student = {
name: '홍길동',
score: 88
};
console.log(student.__proto__ === Object.prototype); // true
객체를 생성할 때 프로토타입이 결정되며, 결정된 프로토타입 객체는 임의의 객체로 변경할 수 있다.
→ 부모 객체인 프로토타입을 동적으로 변경 가능.
→ 이를 통해 객체의 상속 구현
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 (최상위)
② 프로토타입 체인 - 생성자 함수
생성자 함수로 객체를 생성하기 위해서는 생성자 함수를 정의해야 한다.
[함수 정의 방법]
- 함수 표현식
- 함수 선언식
- 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 |