김춘삼씨의 고양이 2024. 4. 21. 19:17

📌 객체지향 프로그래밍

  • 자바스크립트는 프로토타입 기반의 객체지향 프로그래밍 언어이며 자바스크립트를 이르고 있는 거의 모든 것이 객체임 (함수, 배열, 정규 표현식 등)
  • 객체지향 프로그래밍: 여러 개의 독립적 단위(객체의 집합)로 프로그램을 표현하려는 프로그래밍 패러다임
  • 추상화: 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것
  • 객체:
    • 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조
    • 상태 데이터(프로퍼티)와 동작(메서드)을 하나의 논리적인 단위로 묶은 복합적인 자료구조

 

📌 상속과 프로토타입

  • 상속(Inheritance): 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것
  • 자바스크립트는 프로토타입을 기압으로 상속을 구현함
  • 생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해 두면 생성자 함수가 생성할 모든 인스턴스는 별도의 구현 없이 상위(부모) 객체인 프로토타입의 자산을 공유하여 사용할 수 있음 -> 코드 재사용에 유용함

 

📌 프로토타입 객체

  • 프로토타입은 어떤 객체의 상위(부모) 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)를 제공함
  • 프로토타입을 상속받은 하위(자식) 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있음

 

__proto__ 접근자 프로퍼티

  • 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있음
  • __proto__는 접근자 함수를 통해 프로토타입을 취득(get)하거나 할당(set)함
  • 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티를 사용할 수 있음
  • 프로토타입에 접근하기 위해 접근자 프로퍼티를 사용하는 이유는 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위함임 (무조건적으로 프로토타입을 교체할 수 없도록 __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하고 교체하도록 구현됨)
  • 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문에(직접 상속으로 Object.prototype을 상속받지 않는 객체를 생성할 수 있음) 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf 메서드, 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용하는 편이 좋음

 

함수 객체의 prototype 프로퍼티

  • 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴
  • 모든 객체가 가지고 있는(Object.prototype으로부터 상속받은) __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 동일한 프로토타입을 가리키지만 프로퍼티를 사용하는 주체가 다름 (__proto__ 접근자 프로퍼티: 모든 객체, prototype 프로퍼티: 생성자 함수)
  • 생성자 함수에 의해 생성된 인스턴스는 프로토타입의 contructor 프로퍼티에 의해 생성자 함수와 연결됨

 

📌 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

  • 리터럴 표기법에 의한 객체 생성 방식과 같이 명시적으로 new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하지 않는 객체를 만들 수 있음
  • 리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 contructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없음
  • 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재하기 때문에 리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요함 -> 가상적인 생성자 함수를 가짐

 

📌 프로토타입의 생성 시점

  • 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됨
  • 사용자 정의 생성자 함수
    • contructor(생성자 함수로서 호출할 수 있는 함수)는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성됨
    • non-constructor(생성자 함수로서 호출할 수 없는 함수)는 프로토타입이 생성되지 않음
    • 생성된 프로토타입의 프로토타입은 언제나 Object.prototype임
  • 빌트인 생성자 함수
    • 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성됨
    • 객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어 존재함
    • 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당됨

 

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

추상 연산 OrdinaryObjectCreate

  • 모든 객체는 추상 연산 OrdinaryObjectCreate에 의해 생성됨
  • 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 전달받음 -> 프로토타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정됨

 

객체 생성 방식

  • 객체 리터럴
    • 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype임
    • 객체 리터럴 내부에 프로퍼티를 추가함
  • Object 생성자 함수
    • Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype임
    • 먼저 빈 객체를 생성한 이후 프로퍼티를 추가해야 함
  • 생성자 함수
    • 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체임
    • 프로토타입에도 프로퍼티를 추가/삭제할 수 있으며 이렇게 추가/삭제된 프로퍼티는 프로토타입 체인에 즉각 반영됨
  • Object.create 메서드
  • 클래스(ES6)

 

