19 - 프로토타입

2022. 9. 13.·🎨 프론트엔드 공부/JS & TS

모던 자바스크립트 Deep Dive 정리

1. 객체지향 프로그래밍

2. 상속과  프로토타입

3. 프로토타입 객체

▶ 프로토타입을 상속받은 하위 객체 -> 상위 객체의 프로퍼티 사용 가능

▶ 생성자 함수에 의해 생성된 객체의 프로토타입 -> 생성자함수.prototype 형태로 바인딩된 객체

▶ 모든 객체는 [[Prototype]] 이라는 내부 슬롯을 가짐

▷ __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근 가능

3-1. __proto__ 접근자 프로퍼티

▶ 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입([[Prototype]]) 에 접근 가능

▶ 접근자 프로퍼티 : getter, setter 함수로 구성

 

const obj = {};
const parent = { x: 1 };

// 1. __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근 ->
// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;

// 2. 새로운 프로토타입을 할당 ->
// setter함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

console.log(obj.x); // 1

 

__proto__ 접근자 프로퍼티는 상속을 통해 사용된다

▶ __proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니고

▷ Object.prototype의 프로퍼티임

▷ 즉, 모든 객체는 상속을 통해 Object.prototype.__proto__ 를 사용할 수 있음

Object.prototype

▶ 모든 객체는 프로토타입의 계층 구조인 프로토타입 체인에  묶여 있다 (스코프 체인 처럼)

▶ JS 엔진은 객체의 프로퍼티(메서드 포함)에 접근하려고 할때,

▷ 해당 객체에 접근하려는 프로퍼티가 없다면 __proto__ 가 가리키는 참조를 따라 거슬러 올라감

▷ 이때 프로토타입 체인의 최상위 객체는 Object.prototype 이며,

▷ 이 객체의 프로퍼티와 메서드는 모든 객체에 상속됨

__proto__ 접근자 프로퍼티로만 프로토타입에 접근하는 이유

▶ 프로토타입 체인은 일방통행의 구조를 가져야 함

▷ 순환참조하여 프로토타입 체인에서 프로퍼티 참조할 때 무한 루프가 만들어질 수 있음

__proto__ 접근자를 코드 내에서 직접 사용하는 것은 권장되지 않음

▶ 버전 이슈 또는 직접 상속을 통해 Object.prototype을 상속받지 않는 객체가 발생할 수 있음

 __proto__ 대신

▶ 프로토타입의 참조를 취득할 때 : Object.getPrototypeof()

▶ 프로토타입을 할당(교체)할 때 : Object.setPrototypeof()

 

// 3-1. 과 같은 역할의 코드
const obj = {};
const parent = { x: 1 };

// obj 객체의 프로토타입을 취득
Object.getPrototypeOf(obj); // obj.__proto__;
// obj 객체의 프로토타입을 교체
Object.setPrototypeOf(obj, parent); // obj.__proto__ = parent;

console.log(obj.x); // 1

 

3-2. 함수 객체의 prototype 프로퍼티

▶ prototype 프로퍼티 : 함수 객체만이 소유, 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴

▷ 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티 생성 X (프로토타입도 없음)

▶ 모든 객체가 가진 (Object.prototype 에서 상속받은) __proto__ 접근자 프로퍼티와 동일한 프로토타입을 가리킴

3-3. 프로토타입의 constructor 프로퍼티와 생성자 함수

▶ 모든 프로토타입은 constructor 프로퍼티(생성자 함수 가리킴)를 갖는다

▷ 예시

 

// 생성자 함수
function Person(name) {
  this.name = name;
}

const me = new Person('Lee');

// me 객체의 생성자 함수는 Person이다.
console.log(me.constructor === Person);  // true

// me 객체의 프로토타입 체인 상위에 있는 Object.prototype에 constructor 프로퍼티가 있음

 

4. 리터럴 표기법으로 만든 객체의 생성자 함수와 프로토타입

