본문 바로가기
Front-End/JavaScript

19장 프로토타입[19.8절~19.14절] (모던 자바스크립트 Deep Dive)

by kk님 2023. 4. 13.

모던 자바스크립트 Deep Dive 글 목록(스터디)
https://hello-kk.tistory.com/780

 

지난 19.7 까지가 복잡했고, 이번 내용은 지난 내용을 잘 이해하고 있다면 오히려 쉽게 읽을 수 있다.

 

 

instanceof 연산자: 객체(의 프로토타입 체인) instanceof 생성자함수(의 prototype에 바인딩 된 객체)

in 연산자: 프로퍼티 in 객체(의프로토타입 체인상에 있는 모든 프로퍼티를 확인)

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

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가한다면, 프로토타입의 프로퍼티에 덮어써지는게 아니라, 인스턴트 프로퍼티로 추가된다.

=> 섀도잉: 상속 관계에 의해 프로퍼티가 가려지는 현상.

 

오버라이딩: 재정의

오버로딩: 함수명은 같지만 매개변수의 개수, 타입이 다른 메서드. 자바스크립트는 오버로딩을 지원하지 않지만, arguments 객체를 이용해 구현 가능.

 

이렇게 오버라이딩으로 프로토타입 프로퍼티가 섀도잉된 경우, 인스턴스의 프로퍼티를 삭제하면 다시 프로토타입의 프로퍼티를 사용할 수 있다.

delete 생성자.prototype.메서드;// (1)
delete 인스턴스.메서드;// (2)

19.9 프로토타입 교체

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

292 왜 즉시 실행 함수를 썼지?

 

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

 

차이: Person.prototype를 직접 가르키는지 아닌지

 

19.10 instanceof 연산자

객체(의 프로토타입 체인) instanceof 생성자함수(의 prototype에 바인딩 된 객체)

function Person(name){
    this.name=name;
}
const me = new Person('k')
const parent = {};

//me
//Person {name: 'k'}
Object.setPrototypeOf(me,parent)
//Person {name: 'k'}
//me
//Person {name: 'k'}
me.__proto__
{}

=> me 객체의 프로퍼티가 {} 로 교체되진 않는다. 프로토타입만 교체됨..

298 페이지

299 페이지

19.11 직접 상속

Object.create( 생성할 객체의 프로토타입으로 지정할 객체, 생성할 객체의 프로퍼티를 갖는 객체 )

1. null

=> Object.create( null )

 (1) null: 프로토타입이 null. 프로토타입 체인의 종점에 위치함

 (2) Object.prototype 를 상속받지 못한다

2. 객체(생성자 X): const myProto = { x : 10 }

=> Object.create( Person )

 (1) 프로토타입 체인

      obj  → myProto → myProto.prototype → Object.prototype null

3. 생성자.prototype

 (1) 프로토타입 체인

       Person → Person.prototype → Object.prototype  null

 

다시 함수 매개변수를 살펴보면,

Object.create( 생성할 객체의 프로토타입으로 지정할 객체, 생성할 객체의 프로퍼티를 갖는 객체 )

1. 프로토타입을 지정하면서 객체 생성

2. new 연산자가 없이도 객체 생성이 가능=>

new 연산자를 사용하지 않아도 객체를 생성할 수 있는 것은 코드를 더욱 간결하게 만들어줍니다. 즉, 코드의 가독성과 유지보수성을 높여줍니다. 또한 new 연산자를 사용하면 생성자 함수가 불필요하게 만들어지기 때문에, 메모리 사용량이 더 커지고 성능에 악영향을 미칠 수 있습니다. 따라서, new 연산자를 사용하지 않아도 객체를 생성할 수 있는 Object.create 메서드는 메모리와 성능 측면에서도 유리할 수 있습니다.

3. 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다.=>

객체 리터럴로 생성된 객체가 기본적으로 상속 기능을 제공하지 않기 때문

 

Object.create 메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있기 때문에, Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않는다.

=>? 체인의 종점에 위치하는 객체를 생성하는 것이 중요한가? 왜 프로토타입이 null인 객체를 생성하는게 이유일까..

 

아니면, 종점에 위치하는 객체 도 생성할 수 있기 때문인가?

아! 책에는,

프로토타입이 null이어서 프로토타입이 종점에 위치하는 객체는 빌트인 메서드를 사용할 수 없다.

 

만약, hasOwnProperty 메서드를 사용하고 싶다면, (아마도 바인딩을 해서 써야 하는 것 같다)

Object.prototype.hasOwnProperty.call() 을 사용한다.

 

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

const myProto = { x : 10 };

const obj = {
	y: 20,
    __proto__:myProto,
}

//obj
//{y: 20}
//obj.x
//10
//obj.y
//20

obj.z=12

//obj
//{y: 20, z: 12}

const newProto = {a: 30}
Object.setPrototypeOf(obj, newProto)

//obj
//{y: 20, z: 12}
//obj.a
//30

이렇게 객체 리터럴로 생성하더라도, __proto__프로퍼티를 통해 프로토타입을 지정해줄 수 있다. 이 때, x도 같이 상속된다.

상속된 프로퍼티를 사용하는게 자연스러워서, 궁금했던 부분

프로토타입이 교체되면, 이전 프로토타입에 있던 프로퍼티는 그대로 쓸 수 있나?

너무 당연한 결과였지만 !

 

19.12 정적 프로퍼티/메서드

인스턴스는 정적 프로퍼티/메서드를 호출할 수 없다.

