[240326] 19장 프로토타입 (3)

19장 프로토타입 (3)

7. 프로토타입 체인

  • 자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라 한다.
  • 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype
    • 체인의 종점 (end of prototype chain)
    • [[Prototype]] 내부 슬롯의 값은 null
    • Object.prototype 에서도 검색할 수 없으면 undefined 반환
  • 프로토타입 체인 (상속과 프로퍼티 검색을 위한 메커니즘)
  • 스코프 체인 (식별자 검색을 위한 메커니즘)
  • 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용

8. 오버라이딩과 프로퍼티 섀도잉

const Person = (function () {
	// 생성자 함수
	function Person(name) {
		this.name = name;
	}
	
	//프로토타입 메서드
	Person.prototype.sayHello = function () {
		console.log(`Hi ! My name is ${this.name}`);
	};
	
	// 생성자 함수를 반환
	return Person;
}());

const me = new Person('Lee');

// 인스턴스 메서드
me.sayHello = function () {
	console.log(`Hey! My name is ${this.name}`);
};

// 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.
me.sayHello(); // Hey! My name is Lee

프로토타입이 소유한 프로퍼티 : 프로토타입 프로퍼티

인스턴스가 소유한 프로퍼티 : 인스턴스 프로퍼티

  • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면, 프로토타입 프로퍼티를 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다.
  • 프로퍼티 섀도잉 : 상속 관계에 의해 프로퍼티가 가려지는 현상
  • 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
  • 오버로딩: JS 는 오버로딩을 지원하지 않지만 arguments 객체를 사용하여 구현할 수 있다.
  • 삭제도 마찬가지로 인스턴스 메서드가 삭제된다.
    • 프로토타입 체인을 통해 프로토타입 메서드는 삭제되지 않는다.
    • 하위 객체를 통한 set 액세스는 허용하지 않음
    • 변경이나 삭제하려면 프로토타입 체인으로 접근하면 안되고 직접 접근해야함

9. 프로토타입의 교체

  • 프로토타입은 임의의 다른 객체로 변경 가능
  • 이러한 특징을 활용하여 객체 간의 상속 관계를 동적으로 변경 가능
  • 생성자 함수 , 인스턴스에 의해 교체 가능

1. 생성자 함수에 의한 프로토타입 교체

const Person = (function () {
	// 생성자 함수
	function Person(name) {
		this.name = name;
	}
	
	// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입 교체
	Person.prototype = {
		sayHello() {
			console.log('Hi !');
		};
	};
	
	// 생성자 함수를 반환
	return Person;
}());

const me = new Person('Lee');
// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
console.log(me.constructor === Person); // false

// 프로토타입 체인을 따라 Object.prototype 의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); // true

2. 인스턴스에 의한 프로토타입의 교체

  • 생성자 함수의 prototype 프로퍼티에 다른 임의이 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것
  • proto 접근자 프로퍼티를 통해 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것이다.
function Person(name) {
	this.name = name;
}

const me = new Person('Lee');

const parent = {
	sayHello() {
		console.log(`Hi! My name is ${this.name}`);
	}
};

// me 객체의 프로토타입을 parent 객체로 교체 
Object.setPrototypeOf(me,parent);
// me.__proto__ = parent; 동일하다.
me.sayHello(); // Hi! My name is Lee
// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
console.log(me.constructor === Person); // false

// 프로토타입 체인을 따라 Object.prototype 의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); // true
  • ES6 에서 도입된 클래스 사용하면 간편하고 직관적으로 상속 관계 구현 가능

10. instanceof 연산자

  • 객체 instanceof 생성자 함수
  • 이항 연산자
    • 좌변 : 객체를 가리키는 식별자
    • 우변: 생성자 함수를 가리키는 식별자
  • 우변의 피연산자가 함수가 TypeError 발생
  • 우변의 생성자 함수의 prototype 에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가, 그렇지 않으면 false
function Person(name) {
	this.name = name;
}

const me = new Person('Lee');

console.log(me instanceof Person); // true
console.log(me instanceof Object); // true
  • instanceof 연산자 함수로 표현하면 아래와 같다.
function isInstanceof(instance, constructor) {
	const prototype = Object.getPrototypeOf(instance);
	
	// 재귀 탈출 조건
	// prototype null 이면 프로토체인 종점
	if(prototype === null) return false;
	return prototype === constructor.prototype || isInstanceof(prototype, constructor);
}

11. 직접 상속

1. Object.create 에 의한 직접 상속

  • 명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다.
  • 추상 연산 호출
  • 첫 번째 매개변수 : 생성할 객체의 프로토타입으로 지정할 객체
  • 두 번째 매개변수 : 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체 (옵션)
