개념 정리/모던 자바스크립트 딥다이브

모던 자바스크립트 : 프로토타입 2

매진2 2023. 11. 9. 15:28
728x90

7. 프로토타입 체인

a. Person 생성자 함수에 의해 생성된 me 객체는 Object.prototype의 메서드인 hasOwnProperty 호출 가능

  • me 객체가 Person.prototype 뿐만 아니라 Object.prototype도 상속 받았다는 것을 의미
  • me 객체의 프로토타입은 Person.prototype 
  • Person.prototype의 프로토타입은 Object.prototype

b. 프로토타입 체인

  • 프로토타입 체인 : 자바스크립트가 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는 것
  • 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즘

c. me.hasOwnProperty('name')과 같이 메서드 호출 시 자바스크립트 엔진은 다음 과정을 통해 메서드 검색, 프로퍼티 참조

  1. hasOwnProperty 메서드를 호출한 me 객체에서 hasOwnProperty 메서드 검색 
    • me 객체에는 hasOwnProperty 메서드가 없으므로 프로토타입 체인을 따라 [[Prototype]] 내부 슬롯에 바인딩되어 있는 프로토타입으로 이동해 hasOwnProperty 메서드 검색
  2. Person.prototype 에도 hasOwnProperty 메서드가 없으므로 프로토타입 체인을 따라 [[Prototype]] 내부 슬롯에 바인딩되어 있는 프로토타입으로 이동해 hasOwnProperty 메서드 검색
  3. Object.prototype 에는 hasOwnProperty 메서드 존재
    • 자바스크립트 엔진은 Object.prototype.hasOwnProperty 메서드 호출 
    • 이때 Object.prototype.hasOwnProperty 메서드의 this에는 me 객체가 바인딩됨

d. call 메서드

  • call 메서드는 this로 사용할 객체를 전달하면서 함수 호출

e. Object.prototype

  • 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype
  • 모든 객체는 Object.prototype를 상속 받음
  • Object.prototype을 프로토타입 체인의 종점
  • Object.prototype의 프로토타입, 즉 [[Prototype]] 내부슬롯의 값은 null
  • 프로토타입 체인의 종점인 Object.prototype에서도 프로퍼티를 검색할 수 없는 경우 undefined 반환하며 에러는 발생하지 않음

f. 프로토타입 체인과 스코프 체인

  • 자바스크립트 엔진은 프로토타입 체인을 따라 프로퍼티/ 메서드 검색
    • 자바스크립트 엔진은 객체 간의 상속 관계로 이뤄진 프로토타입의 계층적인 구조에서 객체의 프로퍼티 검색
    • 프로토타입 체인은 상속과 프로퍼티 검색을 위한 매커니즘
  • 프로퍼티가 아닌 식별자는 스코프 체인에서 검색
    • 자바스크립트 엔진은 함수의 중첩 관계로 이뤄진 스코프의 계층적 구조에서 식별자 검색
    • 스코프 체인은 식별자 검색을 위한 매커니즘
  • 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티 검색에 사용
    • 스코프 체인에서 me 식별자 검색 -> me 식별자는 전역 스코프에서 검색 -> me 식별자 검색 후 me 객체의 프로토타입 체인에서 hasOwnProperty 메서드 검색

 

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

a. 인스턴스

  • 생성자 함수로 객체(인스턴스) 생성 후 인스턴스에 메서드 추가
  • 프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티
  • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색해 프로토타입 프로퍼티를 덮어쓰는 것이 아니라 인스턴스를 프로퍼티로 추가
  • 인스턴스 메서드 sayHello는 프로토타입 메서드 sayHello를 오버라이딩 했고 프로토타입 메서드 sayHello가 가려짐

b. 섀도잉, 오버라이딩, 오버로딩

  • 프로퍼티 섀도잉 : 상속관계에 의해 프로퍼티가 가려지는 현상
  • 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
  • 오버로딩 : 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식
  • 자바스크립트는 오버로딩을 지원하지 않지만 arguments 객체를 사용해 구현 가능

c. 프로토타입 프로퍼티 변경 또는 삭제

  • 프로토타입 메서드가 아닌 인스턴스 메서드 sayHello가 삭제됨
  • 하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능
  • 하위 객체를 통해 프로토타입에 get 액세스는 허용되나 set 액세스는 허용되지 않음
  • 프로토타입 프로퍼티를 변경 또는 삭제를 하려면 프로토타입에 직접 접근해야함

 

 

