[자바스크립트 딥다이브] 19장 프로토타입 (2)

Bor·2022년 1월 4일
0

JS딥다이브

목록 보기
16/24
post-thumbnail

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

  • 프로토타입이 소유한 프로퍼티(메서드 포함)를 프로토타입 프로퍼티
  • 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 부른다.

프로토타입 프포터이와 같은 이름의 프로퍼티를 인스턴스에 추가!

➡️ 프로토타입 체인을 따라 프로토타입을 검색하여 덮어 쓰는 것이 아니라 ➡️ 인스턴스 프로퍼티로 추가! ➡️ 이 때 인스턴스 메서드 sayHello는 프로토타입 메서드 sayHello를 오버라이딩한 것이며 sayHello는 가려진다. 이처럼 상속 관계에 의해 프로퍼티가 가려지는 현상을 property shadowing이라고 한다.

  • Overriding : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의 해서 사용하는 방식
  • Overloading : 함수 이름은 동일, but 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개 변수에 의해 메서드를 구별하여 호출하는 방식. 자바스크립트는 오버로딩을 지원하지 않지만 argument 객체를 사용해서 구현할 수는 있다.

삭제 -- 당연한 내용들!


당연히 프로토타입 메서드가 아닌 인스턴스 메서드 sayHello가 삭제된다.

이와 같이 하위 객체를 통해서 프로토타입의 프로퍼티 변경 또는 삭제하는 것은 불가능. 프로토타입 프로퍼티를 변경 or 삭제 ➡️ 체인으로 접근하는 게 아니라 프로토타입에 직접 접근해야함.

자식을 통해서 부모님을 수정할 수는 없다. 번지수는 직접 찾아가라!


19.9 프로토타입의 교체

  • 부모 객체인 프로토타입을 동적으로 변경할 수 있다.
  • 객체 간의 상속 관계를 동적으로 변경하며 프로토타입은 생성자 함수 또는 인스턴스에 의해 교체할 수 있다.

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


(1)에서 Prototype에 객체 리터럴로 할당하며 객체의 프로토타입을 객체 리터럴로 교체.

프로토타입으로 교체한 객체 리터럴에는 constructor 프로퍼티가 없다. 얘는 자바스크립트 엔진이 프로토타입을 생성할 때 암묵적으로 추가한 프로퍼티. 따라서 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나온다.

프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살린다.

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

프로토타입은 생서자 함수의 prototype 프로퍼티 뿐만 아니라 인스턴스의 접근자 프로퍼티를 통해서 접근할 수 있다. 따라서 인스턴스의 접근자 프로퍼티를 통해 프로토타입을 교체할 수 있다.

  • 생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것이며,
  • 접근자 프로토타입을 통해 프로토타입을 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것이다.

위처럼 프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 꽤나 번거롭다. 따라서 프로토타입은 직접 교체하지 말자! 상속 관계를 인위적으로 설정하려면 '직접상속'에서 살펴볼 직접 상속이 더 편리하고 안전하다. 또 ES6에서 도입된 클래스를 사용하면 간편하고 직관적으로 상속 관계를 구현할 수 있다.


19.10 instanceof 생성자

instanceof 연산자는 이항 연산자로서 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받는다. 만약 우변의 피연산자가 함수가 아닌 경우 TypeError가 발생한다.

객체 instanceof 생성자 함수 

우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가되고, 그렇지 않으면 false로 평가된다.

instanceof 연산자가 어떻게 동작하는지 이해하기 위해 프로토타입을 교체해보자

me 객체는 비록 프로토타입이 교체되어 프로토타입과 생성자 함수 간의 연결이 파괴되었지만 Person 생성자 함수에 의해 생성된 인스턴스임에는 틀림 없다. 그러나 me instanceof Person은 false로 평가된다. 이는 Person.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않기 때문. 따라서 프로토타입으로 교체한 parent 객체를 Person 생성자 함수의 prototype 프로퍼티에 바인딩하면 me instanceof Person은 true로 평가될 것이다.

이처럼 instanceof 연산자는 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아니라 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다. 따라서 생성자 함수에 의해 프로토타입이 교체되어 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴되어도 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결을 파괴되지 않으므로 instanceof는 아무런 영향을 받지 않는다.