▶ 객체 리터럴 방식으로 만든 객체도 생성자 함수는 Object 생성자 함수이다

▷ 예시

 

// obj 객체는 Object 생성자 함수로 생성한 객체가 아니라 객체 리터럴로 생성했다.
const obj = {};

// 하지만 obj 객체의 생성자 함수는 Object 생성자 함수다.
console.log(obj.constructor === Object); // true

 

▶ 하지만 new.target 등 세부내용은 엄연히 다르다

▷ 함수 리터럴 방식과 Function 생성자 함수 방식 함수가 생성과정, 스코프, 클로저 등 차이가 있는 것과 동일

★ 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 항상 맞물려 존재한다

▶ 리터럴 표기법

 

리터럴 표기법 생성자 함수 프로토타입
객체 리터럴 Object Object.prototype
함수 리터럴 Function Function.prototype
배열 리터럴 Array Array.prototype
정규표현식 리터럴 RegExp RegExp.prototype

 

5. 프로토타입의 생성 시점

▶ 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다

▶ 생성자 함수는 1) 사용자 정의 생성자 함수, 2) 빌트인  생성자 함수 2가지 종류가 있음

5-1. 사용자 정의 생성자 함수와 프로토타입 생성 시점

▶ 생성자 함수로 호출할 수 있는 함수(constructor)는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입 생성

▷ 함수 선언문은 런타임 이전에 먼저 먼저 실행되기 때문에 (함수 호이스팅) 프로토타입도 런타임 이전에 생김

5-2. 빌트인 생성자 함수와 프로토타입 생성 시점

▶ Object, String, Number, Function, Array 등

▷ 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입 생성

★ 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면, 프로토타입은 생성된 객체의 [[prototype]] 내부 슬롯에 할당되고, 생성된 객체는 프로토타입을 상속받음

6. 객체 생성 방식과 프로토타입의 결정

6-1. 객체 리터럴 방식에 의해 생성된 객체의 프로토타입

6-2. Object 생성자 함수에 의해 생성된 객체의 프로토타입

▶ 객체 리터럴, Object 생성자 함수 방식 객체의 프로토타입은 다양한 메서드를 보유

▷ constructor, hasOwnProperty 등등

6-3. 생성자 함수에 의해 생성된 객체의 프로토타입

▶ 생성자 함수에 의해 생성된 객체(인스턴스)의 프로토타입은 constructor 메서드만 보유

▷ 하지만 프로토타입 체인을 거슬러 올라가 Object.prototype을 상속받아 사용할 수 있다

▷ 생성자 함수로 만들어지는 객체는 인스턴스이므로, Object.prototype에 프로퍼티를 추가하여 인스턴스들이 상속받도록 할 수 있다

▷ 상속 예시

 

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

// 프로토타입 메서드
Person.prototype.sayHello = function () {
  console.log(`Hi! My name is ${this.name}`);
};

const me = new Person('Lee');
const you = new Person('Kim');

me.sayHello();  // Hi! My name is Lee
you.sayHello(); // Hi! My name is Kim

// Person 생성자 함수를 통해 생성된 모든 객체는 프로토타입에 추가된 sayHello 메서드를 상속받아
// 자신의 메서드처럼 사용할 수 있다

 

7. 프로토타입 체인

▶ 생성자 함수로 생성한 객체(인스턴스)의 프로토타입 : 인스턴스.prototype

▷ 하지만 Object.prototype 상속 가능

 

▶ 프로토타입 체인

▷ 상위 객체의 프로토타입의 프로퍼티를 순차적으로 검색

▷ 자바스크립트에서 상속을 구현하는 메커니즘

▷ 최상위에는 Object.prototype 존재

 

▶ call 메서드

▷ this로 사용할 객체를 전달하면서 함수 호출

▷ call 메서드 예시

 

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

const me = new Person('Lee');

Object.prototype.hasOwnProperty.call(me, 'name');
// this로 사용할 me 객체를 전달하면서 상위 객체인 Object.prototype의 hasOwnProperty 메서드 호출

 