9. 프로토타입의 교체

프로토타입은 임의의 다른 객체로 변경 가능 = 부모객체인 프로토타입을 동적 변경 가능

이러한 특징을 활용해 객체 간의 상속 관계를 동적 변경 가능

프로토타입은 생성자 함수 또는 인스턴스에 의해 교체 가능

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

a-1. Person.prototype에 객체 리터럴을 할당

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

 

a-2. 프로토타입을 교체

  • 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결 파괴됨
  • 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가해 프로토타입의 constructor 프로퍼티 되살림

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

b-1. __proto__접근자 프로퍼티와 Object.getPrototypeOf

  • 프로토타입은 생성자 함수의 prototype 프로퍼티뿐만 아니라 인스턴스의 __proto__ 접근자 프로퍼티 또는 Object.getPrototypeOf 메서드를 통해 접근 가능
  • 인스턴스의 __proto__접근자 프로퍼티( 또는 Object.getPrototypeOf )를 통해 프로토타입 교체 가능
  • 생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입 교체하는 것
  • __proto__ 접근자 프로퍼티를 통해 프로토타입을 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것

b-2. me 객체의 프로토타입을 parent 객체로 교체함

  • 프로토타입으로 교체한 객체는 constructor 프로퍼티가 없으므로 constructor 프로퍼티와 생성자 함수 간의 연결 파괴됨
  • => 프로토타입의 constructor 프로퍼티로 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나옴

b-3. 생성자 함수에 의한 프로토타입 교체와 인스턴스에 의한 프로토타입 교체는 미묘한 차이가 있음

b-4. 프로토타입 직접 교체?

  • 프로토타입 교체를 통해 객체 간의 상속관계를 동적으로 변경하는것은 번거로움 => 직접교체 안하는게 좋음
  • 상속관계를 인위적으로 설정하려면 직접상속이 더 편리하고 안전함
  • 클래스를 사용하면 간편하고 직관적으로 상속 관계 구현 가능

 

 

10. instanceof 연산자

  • instanceof 연산자는 이항 연산자로서 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받음
  • 우변의 피연산자가 함수가 아닌 경우 타입에러 발생
  • 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true, 그렇지 않은 경우 false

a. instanceof 의 true / false

  • me 객체는 프로토타입이 교체되어 프로토타입과 생성자 함수간의 연결이 파괴되었지만 Person 생성자 함수에 의해 생성된 인스턴스
  • 그러나 me instanceof Person은 false
  • =>Person.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않기 때문
  • 프로토타입으로 교체한 parent 객체를 Person 생성자 함수의 prototype 프로퍼티에 바인딩하면 me instanceof Person은 true

b. instanceof의 작동 과정

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

 

 

11. 직접 상속

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

a-1. Object.create 메서드는 명시적으로 프로토타입을 지정하여 새로운 객체 생성

  • Object.create 메서드도 다른 객체 생성방식과 마찬가지로 추상 연산 OrdinaryObjectCreate 호출
  • Object.create 메서드의 첫번째 매개변수에는 생성할 객체의 프로토타입으로 지정할 객체 전달
  • 두번째 매개변수에는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체 전달
  • 이 객체의 형식은 Object.defineProperties 메서드의 두번째 인수와 동일하며 두번째 인수는 옵션이라 생략 가능

a-2. Object.create 메서드는 첫번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체 생성

  • 객체를 생성하면서 직접적으로 상속을 구현하는 것

a-3. Object.create메서드의 장점

  • new 연산자 없어도 객체 생성 가능
  • 프로토타입을 지정하면서 객체 생성 가능
  • 객체 리터럴에 의해 생성된 객체도 상속 가능

a-4. Object.prototype의 빌트인 메서드

  • Object.prototype의 빌트인 메서드인 Object.prototype.hasOwnProprety, Object.prototype.isPrototypeOf, Object.prototype.propertyIsEnumerable 등은 모든 객체의 프로토타입 체인의 종점 즉 Object.prototype의 메서드이므로 모든 객체가 상속 받아 호출 가능

