프로토타입 Person.prototype 에 프로퍼티를 추가해
자식 객체가 상속받을 수 있도록 구현한 코드이다.
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hi! ${this.name}`);
}
const me = new Person('Hong');
const you = new Person('Bi');
me.sayHello(); // Hi! Hong
you.sayHello(); // Hi! Bi
프로토타입 체인
자바스크립트는 객체의 프로퍼티에 접근하려 할 때 해당 객체에 접근하려는 프로퍼티가 없다면
[[Prototype]] 내부 슬롯의 참조를 따라 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.
이를 프로토타입 체인이라고 한다.
위의 예제에서 me.hasOwnProperty('name') 은 true 를 반환한다.
이 과정은 아래와 같이 설명된다.
1. hasOwnProperty 메서드를 호출한 me 객체에서 hasOwnProperty 를 검색한다. me 객체에는 없으므로 [[Prototype]] 에 바인딩되어 있는 프로토타입(Person.prototype) 으로 이동해 또 다시 hasOwnProperty를 검색한다.
2. Person.prototype 에도 hasOwnProperty 메서드가 없으므로 또 다시 [[Prototype]] 에 바인딩된 프로토타입(Object.prototype) 을 따라서 hasOwnProperty를 검색한다.
3. Object.prototype 에는 hasOwnProperty가 있다. 따라서 Object.prototype.hasOwnProperty 메서드를 호출한다. 이때 Object.prototype.hasOwnProperty 메서드의 this 는 me 객체가 바인딩 된다.
Object.prototype.hasOwnProperty.call(me, 'name');
Object.prototype 은 프로토타입 체인의 종점이다.
그렇기에 Object.prototype 의 프로토타입, 즉 [[Prototype]] 의 값은 null 이다.
만약 Object.prototype 에서도 프로퍼티를 검색할 수 없다면 undefined 를 반환한다.
전에 학습했던 스코프 체인과 개념이 혼동될 수 있다.
하지만 스코프체인은 식별자 검색을 위한 매커니즘이라는 것을 기억해두자.
me.hasOwnProperty('name');
스코프체인의 관점에서, 위 예제는 먼저 스코프 체인 내에서 me 식별자를 검색한다.
me 식별자는 전역에서 선언되었으므로 전역 스코프에서 검색한 후,
me 객체의 프로토타입 체인에서 hasOwnProperty 메서드를 검색한다.
오버라이딩과 프로퍼티 섀도잉
const Person = (function() {
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = funciton () {
console.log(`hi! ${this.name}`);
};
// 생성자 함수 반환
return Person;
}());
const me = new Person('Hong');
me.sayHello = function() {
console.log(`hey~! ${this.name}`);
}
me.sayHello(); // hey~! Hong
프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면
인스턴스 프로퍼티로 추가한다.
이처럼 상속 관계에 의해 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라고 한다.
만약 프로퍼티를 삭제한다고 가정해보자.
delete me.sayHello;
me.sayHello(); // hi! Hong
delete me.sayHello;
me.sayHello(); //hi! Hong
처음 sayHello 프로퍼티를 삭제했을 때는 인스턴스 프로퍼티를 삭제했기때문에
sayHello 를 호출했을 때 프로토타입 프로퍼티인 sayHello 를 호출한다.
두번째 삭제는 이뤄지지않는다.
하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능하기 때문이다.
프로토타입 프로퍼티를 변경/삭제 하려면 프로토타입에 직접 접근해야한다.
delete Person.prototype.sayHello;
다음 파트는 프로토타입의 교체인데, 사실 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 번거롭기 때문에
직접 교체하는건 바람직하지 않다.
상속관계를 인위적으로 설정하려면 직접상속이 더 편리하고 안전하기에 깊은 설명은 하지않겠다.
instanceof 연산자
우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변 객체의 프로토타입 상에 존재하면
true 평가를, 그렇지 않으면 TypeError 가 발생한다.
객체 instanceof 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Hong');
console.log(me instanceof Person); // true
console.log(me instanceof Object); // true
만약 프로토타입을 교체한다면?
function Person(name) {
this.name = name;
}
const me = new Person('Hong');
const parent = {};
Object.setPrototypeOf(me, parent);
console.log(Person.prototype === parent); // false
console.log(parent.constructor === Person); // false
console.log(me instanceof Person); // false
console.log(me instanceof Object); // true
만약 Person.prototype = parent; 구문을 추가한다면
me instanceof Person 은 true 로 평가될 것이다.
이처럼 instanceof 연산자는 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는것이 아닌
생성자 함수의 prototype 에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다.
직접 상속
Object.create 에 의한 직접 상속
Object.create 메서드는 명시적으로 프로토타입을 지정해 새 객체를 생성한다.
Object.create 메서드도 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate를 호출한다.
Object.create 메서드는 첫 번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체를 생성한다.
// 프로토타입이 null 인 객체 생성
let obj = Object.create(null);
console.log(Object.getPropertyOf(obj) === null); //true
// Object.prototype 상속 못받음
console.log(obj.toString()); // TypeError
//obj = {}; 와 동일
obj = Object.create(Object.prototype);
console.log(Object.getPropertyOf(obj) === Object.prototype); //true
obj = Object.create(Object.prototype, {
x: { value: 1, writable: true, enumberable: true, configurable: true}
});
console.log(obj.x); //1
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
const myProto = { x: 10 };
obj = Object.create(myProto);
console.log(obj.x); // 10
console.log(Object.getPrototypeOf(obj) === myProto); // true
function Person(name) {
this.name = name;
}
obj = Object.create(Person.prototype);
obj.name = 'Hong';
console.log(obj.name); //Hong
즉, 객체를 생성하면서 직접적으로 상속을 구현하는 것이다.
위 방법을 사용하면
new 연산자 없이 객체 생성,
프로토타입을 지정하며 객체 생성가능,
객체 리터럴에 의해 생성된 객체도 상속받을 수 있음
등의 장점을 가진다.
객체 리터럴 내부에서 __proto__ 에 의한 직접 상속
Object.create 메서드에 의한 직접 상속은 여러 장점이 있으나
두번째 인자로 프로퍼티를 정의하는게 굉장히 번거롭다.
ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용해 직접 상속을 구현할 수 있다.
const myProto = {x: 10};
const obj = {
y: 20,
__proto__: myProto
};
console.log(obj.x, obj.y); // 10 20
console.log(Object.getPrototypeOf(obj) === myProto); //true
정적 프로퍼티/메서드
정적 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지않아도 참조/호출 할 수 있는 메서드나 프로퍼티를 말한다.
function Person(name) {
this.name = name;
}
Person.staticProp = '정적 프로퍼티';
Person.staticMethod = function () {
console.log('정적 메소드');
}
Person.staticMethod();
me.staticMethod(); // TypeError
정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.
프로퍼티 존재 확인
in 연산자
key in object
const person = {
name: 'Hong',
address = 'hh'
};
console.log('name' in person); // true
만약 console.log('toString' in person); 구문을 실행한다면 true 를 반환할 것이다.
toString 은 Object.prototype 의 메서드이기 때문에 true 를 반환한다.
Object.prototype.hasOwnProperty 메서드
console.log(person.hasOwnProperty('name')); //true
console.log(person.hasOwnProperty('toString')); //false
in 과의 차이를 확인할 수 있다.
프로퍼티 열거
for ( 변수선언문 in 객체) {}
for ... in 문은 순회 대상 객체의 프로퍼티 뿐만아니라 상속받은 프로토타입의 프로퍼티까지 열거한다.
하지만 toString과 같은 Object.prototype 프로퍼티는 열거되지 않는데,
그 이유는 toString 메서드가 열거할 수 없도록 정의되었기 때문이다.
만약 상속받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하고 싶다면
Object.prototype.hasOwnProperty 메서드를 이용해 객체 자신의 프로퍼티인지 확인해야한다.
const person = {
name: 'Hong',
address: 'ss',
__proto__: {age: 20}
};
for (const key in person) {
if (!person.hasOwnProperty(key)) continue;
console.log(key + ': ' + person[key]);
}
// name: Hong
// address: ss
유의해야할 점은, for ... in 문은 순서를 보장하지 않는다는 것이다.
객체 자신의 고유 프로퍼티만 열거하기 위해서는 Object.keys/values/entries 메서드를 사용하길 권장한다.
Object.keys 메서드는 객체 자신의 프로퍼티 키를 배열로 반환하고,
Object.values 메서드는 객체 자신의 프로퍼티 값을 배열로 반환한다.
Object.entries 메서드는 객체 자신의 프로퍼티 키와 값의 쌍 배열을 배열에 담아 반환한다.
'Archive > Develop' 카테고리의 다른 글
[ 모던 자바스크립트 스터디 ] 클로저(closure) (0) | 2022.10.14 |
---|---|
[ 모던 자바스크립트 스터디 ] 실행 컨텍스트 (1) | 2022.10.12 |
[ 모던 자바스크립트 스터디 ] 프로토타입 - 1 (0) | 2022.10.02 |
[ 모던 자바스크립트 스터디 ] strict mode (0) | 2022.10.02 |
[ 모던 자바스크립트 스터디 ] 왜 __proto__ 접근자 프로퍼티를 통해 접근하는가? (0) | 2022.10.02 |