▶ 프로퍼티가 아닌 식별자는 스코프 체인을 따름

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

▶ 오버라이딩

▷ 상위 클래스가 가지고 있는 메서드를 하위  클래스가 재정의(덮어쓰기)하여 사용

 

▶ 오버로딩

▷ 함수 이름은 동일하지만, 매개변수의 타입 또는 갯수가 다른 메서드를 구현하고, 매개변수에 의해 메서드를 구별하여 호출하는 방식

 

▶ 프로퍼티 새도잉

▷ 오버라이딩이 발생하여 프로토타입 프로퍼티가 가려지는 현상

▷ 예시

 

const Person = (function () {
  // 생성자 함수
  function Person(name) {
    this.name = name;
  }

  // 프로토타입 메서드
  Person.prototype.sayHello = function () {
    console.log(`Hi! My name is ${this.name}`);
  };

  // 생성자 함수를 반환
  return Person;
}());

const me = new Person('Lee');

// 인스턴스 메서드
me.sayHello = function () {
  console.log(`Hey! My name is ${this.name}`);
};

// 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.
me.sayHello(); // Hey! My name is Lee

 

▶ 하위 객체를 통해 상위 객체의 프로퍼티를 변경 또는 삭제하는 것은 불가능

▷ 직접 prototype에 접근하면 변경이나 삭제 가능

▷ 예시

 

// 프로토타입 메서드 변경 (인스턴스 메서드 없는 상황)
Person.prototype.sayHello = function () {
  console.log(`Hey! My name is ${this.name}`);
};
me.sayHello(); // Hey! My name is Lee

// 프로토타입 메서드 삭제
delete Person.prototype.sayHello;
me.sayHello(); // TypeError: me.sayHello is not a function

 

9. 프로토타입의 교체

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

▶ 생성자 함수의 프로토타입을 객체 리터럴로 교체할 수 있다

▷ 하지만, 객체 리터럴은 생성자 함수가 없기 때문에(constructor 프로퍼티 부재) 인스턴스에서 생성자 함수의 프로퍼티를 참조할 수 없어진다 (건너뛰어 Object.prototype 으로 올라감)

▷ 이 때, 객체 리터럴에 constructor 프로퍼티를 추가하고 생성자 함수를 value로 넣어주면, 연결이 끊어지지 않는다

9-2. 인스턴스에 의한 프로토타입의 교체

▶ 생성자 함수 이외의 객체에서는 (인스턴스) __proto__  를 통해 프로토타입에 접근한다

▷ __proto__ 를 대신 조작하지 않고, Object.setPrototypeOf(프로토타입을 변경할 객체, 할당할 객체(리터럴)) 메서드 를 통해 교체할 수 있다

★ 프로토타입을 직접 교체하지 않는 것이 권장된다 (대신 클래스 또는 직접 상속을 이용)

10. instanceof 연산자

▶ 객체 instanceof 생성자 함수

▷ 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체 프로토타입 체인 상에 존재하면 true

▷ 우변의 피연산자가 함수가 아닌 경우 TypeError 리턴

11. 직접 상속

11-1. Object.create에 의한 직접 상속

▶ Object.create(param1, param2)

▷ param1 : 생성할 객체의 프로토타입으로 지정할 객체

▷ param2 : 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체

▷ return : 생성된 객체

▷ 프로토타입 체인의 종점에 위치하는 객체를 인위적으로 만들 수 있음

★ 이러한 이유로 Object.hasOwnProperty 같은 Object.prototype의 빌트인 메서드를 객체에 직접 사용하는 것은 권장되지 않음

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

▶ ES6에서는 객체 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수 있음

▷ __proto__ 프로퍼티를 추가하고, 상위 객체(현재 객체가 상속받을)를 밸류값으로 넣어준다

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

▶ 인스턴스를 생성하지 않고도 참조/호출할 수 있는 프로퍼티/메서드

▷ 생성자 함수로 참조/호출

