19장 프로토타입 (2)

seo0·2023년 3월 20일
0

JavaScript

목록 보기
19/26
post-thumbnail

프로토타입 체인

프로토타입 체인이란 자바스크립트가 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는 것을 의미한다.

프로토타입의 프로토타입은 언제나 Object.prototype 이다. 또한 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype이며 Object.prototype을 프로로토타입 체인의 종점이라고 한다. Object.prototype, 즉 [[Prototype]]내부 슬롯의 값은 null이다.

프로토타입의 종점에서도 프로퍼티를 검색할 수 없는 경우에는 undefined를 반환하며 에러는 발생하지 않는다.

function Person(name) {
  this.name = name;
}

Person.prototype.sayHi = function () {
  console.log(`Hi! I'm ${this.name}`);
};

const me = new Person('kim');

console.log(Object.getPrototypeOf(me) === Person.prototype);  //true
console.log(me.hasOwnProperty('name'));  //true
console.log(me.temp);  //undefined

/*
me의 프로토타입은 Person.protytpe이며 
프로토타입의 상속에 의해 Obeject.prototype에 있는 
hasOwnproperty를 자유롭게 사용 가능하다.
*/

프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속과 프로퍼티 검색을 위한 메커니즘이다. 그에 반해 프로퍼티가 아닌 식별자는 스코프체인에서 검색을 하며 스코프 체인은 식별자 검색을 위한 메커니즘이다.

스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용된다.




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

프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 한다.

이때 프로토타입의 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입의 프로퍼티를 재정의하는 것이 아니라 인스턴스 프로퍼티로 추가한다.

이렇게 같은 이름의 메서드라 오버라이딩 되었을 때 상속관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라고 한다.

const Person = (function() {
  //생성자 함수
  function Person(name) {
    this.name = name;
  }
  
  //프로토타입 메서드
  Person.prototype.sayHi = function () {
    console.log(`Hi! I'm ${this.name}`);
  };

  //생성자 함수 반환
  return Person;
}());
 
const me = new Person('kim');

//인스턴스 메서드
me.sayHi = function () {
  console.log(`hey! what's up?`);
};

me.sayHi();  //hey! what's up?

하위 객체를 통해 프로토타입에 get 엑세스는 허용되지만 set 엑세스는 허용되지 않는다. 프로토타입 프로퍼티를 변경 또는 삭제하려면 하위 객체를 통해 프로토타입 체인으로 접근하는 것이 아니라 프로토타입에 직접 접근해야한다.




프로토타입의 교체

프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 꽤나 번거롭기때문에 프로토타입은 직접 교체하지 않는 것이 좋다.




instanceof 연산자

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

function Person(name) {
  this.name = name;
}

const me = new Person('kim');

//Person.prototype은 me 객체의 프로토타입 체인상에 존재하기 때문에 true로 평가된다.
console.log(me instanceof Person);  //true
//Object.prototype은 me 객체의 프로토타입 체인상에 존재하기 때문에 true로 평가된다.
console.log(me instanceof Object);  //true

instanceof 연산자는 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아닌 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인하는 연산자이다.




정적 프로퍼티와 정적 메서드

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

이런 정적 프로퍼티 / 메서드는 생성자 함수가 생성한 인스턴스로 참조, 호출이 불가능하다.

function Person3(name) {
  this.name = name;
}

Person3.prototype.sayHi = function () {
  console.log(`Hi! I'm ${this.name}`);
};

//정적 프로퍼티
Person3.staticProp = '정적 프로퍼티';
//정적 메서드
Person3.staticMethod = function () {
  console.log('정적 메서드');
};

const me2 = new Person3('kim');

Person3.staticMethod();  //정적 메서드
console.log(Person3.staticProp);  //정적 프로퍼티

me2.staticMethod();  //TypeError: me2.staticMethod is not a function
console.log(me2.staticProp);  //undefined

정적 프로퍼티 / 메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티 / 메서드가 아니므로 인스턴스로 접근할 수 없다.




프로퍼티 존재 확인

in 연산자

in 연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인한다. 좌변에는 프로퍼티 키를 나타내는 문자열을, 우변에는 객체로 평가되는 표현식을 사용한다.

const person = {
  name: 'kim',
  age: 24
};

console.log('name' in person);  //true
console.log('age' in person);  //true
console.log('address' in person);  //false

in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속받는 프로토타입의 프로퍼티를 확인하므로 주의가 필요하다.

console.log('toString' in person);  //true

in 연산자 대신 ES6에서 도입된 Reflect.has 메서드를 사용해서 프로퍼티 존재를 확인할 수 있다. in 연산자와 Reflect.has 메서드는 동일하게 동작한다.

const person = {
  name: 'kim',
  age: 24
};

console.log(Reflect.has(person, 'name'));  //true
console.log(Reflect.has(person, 'toString'));  //true

Obejct.prototype.hasOwnProperty 메서드

Obejct.prototype.hasOwnProperty 메서드를 사용해도 객체에 특정 프로퍼티가 존재하는지 확인 가능하다.

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

const person = {
  name: 'kim',
  age: 24
};

console.log(person.hasOwnProperty('name'));  //true
console.log(person.hasOwnProperty('age'));  //true
console.log(person.hasOwnProperty('address'));  //false
console.log(person.hasOwnProperty('toString')); 




프로퍼티 열거

for...in 문

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

const person = {
  name: 'kim',
  age: 24
};

for(const key in person) {
  console.log(key + ': ' + person[key]);
}
//name: kim
//age: 24

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

for...in 문은 in 연산자처럼 순회 대상 객체의 프로퍼티 뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거한다. 이때 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티만 순회하며 열거한다.

또한 for...in문은 프로퍼티 키가 심벌인 프로퍼티에 대해서는 열거하지 않는다.


만약 상속받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하려면 Obejct.prototype.hasOwnProperty 메서드를 사용해 객체 자신의 프로퍼티인지 확인하면 된다.
const person = {
  name: 'kim',
  age: 24
};

for(const key in person) {
  if(person.hasOwnProperty(key)){
     console.log(key + ': ' + person[key]);
  }
}
//name: kim
//age: 24

for...in 문은 프로퍼티를 열거할 때 순서를 보장하지 않지만 대부분의 모던 프라우저는 순서를 보장하고 숫자인 프로퍼티 키에 대해서는 정렬을 실시한다.

const obj = {
  2: 5,
  3: 4,
  1: 3,
  b: 2,
  a: 1,
};

for(const key in obj) {
  if(obj.hasOwnProperty(key)){
     console.log(key + ': ' + obj[key]);
  }
}

/*
1: 3
2: 5
3: 4
b: 2
a: 1
*/

배열에는 for...in문이 아닌 일반적인 for문이나 for...of문 또는 Array.prototype.forEach 메서드를 사용하는 것을 권장한다.

Object.keys/values/entries 메서드

for...in문은 객체 자신의 고유 프로퍼티 뿐만 아니라 상속받은 프로퍼티도 열거한다.

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

Object.keys 메서드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환한다.

Object.values 메서드는 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환한다.

Object.entries 메서드는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환한다.

const person = {
  name: 'kim',
  age: 24,
  __proto__: {address: 'Seoul'}
};

console.log(Object.keys(person));  //['name', 'age']
console.log(Object.values(person));  //['kim', 24]
console.log(Object.entries(person));  //[['name', 'kim'], ['age', 24]]








📔출처
위키북스 - 모던 자바스크립트 Deep Dive
https://wikibook.co.kr/mjs/

0개의 댓글