프로토타입이란

오호·2022년 1월 24일
0
post-thumbnail
post-custom-banner

자바스크립트 주요 개념 정리하기 2탄이다. 지난 스코프에 이어서 이번엔 프로토타입에 대해 정리해보도록 하자.

자바스크립트는 public, private, protected 같은 키워드들이 없어서 객체지향 언어가 아니라는 오해를 받기도 한다.

하지만 자바스크립트는 클래스 기반과는 조금 다른 프로토타입 기반의 객체지향 프로그래밍 언어이다

그렇다면 객체지향은 무엇인가??

1. 객체지향 프로그래밍

프로그램을 명령어 또는 함수의 목록으로 보는 전통적인 절차지향적인 관점에서 벗어나 여러 개의 독립적 단위, 객체의 집합으로 프로그램을 표현하려는 프로그래밍 기법이다.

객체지향 프로그래밍은 객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나로 묶어 생각한다. 여기서 상태 데이터를 프로퍼티 동작을 메서드라고 한다

각 객체는 고유의 기능을 갖는 독립적인 부품으로 볼 수 있지만 고유한 기능을 수행하면서 다른 객체와 관계성을 갖기도 한다.

2. 상속과 프로토타입

상속은 객체지향 프로그래밍의 핵심 개념으로 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용하는 것을 말한다.

이는 중복을 제거해주는 큰 역할을 한다. 예시를 살펴보자.

function Circle(rad) {
  this.rad = rad;
  this.getArea = function () {
     return Math.PI * this.rad ** 2;
  }
}

const circle1 = new Cirlce(1);
const circle2 = new Circle(2);

이 예제의 문제는 무엇일까? 얼핏보면 생성자 함수로 중복을 제거해서 객체를 효율적으로 생성하고 있는 것처럼 보인다.
하지만 getArea라는 메서드가 두 개의 인스턴스에 존재하게 된다.
이를 프로토타입 상속으로 해결해보자.

function Circle(rad){
  this.rad = rad; 
}

Circle.prototype.getArea = function () {
  return Math.PI * this.rad ** 2; 
}

이렇게 작성하면 Circle생성자의 모든 인스턴스는 자신의 프로토타입, 객체 역할을 하는 Circle.prototype의 모든 프로퍼티와 메서드를 상속받는다.

3. 프로토타입 객체

프로토타입은 어떤 객체의 상위 객체의 역할을 하는 객체로 다른 객체에 공유 프로퍼티를 제공한다.

모든 객체는 [[Prototype]] 이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조다. 또한 모든 객체는 하나의 프로토타입을 갖는다. 그리고 모든 프로토타입은 생성자 함수와 연결되어 있다.

3.1 __proto__ 접근자 프로퍼티

__proto__는 접근자 프로퍼티다. __proto__ 접근자 프로퍼티를 통해 간접적으로 [[Prototype]]내부 슬롯의 값에 접근할 수 있다.

__proto__접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니고 Object.prototype의 프로퍼티다.

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

3.2 함수 객체의 prototype 프로퍼티

함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.

따라서 생성자 함수로서 호출할 수 없는 화살표 함수와 ES6 메서드는 prototype 프로퍼티를 소유하지 않는다.

const Person = name => {
  this.name = name; 
}

console.log(Person.hasOwnProperty('prototype');// false

일반 함수도 prototype프로퍼티를 소유하지만 아무런 의미가 없다.

모든 객체가 가지고 있는 __proto__와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다.

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

const me = new Person('leo');

console.log(me.__proto__ === Person.prototype); // true

3.3 constructor 프로퍼티

모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 프로퍼티는 자신을 참조하고 있는 생성자 함수를 가리킨다.

function Person(name) {
  this.name = name; 
}
const me = new Person('leo');

console.log(me.constructor === Person): // true

4. 프로토타입 체인

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

Person.prototype.sayHello = function () {
  console.log(`Hi my name is ${this.name}`);
}

const me = new Person('leo');

console.log(me.hasOwnProperty('name')); // true

Object.getPrototypeOf(me) === Person.prototype; // true

Object.getPrototypeOf(Person.prototype) === Object.prototype; // true

자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.
이를 프로토타입 체인이라고 한다.

프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype이다. 따라서 모든 객체는 Object.prototype을 상속받는다.

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

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('leo');

me.sayHello = function () {
  console.log(`Hi my name is ${this.name}`);
};

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색해서 덮어쓰는 것이 아니고 인스턴스 프로퍼티로 추가한다.

이 때 인스턴스 메서드 sayHello는 프로토타입 메서드 sayHello를 오버라이딩 했고 프로토타입 메서드 sayHello는 가려진다.

6. instanceof 연산자

instanceof 연산자는 이항 연산자로서 좌변에 객체, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받는다.

객체 instanceof 생성자 함수

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

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

const me = new Person('leo');

console.log(me instanceof Person); // true
consoe.log(me instanceof Object); // true

7. 정적 프로퍼티 / 메서드

정적 프로퍼티 / 메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조 / 호출할 수 있는 프로퍼티 / 메서드를 말한다.

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('leo');

Person.staticMethod(); // staticMethod
me.staticMethod(); // TypeError

person 생성자 함수는 객체이므로 자신의 프로퍼티나 메서드를 소유할 수 있다. 생성자 함수 객체가 소유한 프로퍼티와 메서드를 정적 프로퍼티 메서드라고 한다

정적 프로퍼티와 메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티 메서드가 아니므로 인스턴스로 접근할 수 없다.

8. 프로퍼티 존재 확인

8.1 in 연산자

key in Object
const person = {
  name : 'leo',
  address : 'seoul',
};

cosole.log('name' in person); // true

in 연산자는 확인 대상 객체의 프로퍼티 뿐만 아니라 상속받은 모든 프로토타입의 프로퍼티를 확인한다.

console.log('toString' in person); // true

8.2 Object.prototype.hasOwnProperty 메서드

인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환한다.

9. 프로퍼티 열거

9.1 for ...in 문

const person = {
  name : 'messi',
  address : 'tokyo',
};

for (const key in person) {
  console.log(`${key} : ${person[key]`); 
}
// name: messi
// address: tokyo

for in 문은 프로퍼티키를 할당하면서 순회한다. person 객체는 2개의 프로퍼티가 있으므로 객체를 두번 순회하면서 프로퍼티 키를 변수에 할당한 후 코드 블록을 실행한다.

in 연산자와 마찬가지로 for in 문 또한 상속받은 프로퍼티들을 모두 열겨하는데 person의 다른 프로퍼티가 열거 안된 이유는 [[Enumerable]] 값이 false기 때문이다.

9.2 Object.keys/values/entries

for in 문은 상속받은 프로퍼티도 열거한다. 따라서 자신의 프로퍼티인지 확인하는 절차가 추가된다.

그래서 for in 문을 사용하기 보다는 Object.keys/values/entries 메서드를 사용하자.

const person = {
  name : 'messi',
  address : 'seoul',
  __proto__ : {age : 20}
};

console.log(Object.keys(person)); // ["name", "address"]
console.log(Object.values(person));  // ["messi", "seoul"]
console.log(Object.entries(person)); // [["name", "messi"], ["address", "seoul"]]

본 포스팅 내용은 모던자바스크립트 deepdive를 기반으로 작성되었습니다.

profile
오호
post-custom-banner

0개의 댓글