19.11 직접 상속

19.11.1 Object.create에 의한 직접 상속

Object.create 메서드는 명시적으로 프로토타입을 지정해 새로운 객체를 생성.

  • 첫 번째 매개변수에는 생성할 객체의 프로토타입으로 지정할 객체를 전달.
  • 두 번째 매개변수에는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체를 전달(옵션)
// 프로토타입이 null인 객체를 생성한다. 생성된 객체는 프로토타입 체인의 종점에 위치한다 
// obj -> null 
let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); //true 
//Object.prototype을 상속받지 못한다 
//console.log(obj.toString()); // TypeError: obj.toString is not a function

// obj  -> Object.prototype -> null 
// obj = {} 과 동일하다 
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); //true

//obj -> Object.prototype -> null 
//obj = {x : 1}; 와 동일하다 
obj = Object.create(Object.prototype, {
    x : {value: 1, writable: true, enumerable: true, configurable: true}
});

// 위 코드는 아래와 동일하다
// obj = Object.create(Object.prototype); 
// obj.x = 1
console.log(obj.x) //1
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

const myProto = {x:10};
//임의의 객체를 직접 상속 받는다 
// obj -> myProto -> Object.prototype -> null 
obj = Object.create(myProto);
console.log(obj.x); //10
console.log(Object.getPrototypeOf(obj) === myProto); //true

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

//obj -> Person.prototype -> Object.prototype -> null 
//obj = new Person('boram')과 동일하다
obj = Object.create(Person.prototype);
obj.name = 'boram';
console.log(obj.name); // boram
console.log(Object.getPrototypeOf(obj) === Person.prototype);//true

이처럼 Object.create 메서드는 첫 번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체를 생성한다. 즉, 객체를 생성하면서 직접적으로 상속을 구현. 이 메서드의 장점은 다음과 같다.

  • new 연산자가 없이도 객체를 생성할 수 있다
  • 프로토타입을 지정하면서 객체를 생성할 수 있다
  • 객체 리터럴에 의해 생성된 객체도 상속 받을 수 있다.

그런데 ESLint 에서는 앞의 예제와 같이 Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않는다. 왜냐면 Object.create 메서드를 통해 프로토타입 체인 종점에 위피하는 객체를 생성할 수 있기에. 프로토타입 종점에 위치하는 객체는 Object.prototype 빌트인 메서드를 사용할 수 없다.

따라서 이 같은 에러를 발생시킬 위험을 없애기 위해 Object.prototype의 빌트인 메서드는 다음과 같이 직접적으로 호출하는 것이 좋다.

19.11.2 객체 리터럴 내부에서 __proto__에의한 직접상속

Object.create 장점도 있지만 두 번째 인자로 프로퍼티를 정의하는 것은 번거롭다. 일단 객체를 생성한 이후에 프로퍼티를 추가하는 방법도 있으나 이 또한 깔끔한 방법은 아니다. ES6에서는 객체 리터럴 내부에서 __proto__접근자 프로퍼티를 사용해서 직접 상속을 구현할 수 있다.

부모를 직접 지정할 수 있다!


19.12 정적 프로퍼티&메서드

정적 프로퍼티 or 메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조 or 호출할 수 있는 프로퍼티 or 메서드를 말한다.

  • Person 생성자 함수는 객체이므로 자신의 프로퍼티 or 메서들르 소유
  • Person 생성자 함수 객체가 소유한 프로퍼티 or 메서드를 정적 프로퍼티 or 메서드라 한다
  • 정적 프로퍼티 or 메서드는 생성자 함수가 생성한 인스턴스를 참조 or 호출할 수 없다.

