글 작성자: 망고좋아
반응형

 

프로토타입 상속

  • "user의 메서드를 복사하거나 다시 구현하지 않고 user에 약간의 기능을 얹어 adminguest 객체를 만들 수 있지 않을까"를 프로토타입 상속을 이용해서 구현할 수 있다.

 

[[Prototype]]

  • 자바스크립트의 객체는 [[Prototype]]이라는 숨김 프로퍼티를 갖는다.
  • 이 숨김 프로퍼티 값은 null이거나 다른 객체에 대한 참조가 되는데, 다른 객체를 참조하는 경우 참조 대상을 '프로토타입(prototype)'이라 부른다.

 

  • object에서 프로퍼티를 읽으려고 하는데 해당 프로퍼티가 없으면 자바스크립트는 자동으로 프로토타입에서 프로퍼티를 찾는다. 이러한 동작 방식을 '프로토타입 상속'이라고 부른다.

 

// [[Prototype]] 프로퍼티는 내부 프로퍼티이면서 숨김 프로퍼티이지만 다양한 방법을 사용해 개발자가 값을 설정할 수 있다.

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal;

 

 

🔔 __proto__는 [[Prototype]]용 getter·setter이다.

  • __proto__[[Prototype]]과 다르다.
  • __proto__[[Prototype]]의 getter(획득자)이자 setter(설정자)이다.
  • 최근 작성된 스크립트에선 __proto__ 대신 함수 Object.getPrototypeOfObject.setPrototypeOf을 써서 프로토타입을 획득(get)하거나 설정(set)한다.
// 객체 rabbit에서 프로퍼티를 얻고싶은데 해당 프로퍼티가 없다면, 자바스크립트는 자동으로 animal이라는 객체에서 프로퍼티를 얻는다.

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // animal이 rabbit의 프로토타입이 되도록 설정

// 프로퍼티 eats과 jumps를 rabbit에서도 사용할 수 있게 되었다.
alert( rabbit.eats ); // true, rabbit.eats 프로퍼티를 읽으려 했는데, rabbit엔 eats라는 프로퍼티가 없다. 이때 자바스크립트는 [[Prototype]]이 참조하고 있는 객체인 animal에서 eats를 얻어낸다.
alert( rabbit.jumps ); // true

  • 위 그림에서 “rabbit의 프로토타입은 animal입니다.” 혹은 "rabbitanimal을 상속받는다."라고 말할 수 있다.
  • 프로토타입을 설정해줘서 rabbit에서도 animal에 구현된 유용한 프로퍼티와 메서드를 사용할 수 있다.
  • 이렇게 프로토타입에서 상속받은 프로퍼티를 '상속 프로퍼티(inherited property)'라고 한다.

 

상속 프로퍼티를 사용해 animal에 정의된 메서드를 rabbit에서 호출

let animal = {
  eats: true,
  walk() {
    alert("동물이 걷습니다.");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// 메서드 walk는 rabbit의 프로토타입인 animal에서 상속받았다.
rabbit.walk(); // 동물이 걷습니다.

 

프로토타입 체이닝

let animal = {
  eats: true,
  walk() {
    alert("동물이 걷습니다.");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// 메서드 walk는 프로토타입 체인을 통해 상속받았다.
longEar.walk(); // 동물이 걷습니다.
alert(longEar.jumps); // true (rabbit에서 상속받음)

 

프로토타입 체이닝 제약 사항 2가지

  • 순환 참조(circular reference)는 허용되지 않는다. __proto__를 이용해 닫힌 형태로 다른 객체를 참조하면 에러가 발생한다.
  • __proto__의 값은 객체나 null만 가능하다. 다른 자료형은 무시된다.

 

 

⚠️ 프로토타입은 프로퍼티를 읽을 때만 사용한다.

  • 프로퍼티를 추가, 수정하거나 지우는 연산은 객체에서 직접 해야 한다.
let user = {
  name: "John",
  surname: "Smith",

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  },

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

let admin = {
  __proto__: user,
  isAdmin: true
};

alert(admin.fullName); // John Smith (*)

// setter 함수가 실행된다!
admin.fullName = "Alice Cooper"; // (**)

alert(admin.fullName); // Alice Cooper , state of admin modified
alert(user.fullName); // John Smith , state of user protected

 

 

this는 프로토타입에 영향을 받지 않는다.

  • 메서드를 객체에서 호출했든 프로토타입에서 호출했든 상관없이 this는 언제나 . 앞에 있는 객체가 된다.
  • 위 코드에서는 admin.fullName=으로 setter 함수를 호출할 때, thisuser가 아닌 admin이 된다.
// animal엔 다양한 메서드가 있다.
let animal = {
  walk() {
    if (!this.isSleeping) {
      alert(`동물이 걸어갑니다.`);
    }
  },
  sleep() {
    this.isSleeping = true;
  }
};

let rabbit = {
  name: "하얀 토끼",
  __proto__: animal
};

// rabbit의 프로퍼티 isSleeping을 true로 변경한다.
rabbit.sleep();

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (프로토타입에는 isSleeping이라는 프로퍼티가 없다.)

  • 상속받은 메서드의 thisanimal이 아닌 실제 메서드가 호출되는 시점의 점(.) 앞에 있는 객체가 된다.
  • 따라서 this에 데이터를 쓰면 animal이 아닌 해당 객체의 상태가 변화한다.
  • 결론 : 메서드는 공유되지만, 객체의 상태는 공유되지 않는다.

 

 

Object.prototype.hasOwnProperty()

  • hasOwnProperty() 메소드는 객체가 특정 프로퍼티를 가지고 있는지를 나타내는 불리언 값을 반환한다.
const object1 = {};
object1.property1 = 42;

console.log(object1.hasOwnProperty('property1')); //true

console.log(object1.hasOwnProperty('toString')); // false

console.log(object1.hasOwnProperty('hasOwnProperty')); // false

 

 

for…in 반복문

  • for..in은 상속 프로퍼티도 순회대상에 포함시킨다.
let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};
javascript
// Object.keys는 객체 자신의 키만 반환한다.
alert(Object.keys(rabbit)); // jumps

// for..in은 객체 자신의 키와 상속 프로퍼티의 키 모두를 순회한다.
for(let prop in rabbit) alert(prop); // jumps, eats
// obj.hasOwnProperty(key)를 응용하면 아래 예시에서처럼 상속 프로퍼티를 걸러낼 수 있고, 상속 프로퍼티만을 대상으로 무언가를 할 수도 있다.
let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

for(let prop in rabbit) {
  let isOwn = rabbit.hasOwnProperty(prop);

  if (isOwn) {
    alert(`객체 자신의 프로퍼티: ${prop}`); // 객체 자신의 프로퍼티: jumps
  } else {
    alert(`상속 프로퍼티: ${prop}`); // 상속 프로퍼티: eats
  }
}

  • for..in은 오직 열거 가능한 프로퍼티만 순회 대상에 포함하기 때문에 hasOwnProperty는 얼럿창에 출력되지 않습니다. (hasOwnProperty는 열거 가능한(enumerable) 프로퍼티가 아니기 때문에)

 

 

📌 참고

반응형