script 기초 - 프로토타입

9893 단어 JavaScriptJavaScript

프로토타입 상속

사람에 관한 프로퍼티와 메서드를 가진 user라는 객체가 있다고 해보자. 이 때 user와 상당히 유사하지만 약간의 차이가 있는 adminguest라는 객체를 만들어야 한다고 가정해보자. 프로토타입 상속(prototypal inheritance)을 이용하면, user에 약간의 기능을 얹어 adminguest 객체를 만들 수 있다.

✔️ [[Prototype]]

객체는 [[Prototype]]이라는 숨김 프로퍼티를 갖는다. 이 숨김 프로퍼티 값은 null이거나 다른 객체에 대한 참조가 되는데, 다른 객체를 참조하는 경우 참조 대상이 '프로토타입'이 된다.

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

🔍 __proto__

__proto__ 는 [[Prototype]]용 getter(획득자)이자 setter(설정자)이다.

비교적 근래에 작성된 스크립트에선 __proto__ 대신 함수 Object.getPrototypeOfObject.setPrototypeOf을 써서 프로토타입을 획득(get)하거나 설정(set)한다.

✔️ 프로토타입 체이닝

다른 객체를 참조하는 것이 이어지면 프로토타입 체이닝이 발생한다. 프로토타입 체이닝에는 두 가지 제약사항이 있다.

  1. 순환 참조는 허용되지 않는다. __proto__를 이용해 닫힌 형태로 다른 객체를 참조하면 에러가 발생한다.
  2. __proto__의 값은 객체null만 가능하다. 다른 자료형은 무시된다.
  3. 당연히 하나의 객체는 오직 하나의 [[Prototype]]만 있을 수 있다.

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

만약 walk라는 메서드를 가진 animal이라는 객체가 있고 이를 참조하는 객체 rabbit이 있다고 치자. 이 때 rabbitwalk라는 메서드를 새로 호출하면, 프로토타입에 있는 메서드가 실행되는 것이 아니라 rabbit에 추가한 walk가 실행된다.

그러나 접근자 프로퍼티는 setter 함수로 프로퍼티에 값을 할당하므로 이 규칙이 적용되지 않는다. 접근자 프로퍼티에 값을 할당하는 것은 함수를 호출하는 것과 같으므로, 문제 없이 실행될 것이다.

요약 : 접근자 프로퍼티가 아닌 데이터프로퍼티를 다루고 있다면, 쓰기나 지우기와 관련된 연산은 프로토타입을 통하지 않고 객체에 직접 적용된다.

✔️ 'this'는 언제나 .앞의 객체가 된다.

메서드를 객체에서 호출했든, 프로토타입에서 호출했든 상관없이 this는 언제나 .앞에 있는 객체가 된다.
상속받은 메서드를 사용하더라도 객체는 프로토타입이 아닌 자신의 상태를 수정한다.
메서드는 공유되지만, 객체의 상태는 공유되지 않는다.

✔️ for...in 반복문

let animal = {
  eats : true
};

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

//Object.keys는 객체 자신의 키만 반환
alert(Object.keys(rabbit); //jumps
  
// for...in은 객체 자신의 키와 상속 프로퍼티의 키 모두를 순회
for(let prop in rabbit) alert(prop); //jumps, eats

함수의 prototype 프로퍼티

new F()와 같은 생성자 함수를 이용하면 새로운 객체를 만들 수 있다.
이 때 F.prototype이 객체면 new 연산자는 F.prototype을 사용해 새롭게 생성된 객체의 [[Prototype]]을 설정한다.

🙋🏻‍♀️ F.prototype에서 "prototype"은 '프로토타입' 객체가 아니라 F에 정의된 일반 프로퍼티이다.

이거 좀.. 신기하네

let animal = {
  eats : true
}

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

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal 

alert( rabbit.eats ); //true

Rabbit.prototype = animalnew Rabbit을 호출해 만든 새로운 객체(rabbit)의 [[prototype]]animal로 설정하라는 뜻 ~

✔️ 함수의 prototype 프로퍼티와 consturctor 프로퍼티

개발자가 특별히 할당하지 않더라도 모든 함수는 "prototype" 프로퍼티를 갖는다.

function Rabbit (){}

//Rabbit.prototype = { constructor : Rabbit };

프로토타입 메서드와 proto가 없는 객체

__proto__는 다소 구식이기 때문에 더는 사용하지 않는 것이 좋다.
대신 사용할 수 있는 모던한 메서드가 있다는 점!

  • Object.create(proto, [descriptors]) : [[prototype]]proto를 참조하는 빈 객체를 만든다. 이 때 프로퍼티 설명자를 추가로 넘길 수 있다.
  • Object.getPrototype(obj) : obj[[prototype]]을 반환
  • Object.setPrototype(obj, proto) : obj[[prototype]]proto가 되도록 설정

🔍 프로퍼티 플래그와 설명자

객체 프로퍼티 추가 구성 옵션에 대해 알아보자.

프로퍼티 플래그

객체 프로퍼티는 값(value)과 함께 플래그(flag)라 불리는 특별한 속성 세 가지를 갖는다.

  • writable : true이면 값 수정 가능, false면 읽기만 가능

  • enumerable : true이면 반복문을 사용해 나열 가능.

  • configurable : true이면 프로퍼티 삭제나 플래그 수정 가능

  • Object.getOwnPropertyDescriptor(obj, propertyName) : 특정 프로퍼티에 대한 정보를 모두 얻을 수 있다.

  • Object.defineProperty(obj, propertyName, descriptor) : obj, propertyName은 설명자를 적용하고 싶은 객체와 프로퍼티, descriptor는 적용하고자 하는 프로퍼티 설명자.
    -> 이 메서드는 객체에 해당 프로퍼티가 있으면 플래그를 원하는대로 변경해준다. 프로퍼티가 없으면 인수로 넘겨받은 정보를 이용해 새로운 프로퍼티를 만든다. 이때 플래그 정보가 없으면 플래그값은 자동으로 false가 된다.

  • Object.defineProperties : 프로퍼티 여러개를 한 번에 정의할 수 있다.

Object.defineProperties(user, {
  name : { value : "john" , writable : false },
  surname : {value : "smith", writable : false },
   // ...
});
  • Object.getOwnPropertyDescriptor : 프로퍼티 설명자를 전부 한꺼번에 가져올 수 있다.Object.defineProperties와 함께 사용하면 객체 복사시 플래그도 함께 복사할 수 있다. 심볼형 프로퍼티를 포함한 프로퍼티 설명자 전체를 반환.
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));

좋은 웹페이지 즐겨찾기