생성자 함수가 생성한 인스턴스는 자신의 프로토타입 체인에 속한 객체의 프로퍼티/메서드에 접근할 수 있다. 하지만 정적 프로퍼티/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니므로 인스턴스로 접근할 수 없다.

  • 앞에서 살펴본 Object.create 메서드는 Object 생성자 함수의 정적 메서드
  • Object.prototype.hasOwnProperty 메서드는 Object.prototype의 메서드다
  • 따라서 Object.create 메서드는 인스턴스, 즉 Object 생성자 함수가 생성한 객체로 호출할 수 없다.
  • 하지만 Object.prototype.hasOwnProperty 메서드는 모든 객체의 프로토타입 체인의 종점, 즉 Object.prototype의 메서드이므로 모든 객체가 호출할 수 있다

  • 만약 인스턴스/프로토타입 메서드 내에서 this를 사용하지 않으면 그 메서드는 정적 메서드로 변경할 수 있음
  • 인스턴스가 호출한 인스턴스/프로토타입 메서드 내에서 this는 인스턴스를 가리킴
  • 메서드 내에서 인스턴스를 참조할 필요가 없다면 정적 메서드로 변경하여도 동작
  • 프로토타입 메서드를 호출하려면 인스턴스를 생성해야 하지만 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있음

MDN과 같은 문서를 보면 다음과 같이 정적 프로퍼티/메서드와 프로토타입 프로토퍼티/메서드를 구분해서 소개!


19.13 프로퍼티 존재 확인

19.13.1 in 연산자

  • in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속 받은 모든 프로토타입의 프로퍼티를 확인하므로 주의가 필요
  • person 객체에는 toString이라는 프로퍼티가 없지만 다음 코드의 결과는 true

이는 in 연산자 person 객체가 속한 프로토타입 체인 상에 존재하는 모든 프로토타입 toString 프로퍼티를 검색했기 때문에. 젤 위에 있는 Object.prototype의 메서드다.

in 연산자 대신 ES6에서 도입된 Reflect.has 메서드를 사용할 수도 in과 동일하게 동작한다.

19.13.2 Object.prototype.hasOwnProperty 메서드

얘를 사용해도 프로퍼티가 존재하는지 확인할 수 있다.

이름에서 알 수 있듯 임수로 전달 받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고 상속 받은 프로토타입의 프로퍼티 키인 경우 false를 반환한다.

자기 갖고 있는지 여부로 T/F를 판단. 부모까지 가지 않는다.
유전자 검사 종류가 두 가지. in은 조상까지 모두, hasOwn은 내 유전자만 검사!


19.14 프로퍼티 열거

19.14.1 for ... in 문

객체의 모든 프로퍼티를 순회하며 enumeration 하려면 for...in 문을 사용한다.

for (변수 선언문 in 객체) {...}

for...in

프로퍼티 개수만큼 순회하며 for...in 변수 선언문에서 선언한 변수에 프로퍼티 키를 할당.

  • 프로퍼티 키를 key 변수에 할당한 후 코드블록을 실행
  • in 연산자처럼 (안에 들어 있으니) 상속받은 프로토타입의 프로퍼티까지 열거한다
  • 그러나 toString과 같은 Object.prototype 프로퍼티가 열거되지 않는다

이는 toString 메서드가 열거할 수 없도록 정의되어 있는 프로퍼티. 다시 말해, Object.prototype.string 열거 가능 여부를 나타내는 프로퍼티의 어트리뷰트 [[Enumerable]]의 값이 false이기 때문이다.

따라서 for...in 문에 대해서 좀 더 정확하게 표현하면
for ... in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumeralbe]]의 값이 true인 프로퍼티를 순회하며 열거함

  • for ... in 은 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않는다
  • 상속 받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasOwnproperty 메서드를 사용해 객체 자신의 프로퍼티인지 확인해야
  • for ... in 열거할 때 순서를 보장하지 않는다. 하지만 대부분의 브라우저는 순서를 보장하고 숫자인 프로퍼티 키에 대해서는 정렬을 실시
  • 배열에는 for ... in을 사용하지 말고 for문이나 for...of문 또는 forEach를 사용할 것. 배열도 객체이므로 상속 받은 프로퍼티가 포함될 수 있다.

19.14.2 Object.keys/values/entries 메서드

객체 자신의 고유한 프로퍼티만 나열하기 위해서는 for in 문 보다 Object.keys/values/entries 메서드를 사용하는 것을 권장하다.

  • Object.keys 는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환 (name, address)
  • Object.values 는 열거 가능한 프로퍼티 값을 배열로 반환(boram, seoul)
  • ES8에서 도입된 Object.entries 메서드는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환([[name","boram"], ["address","seoul"]])

0개의 댓글