fffo
프로토타입 본문
프로토타입
- js는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어임
객체지향 프로그래밍
- 명령형 프로그래밍 : 프로그램을 명령어나 함수의 목록으로 보는 전통적인 관점
- 객체지향 프로그래밍 : 여러 개의 독립적인 단위(객체)의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임
- 실세계의 실체를 인식하는 사고를 프로그래밍에 접목시키고자 함
- 속성 : 실체는 특징이나 성질을 나타냄
- 추상화 : 속성을 프로그램에 필요한 부분만 간추려내 표현
- 객체 : 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적 자료구조.
- 상태(property)와 동작(method)을 하나의 논리적 단위로 묶음
상속과 프로토타입
- 상속 : 객체의 프로퍼티나 메서드를 다른 객체가 상속 받아 그대로 사용할 수 있는 것. 객체지향 프로그래밍의 핵심
- 상속은 중복을 제거, 코드를 재사용
function Person(name) {
this.name = name,
this.greeting = function() {
return `Hi I'm ${name}.`;
}
}
const p1 = new Person("dlwlrma");
const p2 = new Person("BDNS");
console.log(p1.greeting === p2.greeting); // false
function Person(name) {
this.name = name,
}
Person.prototype.greeting = function() {
return `Hi I'm ${name}.`;
};
const p1 = new Person("dlwlrma");
const p2 = new Person("BDNS");
console.log(p1.greeting === p2.greeting); // true;
console.log(p1.greeting()); //Hi I'm dlwlrma.
console.log(p2.greeting()); //Hi I'm BDNS.
- js는 프로토타입을 기반으로 상속을 구현
프로토타입 객체
- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가짐
- 객체의 프로토타입의 참조가 저장됨
- 내부 슬롯은 직접 접근 불가. [[Prototype]]는 proto를 통해 간접적으로 접근 가능
proto 접근자 프로퍼티
- 객체의 [[Prototype]]에 접근할 수 있는 접근자 프로퍼티
- proto는 객체가 직접 소유하는 프로퍼티가 아닌 Object.prototype의 프로퍼티를 상속 받아서 사용하는 것임
const obj = {};
const parent = { x: 1 };
obj.__proto__ = parent; // 프로퍼티 [[value]]을 설정하는 것이 아닌 setter함수 호출!!!!
console.log(obj.x); // 1
- 위의 코드에서 obj.proto는 프로퍼티 값을 할당하는 것이 아닌 setter함수를 호출하는 것에 유의
- 접근자 프로퍼티를 사용하는 이유는 단방향의 프로토타입 체인을 만들기 위해 제약을 주기 위함
- 순환참조가 생기는 것을 체크하고 방지
- proto를 코드에서 직접 사용하는 것은 권장되지 않음
- 모든 객체가 proto를 사용할 수 있는 것은 아니기 때문
- 대신 Object.getPrototypeOf / ObjectsetPrototypeOf 사용을 권장. 같은 동작 수행함
const obj = Object.create(null); console.log(obj.__proto__); // undefinded console.log(Object.getPrototypeOf(obj)); // nulll const child = {}; const parent = { x: 1 }; Object.getPrototypeOf(child); // child.__proto__; 와 같은 동작 Object.setPrototypeOf(child, parent); // child.__proto__ = parent; 와 같은 동작 console.log(child.x); // 1
- 각각 ES5, ES6에 도입된 메서드이고 IE9이상 IE11이상에서 지원함
함수 객체의 prototype 프로퍼티
- prototype 프로퍼티 : 생성자로 호출할 수 있는 함수 객체만 소유함. 생성자 함수가 생성할 인스턴스의 프로토타입을 참조
- 일반 함수도 생성자로 호출할 수 있다면 prototype을 가지지만 의미 없음
- 생성자함수의 prototype 프로퍼티와 인스턴스의 proto 접근자 프로퍼티로 접근한 객체는 같은 프로토타입 객체를 가리킴
function Person(name) {
this.name = name
};
const p1 = new Person('dlwlrma');
console.log(Person.prototype === p1.__proto__); // true
프로토타입의 constructor 프로퍼티와 생성자 함수
- 모든 프로토타입은 constructor 프로퍼티를 가짐
- 생성자 함수로 생성된 인스턴스 객체는 constructor 프로퍼티를 가지지 않지만 프로토타입을 상속 받아서 사용 가능
리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
- 엄밀히 말하면 리터럴로 생성된 객체와 생성자 함수로 생성된 객체는 다름
- 리터럴로 생성한 객체도 상속을 위해 프로토타입이 필요하므로 생성자 함수가 존재해야함
- 리터럴로 생성된 객체는 실제로 객체의 생성자 함수는 아니지만 가상적인 생성자 함수를 가짐
function temp() {}
console.log(temp.constructor === Function); // true
// 실제 temp함수 객체는 Fucntion 생성자함수에 의해 생성된 것이 아니지만 그렇게 생각해도 무방
프로토타입의 생성 시점
- 프로토타입은 생성자 함수가 생성되는 시점에 함께 생성됨
사용자 정의 생성자 함수의 프로토타입 생성 시점
- 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입 생성
- 함수 선언문은 런타임 이전에 js엔진에 의해 먼저 실행되므로 프로토타입도 런타임 이전에 생성됨
빌트인 생성자 함수의 프로토타입 생성 시점
- 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성되고 이 때 프로토타입도 생성되고 바인딩됨
- 전역 객체 : 런타임 이전에 js엔진에 의해 생성되는 특수한 객체
- 브라우저는 window, node.js는 global
- 프로퍼티로 표준 빌트인 객체와 환경에 따른 호스트 객체, var키워드로 선언한 전역 변수 및 전역 함수를 가짐
- Math, Reflect, JSON을 제외한 모든 표준 빌트인 객체는 생성자 함수임
객체 생성 방식과 프로토타입의 결정
- 다양한 객체 생성 방법이 있지만 공통적으로 추상 연산 OrdinaryObjectCreate에 의해 생성됨
- OrdinaryObjectCreate는 자신이 생성할 객체의 프로토타입을 인수로 전달 받음, 즉, 프로토타입은 OrdinaryObjectCreate에 전달되는 인수에 의해 결정됨
- 이 인수는 객체가 생성되는 시점에 객체 생성 방식에 의해 결정됨
- 객체 리터럴 : Object.prototype 전달. 객체 리터럴 내부에 프로퍼티 추가
- Object 생성자 함수 : Object.prototype 전달. 빈 객체 생성 후 프로퍼티 추가
- 생성자 함수 : 생성자 함수의 prototype에 바인딩되어있는 객체 전달
프로토타입 체인
- 프로토차입 체인 : 객체의 프로퍼티에 접근하려할 때 객체에 해당 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 타고 올라가면서 프로토타입의 프로퍼티를 검색함
- 프로토타입의 최상위는 항상 Object.prototype임. 프로토타입 체인의 종점
- 최상위에도 프로퍼티가 없다면 에러 발생이 아닌 undefined를 반환함에 유의
- 프로퍼티가 아닌 식별자는 스코프 체인에서 검색함
- 스코프 체인과 프로토타입 체인은 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용됨
오버라이딩과 프로퍼티 섀도잉
- 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의해 사용하는 방식
- 오버로딩 : 함수의 이름은 동일하지만 매개변수의 타입이나 개수가 다른 메서드를 구현해 매개변수에 의해 메서드를 구별하여 호출하는 방식. js는 오버로딩을 지원하지 않지만 arguments 객체를 이용해 구현할 수는 있음
- 프로퍼티 섀도잉 : 상속관계에 의해 프로퍼티가 가려지는 현상
- 인스턴스에 메서드를 추가시 해당 객체의 프로퍼티가 추가될 뿐, 실제로 프로토타입을 덮어쓰진 않음
- 거꾸로 메서드 삭제시 해당 객체에서의 삭제가 일어날 뿐, 프로토타입의 메서드는 삭제 불가능
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype.greeting = function () {
console.log(`Hi I'm ${this.name}`);
};
return Person;
}());
const p1 = new Person('dlwlrma');
p1.greeting(); // Hi I'm dlwlrma
p1.greeting = function () {
console.log(`Hello. I'm ${this.name}`);
}
p1.greeting(); // Hello I'm dlwlrma
delete p1.greeting;
p1.greeting(); // Hi I'm dlwlrma
delete p1.greeting;
p1.greeting(); // Hi I'm dlwlrma
프로토타입의 교체
- prototype에 새로운 객체를 넣음으로써 프로토타입 교체 가능
- constructor를 추가하지 않으면 생성자 함수와의 연결이 끊김
- 생성자 함수를 통한 교체와 객체 인스턴스를 통한 교체가 있음
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
greeting () {
console.log(`Hi I'm ${this.name}`);
}
};
return Person;
}());
const p1 = new Person('dlwlrma');
p1.greeting(); // Hi I'm dlwlrma
const parent = {
constructor: Person,
greeting () {
console.log(`Hello I'm ${this.name}`);
}
}
Object.setPrototypeOf(p1,parent);
p1.greeting(); // Hello I'm dlwlrma
- 객체 간 상속 관계를 동적으로 변경하는 것은 번거롭기 때문에 프로토타입은 이렇게 직접 교체하지 않는 것을 권장
- 상속 관계 변경은 직접 상속이나 클래스를 사용 권장
instanceof 연산자
- instanceof : 이항 연산자로, 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받음
- 우변의 피연산자가 함수가 아니면 TypeError 발생
- 우변의 prototype에 바인딩된 객체가 좌변 객체의 프로토타입 체인 상에 존재하면 true, 그렇지 않으면 false 반환
- 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아님에 유의
function Person(name) {
this.name = name;
}
const me = new Person('wlrma');
console.log(me instanceof Person); // true
const parent = {};
Object.setPrototypeOf(me, parent);
console.log(me instanceof Person); // false
Person.prototype = parent;
console.log(me instanceof Person); // true
직접 상속
Object.create에 의한 직접 상속
- Object.create : 명시적으로 프로토타입을 지정하여 새로운 객체를 만드는 메서드
- 다른 객체 생성방식과 마찬가지로 추상 연산 OrdinaryObjectCreate를 호출함
- 매개변수로 생성할 객체의 프로토타입으로 지정할 객체 전달.
- 옵션으로 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이루어진 객체 전달(Object.defineProperties 메서드의 둘째 인수와 형식 동일)
let obj = Object.create(null); // 프로토타입 체인의 종점에 위치하는 객체 생성
// console.log(obj.toString()) ; // -> TypeError
obj = Object.create(Object.prototype); // obj = {}; 와 동일
obj = Object.create(Object.prototype, {
x: { value: 1, writable: true, enumerable: true, configurable: true }
}); // obj = { x: 1}; 와 동일
const myProto = { x: 10};
obj = Object.create(myProto);
console.log(obj.x); // 10
- 장정 :
- new 연산자가 없이 객체 생성 가능
- 프로토타입 지정하면서 객체 생성 가능
- 객체 리터럴에 의해 생성된 객체도 상속 가능
- ESLint에서는 Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않음 → Object.create를 통해 프로토타입 체인의 종점에 위치하는 객체 생성 가능하기 때문
- call을 통해 간접적으로 호출 권장
const obj = Object.create(null); obj.a = 1; console.log(Object.prototype.hasOwnProperty.call(obj,'a')); // true
객체 리터럴 내부에서 proto에 의한 직접 상속
- ES6에서 추가된 Object.create 메서드보다 간편한 방법
const myProto = { x: 10};
const obj = {
y: 20,
__proto__: myProto
};
console.log(obj.x, obj.y); // 10 20
정적 프로퍼티/메서드
- 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드
- 인스턴스는 정적 프로퍼티/메서드를 사용할 수 없음
- MDN과 같은 문서에서 메서드를 설명할 때 프로토타입 메서드와 정적 메서드를 구분해서 표기함
- 프로토타입 메서드를 표기할 때 prototype을 #으로 표기하는 경우도 있음
프로퍼티 존재 확인
in 연산자
const person = {
name: 'wlrma'
}
console.log('name' in person); // true
console.log('toString' in person); // true
- 객체가 상속 받은 모든 프로토타입의 프로퍼티를 확인함
- ES6에 도입된 Reflect.has 메서드도 동일하게 동작
const person = {
name: 'wlrma'
}
console.log(Reflect.has(person, 'name')); // true
console.log(Reflect.has(person, 'toString')); // true
Objec.prototype.hasOwnProperty 메서드
- 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티인 경우만 true 반환
const person = {
name: 'wlrma'
}
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('toString')); // false
프로퍼티 열거
for ... in 문
- 객체의 프로토타입 체인 상에 존재하는 모든 프로퍼티 중 프로퍼티 어트리뷰트 [[Enumerable]]값이 true인 프로퍼티를 순회하며 열거
- 열거할 때 순서를 보장하지 않음(대부분의 모던 브라우저는 순서를 보장하고 숫자(사실은 문자열)인 프로퍼티 키에 대해서는 정렬을 실시함)
const obj = {
2: 2,
3: 3,
1: 1,
b: 'b',
a: 'a'
}
for (const key in obj) {
if (!obj.hasOwnProperty(key)) continue;
console.log(`${key}: ${obj[key]}`);
}
/*
1: 1
2: 2
3: 3
b: b
a: a
*/
Object.keys/values/entries 메서드
- 객체 자신의 열거 가능한 고유 프로퍼티를 열거
- Object.values/entries는 ES8에 도입됨
const person = {
name: 'wlrma',
age: 29,
__proto__: { sing: 'night letter'}
}
for (const key in person) {
console.log(key);
}
/*
name
age
sing
*/
console.log(Object.keys(person)); // [ 'name', 'age' ]
'Programming > Javascript' 카테고리의 다른 글
빌트인 객체 (0) | 2021.10.05 |
---|---|
strict mode (0) | 2021.10.04 |
함수의 일급 객체 (0) | 2021.10.02 |
생성자 함수에 의한 객체 생성 (0) | 2021.10.01 |
프로퍼티 어트리뷰트 (0) | 2021.09.30 |
Comments