a-5. ESLint에서는 Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않음

  • Object.create 메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성 할 수 있기 때문
  • 프로토타입 체인의 종점에 위치하는 객체는 Object.prototype의 빌트인 메서드를 사용할 수 없음
  • 에러 발생 위험을 없애기 위해 Object.prototype 빌트인 메서드는 간접 호출하는 것이 좋음

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

  • Object.prototype 메서드에 의한 직접 상속은 두번째 인자로 프로퍼티를 정의하는 것이 번거로움
  • 객체 생성 후 프로퍼티 추가하는 방법도 있는데 별로임
  • ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용해 직접 상속 구현

 

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

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

  • 정적 프로퍼티/메서드 : 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드
  • Person 생성자 함수는 객체이므로 자신의 프로퍼티/메서드 소유 가능
  • Person 생성자 함수 객체가 소유한 프로퍼티/메서드를 정적 프로퍼티/메서드
  • 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출 할 수 없음

 

b. 정적 프로퍼티/메서드의 접근

  • 생성자 함수가 생성한 인스턴스는 자신의 프로토타입 체인에 속한 객체의 프로퍼티/메서드에 접근 가능
    • 하지만 정적 프로퍼티/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티 메서드가 아니므로 인스턴스로 접근 할 수 없음
  • Object.create 메서드는 Object 생성자 함수의 정적 메서드고 Object.prototype.hasOwnProprety 메서드는 Object.prototype의 메서드임
    • Object.create 메서드는 인스턴스라 Object 생성자 함수가 생성한 객체로 호출 불가능
    • 하지만 Object.prototype.hasOwnProprety 메서드는 모든 객체의 프로토타입 체인의 종점, Object.prototype의 메서드이므로 모든 객체가 호출 가능

c. 인스턴스/프로토타입 메서드 내에서 this

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

d. 정적 프로퍼티/메서드와 프로토타입 프로퍼티/메서드를 구분하기 때문에 표기법만으로도 구별해야함

 

13. 프로퍼티 존재 확인

a. in 연산자

  • in 연산자는 객체 내에 특정 프로퍼티가 존재 여부 확인
  • in 연산자는 확인 대상 객체의 프로퍼티 뿐만 아니라 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인하므로 주의 필요
  • in 연산자 대신 ES6의 Reflect.has 메서드 사용 가능 => in 연산자와 동일하게 동작

 

b. Object.prototype.hasOwnProprety 메서드

  • Object.prototype.hasOwnProprety 메서드 사용하면 객체에 특정 프로퍼티 존재 여부 확인 가능
  • Object.prototype.hasOwnProprety 메서드는 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true 반환, 상속받은 프로토타입의 프로퍼티 키인 경우 false qksghks

 

14. 프로퍼티 열거

a. for...in 문

  • 객체의 모든 프로퍼티를 순회해 열거하려면 for...in 문 사용
  •  for...in 문은 객체의 프로퍼티 개수만큼 순회하며  for...in 문의 변수 선언문에서 선언한 변수에 프로퍼티 키 할당
  • 객체를 2번 순회하면서 프로퍼티 키를 key 변수에 할당 후 코드 블록 실행
  •  for...in 문은 in 연산자처럼 순회 대상 객체의 프로퍼티뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거

a-1. toString 메서드

  • toString 메서드는 열거할 수 없도록 정의되어 있는 프로퍼티
  • Object.prototype.toString 프로퍼티의 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false기 때문
  • 프로퍼티 어트리뷰트 [[Enumerable]]은 프로퍼티 열거 가능 여부를 boolean 값으로 나타냄

a-2. for...in 문의 특징

  • for...in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거함
    • for...in 문은 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않음
    • 상속받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasOwnProprety 메서드 사용해 객체 자신의 프로퍼티인지 확인해야함
  • for...in 문은 프로퍼티를 열거할 때 순서를 보장하지 않음
    • 대부분의 모던 브라우저는 순서를 보장하고 숫자인 프로퍼티 키는 정렬함
  • 배열에는  for...in 문보다 for, for...of, Array.prototype.forEach 사용 권장
    • 배열도 객체이기때문에 프로퍼티와 상속받은 프로퍼티가 포함될 수 잇음

b. Object.keys/values/entried 메서드

  • for...in 문은 객체 자신의 고유 프로퍼티뿐 아니라 상속받은 프로퍼티도 열거함
  • Object.prototype.hasOwnProprety 메서드 사용해 객체 자신의 프로퍼티인지 확인하는 추가 처리 필요
  • 객체 자신의 고유 프로퍼티만 열거하기 위해서는 for...in 문보다 Object.keys/values/entried 메서드 사용 권장

b-1. Object.keys

  • 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환

b-2. ES8 Object.values 

  • 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환

b-3. ES8 Object.entried

  • 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환

 

 

 

모던 자바스크립트 딥다이브 책을 정리한 내용입니다!!!!
728x90