const obj = Object.create(Object.prototype);
console.log(Object.getPrototypeof(obj)===Object.prototype);

// obj2 = { x:1 }; 와 동일
const obj2 = Object.create(Object.prototype, {
 x: { value:1, writable: true, enumerable: true, configurable: true }
});

직접 상속 장점

  • new 연산자 없이도 객체 생성
  • 프로토타입을 지정하면서 객체 생성 가능
  • 객체 리터럴에 의해 생성된 객체도 상속 가능
const obj = Object.create(null);
obj.a = 1;

console.log(Object.getPrototypeof(obj) === null); // true

// obj 는 Object.prototype 의 빌트인 메서드를 사용할 수 없다.
console.log(obj.hasOwnProperty('a')); 
// TypeError : obj.hasOwnProperty is not a function
  • Object.prototype 의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않음
    • 체인의 종점에 위치하는 객체는 빌트인 메서드를 사용할 수 없다.
    • 따라서 Object.prototype.hasOwnProperty.call(obj, ‘a’) 처럼 간접적으로 호출하는 것이 좋다.

2. 객체 리터럴 내부에서 proto 에 의한 직접 상속

  • Object.create 는 두 번째 인자 프로퍼티 정의하는 것은 번거롭다.
  • ES6 에서는 객체 리터럴 내부에서 proto 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수 있다.
const myProto = { x: 10 }; 
const obj = {
	y: 20,
	// obj -> myProto -> Object.prototype -> null
	__proto__: myProto
};

/* 위 코드는 아래와 동일하다.
const obj = Object.create(myProto, {
 y: { value:20, writable: true, enumerable: true, configurable: true }
});
*/

12. 정적 프로퍼티/메서드

  • 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말한다.
function Person(name) {
	this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHello = function () {
	console.log(`${this.name}`);
};

// 정적 프로퍼티
Person.staticProp = 'static prop';

// 정적 메서드
Person.staticMethod = function () {
	console.log('staticMethod');
};

const me = new Person('Lee');

Person.staticMethod(); // staticMethod

// 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.
// 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 한다.
me.staticMethod(); // TypeError: me.staticMethod is not a function
// Object.create 는 정적 메서드다.
const obj = Object.create({ name: 'Lee' });

// Object.prototype.hasOwnProperty는 프로토타입 메서드다
obj.hasOwnProperty('name'); // false
  • MDN 문서에도 정적과 프로토타입 구분해서 소개하고 있음
  • prototype을 # 로 표기하는 경우도 있다.

13. 프로퍼티 존재 확인

1. in 연산자

  • 객체 내에 특정 프로퍼티가 존재하는지 여부 확인
  • key in object
  • 프로퍼티 뿐 아니라 확인 대상 객체가 받은 모든 프로토타입의 프로퍼티를 확인한다.

      console.log('toString' in person); // true
    
  • Reflect.has 메서드도 동일하게 동작 (ES6 도입)

      Reflect.has(person, 'name');
    

2. Object.prototype.hasOwnProperty 메서드

  • 프로퍼티타입의 프로퍼티키인 경우 false 반환
  • 인스로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true 반환

14. 프로퍼티 열거

1. for …in 문

  • for (변수선언문 in 객체) { … }
  • 객체의 모든 프로퍼티를 순회하며 열거
  • 상속받은 프로토타입의 프로퍼티까지 열거
  • [[Enumerable]] 이 true 이면 열거
  • 키가 심벌인 프로퍼티는 열거하지 않는다.
  • 상속 받은 프로퍼티를 제외하려면 hasOwnProperty 메서드를 사용해 객체 자신의 프로퍼티인지 확인 해야함
  • 열거할 때 순서를 보장하지 않는다.
    • 대부분 모던 브라우저는 숫자(사실은 문자열)인 프로퍼티 키에 대해서는 정렬을 실시한다.
  • 배열에는 for …in 문 말고 일반적인 for 문이나 for of , Array.prototype.forEach 메서드 권장
    • 해당 사항에 관한 자세한건 뒷 장

2. Object.keys/ values / entries 메서드

  • 객체 자신의 고유 프로퍼티만 열거
  • Object.keys : 열거 가능한 프로퍼티 키를 배열로 반환
  • Object.values : 열거 가능한 프로퍼티 값를 배열로 반환 (ES8 도입)
  • Object.entries : 열거 가능한 프로퍼티 키와 값의 쌍의 배열로 반환 (ES8 도입)

Categories:

Updated:

Leave a comment