책정리/Deep Dive Javascript

자바스크립트는 프로토타입 기반 언어이다

뽀글보리 2023. 11. 26. 17:52
반응형

자바스크립트는 객체지향 프로그래밍 언어, 프로타입 기반 언어이다.

자바스크립트는 명령형, 함수형, 프로토타입 기반, 객체지향 프로그래밍을 모두 지원하는 멀티 패러다임 프로그래밍 언어이다. 자바스크립트가 객체 지향 언어가 아니라고 오해하기 쉬운데, 자바스크립트는 원시 값을 제외한 함수, 배열, 정규 표현식 등은 모두 내부적으로 객체처럼 동작한다. 자바스크립트 ES6에서 클래스가 도입되었으나, 다른 일반적인 프로그래밍 언어와 다르게 내부적으로 프로토타입 기반으로 동작한다.

 

 

모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다. 접근자 프로퍼티 __proto__를 통해 간접적으로 접근 가능하다.

 

상속과 인스턴스 생성

const circle1 = new Circle();
const circle2 = new Circle();

Circle이라는 생성자 함수로 circle1, circl2라는 두개의 인스턴스를 생성한다고 생각해보자. Circle이라는 생성자 함수는 radius라는 프로퍼티와 getArea라는 메서드를 가지는 구조로 되어있다. 이 때 인스턴스마다 radius, getArea 프로퍼티를 포함하도록 생성한다면 같은 메서드인 getArea가 중복 생성된다는 점에서 비효율적이다.

자바스크립트는 프로토타입 기반으로 상속을 구현하기 때문에 불필요한 중복을 제거할 수 있다.

Circle 생성자 함수가 생성한 인스턴스는 Circle.prototype의 getArea 메서드를 상속받아 사용할 수 있다. 따라서 radius에 대한 내용만 개별적으로 소유하고 메서드는 상속을 통해서 공유하며 사용한다. 이렇게 상속을 사용하면 코드를 재사용할 수 있어서 유용하다.

프로토타입 객체

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가진다. 객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장된다.

__proto__와 prototype 프로퍼티는 다르다.

__proto__ 접근자 프로퍼티는 Object.prototype으로부터 상속받는 프로퍼티이다. prototype도 동일한 역할을 하지만 생성자 함수가 인스턴스를 위해 사용한다는 점에서 사용 목적과 사용 주체가 다르다는 차이가 있다.

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

const me = new Person('Lee');

 

다음과 같은 코드를 작성한다고 생각했을 때, Person은 생성자 함수이다. 자바스크립트에서 return문을 작성하지 않으면 this를 return하기 때문이다.

proto 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
모든 객체가 proto 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문에
proto 접근자 프로퍼티를 통해 프로토타입에 접근하는 것은 권장하지 않는다.

프로퍼티 대신 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototpyeOf 메서드나 Object.setPrototypeOf 메서드를 사용할 것을 권장한다.

 

프로토타입 체인

프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype이다. 따라서 모든 객체는 Object.prototype을 상속받는다.
자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라고 한다.

 

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

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

 

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색하여 프로토타입 프로퍼티를 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다.
이처럼 상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라 한다.

하위 객체를 통해 프토토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능하다.
하위 객체를 통해 프로토타입 체인으로 접근하는 것이 아니라, 프로토타입에 직접 접근해야 한다.

프로토타입의 교체

부모 객체인 프로토타입을 변경하여 객체 간의 상속 관계를 동적으로 변경할 수 있다.

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

  // ① 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
  Person.prototype = {
    sayHello() {
      console.log(`Hi! My name is ${this.name}`);
    }
  };

  return Person;
}());

const me = new Person('Lee');

 

위와 같이 프로토타입을 교체하게 되면 me 의 생성자 함수는 Person이 아닌 Object로 바뀐다.
자바스크립트 엔진은 프로토타입을 생성할 때 암묵적으로 constructor 프로퍼티를 추가하는 데, 이처럼 프로토타입을 교체하게 되면 constructor 프로퍼티가 없다. 따라서 me객체의 생성자 함수를 검색하면 Object가 나온다.
constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살릴 수 있다.

instanceof 연산자

객체 instanceof 생성자 함수

 

우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가되고, 그렇지 않은 경우에는 false로 평가된다.
생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다.

in 연산자

in 연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인한다. in 연산자는 프로토타입 체인 상에 존재하는 모든 프로토타입에서 프로퍼티를 검색한다.

참고

반응형