=> 생성자 함수의 프로퍼티로 귀속되어, 생성자.prototype 프로퍼티에 속하지 않는다.

따라서, 프로토타입 체인상에 존재하지 않기 때문에 인스턴수가 호출할 수 없는 것

인스턴스의 프로토타입은 생성자.prototype이다. 생성자가 아니다! 따라서, 생성자.prototype이 프로토타입 체인상에 존재하는 것

차이점은, 생성자 함수 메서드인가? 프로토타입 메서드인가?

 

19-57예제랑 위에 설명이랑 매치하려고 했는데, 어렵게 생각했다... 예제는, 

obj.create 가 불가능하다는 것, Object.create 만 가능하다는 것. => 정적 메서드

하지만

obj.hasOwnProperty는 가능하다는 것을 설명한 것 같다. => 프로토타입 메서드

const obj = Object.create({name:'k'})

//obj.__proto__
//{name: 'k'}

//obj.__proto__.__proto__
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

정적 메서드는,

생성자함수.메서드 = function () {} 으로 할당할 수 있다.

인스턴스를 생성하지 않은 상태에서 정적 메서드 호출이 가능하다.

 

19.13 프로퍼티 존재 확인

19.13.1 in 연산자

프로퍼티 in 해당 객체일 때, 프로토타입 체인상에 있는 모든 프로퍼티를 확인한다. (ES6의 Reflect.has 도 동일)

19.13.2 Object.prototype.hasOwnProperty 메서드

객체 고유의 프로퍼티인 경우에만 true 반환. 상속받은 프로퍼티라면 false 반환. 

섀도잉 (오버라이딩)되어도 마찬가지로 true 반환

메서드 이름도 hasOwnProperty ~

const Person = {name:'k'}
const obj = Object.create(Person)
obj.name = 'kk'
obj.hasOwnProperty('name')
//true

19.14 프로퍼티 열거

19.14.1 for ... in 문 (key)

객체의 모든 프로퍼티를 순회하며 열거하는 방식.

in과 같이(물론, in은 프로토타입 체인상에 존재하는지 여부를 확인하지만) for ... in은 모든 프로퍼티 'key'를 열거한다. 상속받은 프로퍼티 key도 열거한다. 하지만!

[[Enumerable]] 이 false라면, 열거하지 않는다.

어디서 봤더라?

19.11.1에서, Object.create로 객체를 생성할 때 프로퍼티까지 같이 넣어주는 경우에서 봤다. 이때는 그러려니 하고 넘어갔지만, 열거 여부를 true/false할 수 있는 속성이다.

예제를 잘 보면 객체를 생성할 때 프로퍼티를 같이 생성해주는데, 해당 프로퍼티에 대한 정보도 같이 제공하고 있다.

//예제 19-51 

...

obj = Object.create(Object.prototype, {
	x: { value:1, writable: true, enumerable: true, configurable:true }
}

...

그런데, 309페이지의 예제 19-66을 보면,

Object.getOwnPropertyDescriptor 메서드는 객체의 프로퍼티 어트리뷰트에 대한 정보를 제공하는 객체를 반환한다.

위에서 { value : ~  ... } 에 대한 정보!

객체의 프로퍼티 어트리뷰트(attribute)는 해당 프로퍼티의 동작 방식과 제한 사항을 정의하는 속성(property)입니다. 예를 들어, 프로퍼티의 값(value)이나 데이터 타입(type), 열거 가능 여부(enumerable), 쓰기 가능 여부(writable), 속성의 엄격 모드 여부(strict mode) 등이 프로퍼티 어트리뷰트에 해당합니다.

정리하자면, for ... in 문은 객체의 프로퍼티를 순회하는데, 프로토타입 체인상에 위치한 모든 프로퍼티도 순회한다. 다만, [[Enumerable]] 속성이 true 인 경우에만!

const Person = {name:'kk', age:00}
const obj = Object.create(Person)

obj.like = 'JavaScript'

for ( const key in obj){
    console.log(key)
}

//like
//name
//age

정리하기

in : 해당 프로퍼티가 인스턴스 객체의 프로토타입 체인 상에 존재하는지 확인

for ... in : 해당 객체의 모든 프로퍼티 key를 열거(프로토타입 프로퍼티도). 단, 프로토타입 체인 상에서 프로퍼티라면 [[Enumerable]] true인 경우만

=> key가 심벌이면 열거하지 않는다. key를 열거하는 순서를 보장하지 않는다(정렬을 실시)

Object.prototype.hasOwnProperty: 인스턴스 자신에게 포함된 프로퍼티인지 확인

 

★배열 특이점★

배열도 객체이기에 프로퍼티를 할당할 수 있다!

const arr = [1,2,3]
arr.x = 100

1. for ... in

   1 2 3 100 => 모든 프로퍼티 출력

2. arr.length

   3 => 배열 요소의 개수

3. forEach

   1 2 3 => 프로퍼티는 제외. 요소만 출력

4. for ... of

   1 2 3 => 값을 할당

 

19.14.2 Object.keys/values/entries 메서드

자신의 고유 프로퍼티만 열거하는 방법

(1) Object.keys: 자신의 프로퍼티 중 [[Enumerable]]한 프로퍼티의 key만 배열로 열거

(2) Object.values: 자신의 프로퍼티 중 [[Enumerable]]한 프로퍼티의 value만 배열로 열거

(3) Object.entries:자신의 프로퍼티 중 [[Enumerable]]한 프로퍼티의 key value쌍을 배열로 열거 

[["name", "kk"], ["age",00]]