▷ 예시

 

// 생성자 함수
function Person(name) {
  this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHello = function () {
  console.log(`Hi! My name is ${this.name}`);
};

// 정적 프로퍼티
Person.staticProp = 'static prop';

// 정적 메서드
Person.staticMethod = function () {
  console.log('staticMethod');
};

const me = new Person('Lee');

// 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출한다.
Person.staticMethod(); // staticMethod

// 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.
// 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 한다.
me.staticMethod(); // TypeError: me.staticMethod is not a function

 

▷ Object.create()가 정적 메서드에 해당 -> Object.create() 처럼 사용

▷ 반면 Object.hasOwnProperty() 는 프로퍼티 메서드 -> Object.prototype.hasOwnProperty() 처럼 사용

▷ MDN에서 이처럼 정적 메서드와 프로퍼티 메서드를 구분 (prototype 대신 #을 써서 간소화하기도 함)

★ 메서드 내에서 this를 사용하지 않으면 (인스턴스를  참조하지 않으면) 정적 메서드로 변환 가능

13. 프로퍼티 존재 확인

13-1. in 연산자

▶ 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인

▷ 객체의 프로토타입 체인까지 거슬러 올라가 프로퍼티를 검색

▷ ES6의 Reflect.has(객체, '프로퍼티') 가 동일하게 동작

13-2. Object.prototype.hasOwnProperty 메서드

▶ in 연산자와 동일하게 동작하지만

▷ 객체 고유의 프로퍼티인 경우에만 true (상속받은 프로퍼티이면 false) 반환

14. 프로퍼티 열거

14-1. for ... in 문

▶ 객체의 모든 프로퍼티(모든 프로토타입 체인 중 enumerable property만)를 순회하며 열거

▷ 데이터 프로퍼티 중 enumerable이 true인 것만 열거함

▷ 따라서 toString 같은 Object.prototype 프로퍼티는 열거되지 않음

▷ 정확한 순서가 보장되지않음

14-2. Object.keys/values/entries 메서드

▶ 객체 자신의 고유하고 열거 가능한 프로퍼티를 열거할 때 사용

▷ keys : 프로퍼티 키를 배열로 반환

▷ values : 프로퍼티 값을 배열로 반환

▷ entries : 프로퍼티 키와 값의 쌍의 배열을 배열에 담아(2차원 배열) 반환

'🎨 프론트엔드 공부/JS & TS' 카테고리의 다른 글
  • 21 - 빌트인 객체
  • 20 - strict mode
  • 18 - 함수와 일급 객체
  • 17 - 생성자 함수에 의한 객체 생성
지식물원
지식물원
지식이 자라는 식물원!
  • 지식물원
    지식물원
    지식물원
  • 전체
    오늘
    어제
    • 분류 전체보기 (510)
      • 🎨 프론트엔드 공부 (247)
        • JS & TS (86)
        • HTML & CSS (22)
        • React & Next (49)
        • Vue & Nuxt (22)
        • 기타 (68)
      • 🤓 기술 학습 & 공부 기록 (116)
        • Node.js (0)
        • Python (37)
        • 백엔드 (0)
        • 딥러닝 (1)
        • 컴퓨터 일반 (72)
        • 개발 인프라 (6)
      • 👨‍💻 프로젝트 경험 (6)
        • Work (0)
        • Toy (6)
      • ⚙️ 개발 팁 & 노하우 (21)
        • 프론트엔드 (6)
        • 기타 (15)
      • ☕️ 커리어 & 인터뷰 준비 (88)
        • 코딩 테스트 (88)
      • 📰 기술 트렌드 & 생각 정리 (4)
      • 📚 기타 (25)
        • 마케팅 (15)
        • 비개발서적 (10)
  • 블로그 메뉴

    • 태그
  • 링크

  • 공지사항

    • 모바일 접속 시 코드 하이라이팅 깨질 때
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
지식물원
19 - 프로토타입
상단으로

티스토리툴바