프로토타입

정지훈·2020년 12월 6일
0

자바스크립트는 멀티 패러다임 프로그래밍 언어다.

명령형,함수형,프로토타입 기반 객체지향 프로그래밍을 지원한다.

자바스크립트는 객체 키반의 프로그래밍 언어이고 자바스크립트를 이루고 있는 거의 모든 것이 객체입니다.

원시 타입의 값을 제외한 모든 것이 객체라고 생각하면 된다.

객체지향 프로그래밍

객체지향 프로그래밍은 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.

여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조를 객체라고 한다.

예를들어서 사람에게는 다양한 속성이 있지만 관심이 있는 사람의 이름과 주소라는 속성에만 관심이 있다고 가정하면 그 속성만 간추려 내어 표현하는 것을 추상화 한다.

이름과 주소 속성을 갖는 person이라는 객체를 자바스크립트로 표현 하면 다음과 같다.

const person = {
  name: 'Jung',
  address: 'Seoul'
};

console.log(person); // {name: 'Lee', address: 'Seoul'}

이렇게 표현할 수 있다,

상속과 프로토타입

객체지향 프로그래밍의 핵심은 상속이다. 어떤 객체의 프로퍼티나 메서드를 다른 객체가 상속받아 그대로 사용할 수 있다.

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

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

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

console.log(circle1.getArea === circle2.getArea); // true

console.log(circle1.getArea()); // 3.141592...
console.log(circle2.getArea()); // 12.566370614...

이처럼 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입 즉 상위 객체 역할을 하는 Circle.prototype의 모든 프로퍼티와 메서드를 상속 받는다.

프로토타입 객체

프로토타입 객체는 객체지향 프로그래밍의 근간을 이루는 객체 간 상속을 구현하기 위해 사용된다.

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

즉 객체가 생성될때 객체 생성방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장된다.

constructor 프로퍼티를 통해서 생성자 함수에 접근할 수 있고 생성자 함수는 자신의 prototype 프로퍼티를 통해 프로토타입에 접근할 수 있다.

proto 접근자 프로퍼티

모든 객체는 proto 접근자 프로퍼티를 통해 자신의 프로토타입 즉 [[Prototype]]내부슬롯에 간접적으로 접근할 수 있다.

proto는 접근자 프로퍼티이다.

즉 다른 프로퍼티의 값을 읽거나 저장할 수 있다.

이것을 사용하면 상속을 바꿀 수 있기 때문에 사용할때는 조심히 사용 해야한다.(대도록 코드 내에서 직접 사용하지 않도록 한다.)

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

// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;
// setter함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

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

proto 접근자 프로퍼티는 ES5까지는 ECMAScript사양에 포함되지 않은 비표준이었지만 일부 브라우저는 지원하고 있기 때문에 호환성 때문에 ES6에서 표준으로 채택했다.

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

Object.prototype을 상속받지 않는 객체를 생성 할 수도 있기 때문에 proto접근자 프로퍼티를 사용할 수 없는 경우가 있다.

// obj는 프로토타입 체인의 종점이다. 따라서 Object.__proto__를 상속받을 수 없다.
const obj = Object.create(null);

// obj는 Object.__proto__를 상속받을 수 없다.
console.log(obj.__proto__); // undefined

// 따라서 Object.getPrototypeOf 메서드를 사용하는 편이 좋다.
console.log(Object.getPrototypeOf(obj)); // null

함수 객체의 prototype 프로퍼티

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

하지만 non-constructor인 화살표함수나 ES6메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않는다 그래서 프로토타입도 생성하지 않는다.

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

리터럴 표기법에 의해 객체 생성 방식은 new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하지 않는 객체 생성 방식도 있다.

// 객체 리터럴
const obj = {};

// 함수 리터럴
const add = function (a, b) { return a + b; };

// 배열 리터럴
const arr = [1, 2, 3];

// 정규표현식 리터럴
const regexp = /is/ig;

리터럴 표기법에 의해 생성된 객체도 물론 프로토타입이 존재하지만 리터럴 표기법에 의해 생성된 객체를 생성자 함수라고 단정할 수 없다.

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

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

위에처럼 객체 리터럴을 생성한 생성자 함수가 Object? Object 생성자 함수로 생성된 것은 아닐까? 라는 생각이 들지만 new 연산자와 함께 호출하면 내부적으로 추상연산을 호출하여 Object생성자 함수에 인수를 전달하지 않거나 undefined 또는 null을 인수로 전달하면서 new 연산자와 함께 호출하면 내부적으로는 추상연산 OrdinaryObjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성한다.

객체 리터럴이 평가될때 다음과 같이 추상연산을 호출하여 빈 객체를 생성하고 프로퍼티를 추가하도록 정의 되어 있다.

리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요하다.

그래서 리터럴 표기법에 의해 생성된 객체도 가상적인 생성자 함수를 갖는다.

리터럴 표기법에 의해 생성한 객체는 생성과정에 미묘한 차이는 있지만 결국 객체로서 동일한 특성을 가진다.

따라서 프로토타입의 constructor 프로퍼티를 통해 연결되어 있는 생성자 함수를 리터럴 표기법으로 생성한 객체를 생성자 함수로 생각해도 크게 무리는 없다.

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

[[Construct]]를 갖는 함수 객체는 new 연산자와 함께 생성자 함수로서 호출 할 수 있다.

생성자 함수로서 호출할 수 있는 함수는 함수 정의가 평가되어서 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다!!

(이는 일반 함수가 호출전에 이 함수가 무엇의 용도인지 모르기 때문이다. 그래서 평가때 같이 생성된다.)

// 함수 정의(constructor)가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(Person.prototype); // {constructor: ƒ}

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

객체 리터럴에 의해 생성된 객체의 프로토타입

자바스크립트 엔진은 객체 리터럴을 평가하여 객체를 생성할 때 추상 연산을 호출한다. 이때 추상연산에 전달되는 프로토타입은 Object.prototype이다. 즉 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype이다.

이처럼 객체 리터럴에 의해 생성된 obj 객체는 Object.prototype을 프로토타입으로 갖게 되며 Object.prototype을 상속 받는다.

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

new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 생성방식과 마찬가지로 추상연산이 호출된다. 이때 추상연산에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체다.

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

const me = new Person('jung');

위 코드가 실행되며 추상 연산에 의해 다음과 같이 생성자 함수와 생성자 함수의 portotype 프로퍼티에 바인딩되어 있는 객체와 생성된 객체 사이에 연결이 만들어 진다.

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 메서드를 상속받아 자신의 메서드처럼 사용할 수 있다.

프로토타입 체인

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

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

const me = new Person('Lee');

// hasOwnProperty는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty('name')); // true

Person 생성자 함수에 의해 생성된 me 객체는 Object.prototype의 메서드인 hasOwnProperty를 호출할 수 있다. 이것은 me 객체가 Person.prototype 뿐만 아니라 Object.prototype도 상속받았다는 것을 의미한다.

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

자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]]내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다,. 이를 프로토타입 체인이라 한다. 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘이다.

출저: https://poiemaweb.com/fastcampus/prototype

0개의 댓글