📌 프로토타입 체인

  • 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없으면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로토타입을 순차적으로 검색하는 것
  • 프로토타입 체인: 객체지향 프로그래밍의 상속을 구현과 프로터피 검색을 위한 메커니즘
  • 스코프 체인: 식별자 검색을 위한 메커니즘

 

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

  • 오버라이딩: 상위클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
  • 오버로딩: 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식 (자바스크립트는 오버로딩 지원X)
  • 프로퍼티 섀도잉: 상속관계에 의해 프로퍼티가 가려지는 현상
  • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로퍼티를 인스턴스 프로퍼티로 추가하며 기존 메서드를 오버라이딩함
  • 프로토타입 프로퍼티를 변경 또는 삭제하려면 하위 객체를 통해 프로토타입 체인으로 접근하는 것이 아니라 프로토타입에 직접 접근해야 함

 

📌 프로토타입의 교체

  • 프로토타입은 생성자 함수 또는 인그턴스에 의해 교체할 수 있음
  • 프로토타입 교체를 통해 객체 간의 상속관계를 동적으로 변경하는 것은 꽤 번거롭기 때문에 프로토타입을 직접 교체하지 않는 것이 좋음

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

  • 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨
  • 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살릴 수 있음

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

  • 인스턴스의 __proto__접근자 프로퍼티를 통해 프로토타입을 교체할 수 있음
  • 프로토타입으로 교체한 객체에는 constructor 프로퍼티가 없으므로 contructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨

 

📌 instanceof 연산자

  • 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true, 아니면 fasle로 평가함
  • instanceof 연산자는 프로토타입의 constructor 프로퍼티가 가리키눈 생성자 함수를 찾는 것이 아니라 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인함

 

📌 직접 상속

Object.create에 의한 직접 상속

  • Object.create 메서드는 명시적으로 프로토타입울 지정하여 새로운 객체를 생성함
  • Object.create 메서드의 첫번째 매개변수에는 생성할 객체의 프로토타입으로 지정할 객체를, 두번째 매개변수에는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크럽터 객체로 이루어진 객체를 전달함
  • Object.create 메서드의 장점:
    • new 연산자가 없어도 객체를 생성할 수 있음
    • 프로토타입을 지정하면서 객체를 생성할 수 있음
    • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있음
  • 프로토타입이 null인 객체가 있을 수 있기 때문에 Object.prototype의 빌트인 메서드는 간접적으로 호출하는 것이 좋음

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

  • ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용해 직접 상속을 구현할 수 있음

 

📌 정적 프로퍼티/메서드

  • 정적 프로퍼티/메서드: 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드
  • 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없음
  • Object.prototype.hasOwnProperty 메서드는 모든 객체의 프로토타입 체인의 종점인 Object.prototype의 메서드이므로 모든 객체가 호출할 수 있음
  • 인스턴스/프로토타입 메서드 내에서 this를 사용하지 않는다면 그 메서드는 정적 메서드로 변경할 수 있음
  • 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있음

 

📌 프로퍼티 존재 확인

in 연산자

  • 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인함
  • 확인 대상 객체의 프로퍼티 뿐 아니라 확인 대상 객체가 상속받은 모든 프로퍼티를 확인함
  • Reflect.has 메서드를 사용해도 동일하게 동작함

Object.prototype.hasOwnProperty

  • 객체에 특정 프로퍼티가 존재하는지 확인할 수 있음
  • 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환함

 

📌 프로퍼티 열거

for ... in 문

  • 객체의 모든 프로퍼티를 순회하며 열거함
  • 객체의 프로퍼티 개수만큼 순회하며 for ... in 문의 변수 선언문에서 선언한 변수에 프로퍼티 키를 할당함
  • 순회 대상 객체의 프로퍼티뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거함
  • for ... in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumable]]의 값이 true인 프로퍼티를 순회하며 열거함
  • 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않음
  • 객체 자신의 프로퍼티만 열거하려면 Object.prototype.haseOwnProperty 메서드를 사용해 객체 자신의 프로퍼티인지 확인함
  • 배열에는 일반적인 for문이나 for ... of 문 또는 Array.prototype.foreach 메서드를 사용하기를 권장함

Object.keys / values / entries 메서드

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


 

참고문헌 및 출처 : 모던 자바스크립트 Deep Dive (이웅모)