티스토리 뷰

1.객체지향 프로그래밍

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

 

다양한 속성 중에서 프로그램에 필요한 속성만 표현하는 것을 추상화라고 한다.

 

// 이름과 주소 속성을 갖는 객체
const person = {
  name: 'Lee',
  address: 'Seoul'
};

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

 

속성을 통해 여러개의 값을 하나의 단위로 구성한 복합적인 자료구조를 객체라 하고, 객체지향 프로그래밍은 독립적인 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임이다.

 

const circle = {
  radius: 5, // 반지름

  // 원의 지름: 2r
  getDiameter() {
    return 2 * this.radius;
  },

  // 원의 둘레: 2πr
  getPerimeter() {
    return 2 * Math.PI * this.radius;
  },

  // 원의 넓이: πrr
  getArea() {
    return Math.PI * this.radius ** 2;
  }
};

console.log(circle);
// {radius: 5, getDiameter: ƒ, getPerimeter: ƒ, getArea: ƒ}

console.log(circle.getDiameter());  // 10
console.log(circle.getPerimeter()); // 31.41592653589793
console.log(circle.getArea());      // 78.53981633974483

 

객체의 상태를 나타내는 데이터와, 이 상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶는다.

따라서 객체는 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라고 할 수 있다.

 

이때 데이터를 프로퍼티, 동작을 메서드라고 한다.

 


2. 상속과 프로토타입

상속이란 어떤 객체의 프로퍼티, 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는것을 말한다.

JS는 프로토ㅇ타입을 기반으로 상속을 구현해 불필요한 중복을 제거한다.

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
  this.getArea = function () {
    // Math.PI는 원주율을 나타내는 상수다.
    return Math.PI * this.radius ** 2;
  };
}

// 반지름이 1인 인스턴스 생성
const circle1 = new Circle(1);
// 반지름이 2인 인스턴스 생성
const circle2 = new Circle(2);

// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
// getArea 메서드는 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 바람직하다.
console.log(circle1.getArea === circle2.getArea); // false

console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172

 

circle생성자 함수가 생성하는 모듣 객체는 radius프로퍼티와 getArea메서드를 갖고 있다. 

radius프로퍼티의 값은 인스턴스마다 다르지만, getArea메서드는 모든 인스턴스가 동일한 내용의 메서드를 사용하므로 하나만 생성하여 공유해 사용하는것이 바람직하다. 

하지만 인스턴스가 생성될 때 마다 getArea메서드가 중복생성,소유된다.

 

아래는 상속을 통해 중복을 제거하는 코드이다.

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
}

// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가한다.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있다.
Circle.prototype.getArea = function () {
  return Math.PI * this.radius ** 2;
};

// 인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);

// Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는
// 프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받는다.
// 즉, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유한다.
console.log(circle1.getArea === circle2.getArea); // true

console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172

Circle생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입의 모든 프로퍼티와 메서드를 상속받는다.

getArea 메서드는 단 하나만 생성되어 프로토타입인 Circle.protorype의 메서드로 할당되어 있어, 생성자 함수가 생성하는 모든 인스턴스는 getArea메서드를 상속받아 사용할 수 있다.

 

이처럼 공통적으로 사용될 프로퍼티나 메서드를 프로토타입에 미리 구현해두면 생성자 함수가 생성할 인스턴스는 모드 해당 내용을 사용할 수 있게 된다.


3.프로토타입 객체

프로토타입 객체란 객체간 상속을 구현하기 위해 사용된다.

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

그러면 하위 객체는 해당 프로퍼티를 자유롭게 사용할 수 있게된다.

 

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조다.

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

 

[[Protorype]] 내부 슬롯에는 직접 접근할 수 없지만, 위 그림처럼 __proto__ 접근자 프로퍼티를 통해 내부 슬롯이 가리키는 프로토타입에 간접적으로 접근할 수 있다. 

 

3.1 __proto__접근자 프로퍼티

모든 객체는 __proto__접근자 프로퍼티를 통해 자신의 프로토타입에 접근할 수 있다.

아래와 같이 객체를 콘솔에서 log를 찍어보면 [[Prototype]]을 통해 내부 슬롯에 접근할 수 있는것을 볼 수 있다.

__proto__는 접근자 프로퍼티다.

JS는 원칙적으로 내부 슬롯과 내부 메서드에 직접적으로 접근한거나 호출할 수 있는 방법을 제공하지 않기 때문이다.

그래서 [[Prototype]]내부 슬롯에 직접 접근하지 못하고 접근자 프로퍼티를 통한 간접접근만 가능하다.

그래서 Object.prototype의 접근자 프로퍼티인__proto__는 gett/setter/함수라고 불리는 접근자 함수를 통해 내부 슬롯의 값을 취득하거나 할당하는 것이다.

 

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

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

const person = { name: 'Lee' };

// person 객체는 __proto__ 프로퍼티를 소유하지 않는다.
console.log(person.hasOwnProperty('__proto__')); // false

// __proto__ 프로퍼티는 모든 객체의 프로토타입 객체인 Object.prototype의 접근자 프로퍼티다.
console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'));
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}

// 모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속받아 사용할 수 있다.
console.log({}.__proto__ === Object.prototype); // true

 

 

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

프로토타입에 접근하기위해 접근자를 사용하는 이유는, 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해서다.

const parent = {};
const child = {};

// child의 프로토타입을 parent로 설정
child.__proto__ = parent;
// parent의 프로토타입을 child로 설정
parent.__proto__ = child; // TypeError: Cyclic __proto__ value

 

parent객체를 child 객체의 프로토타입으로 설정한 후, child객체를 parent객체의 프로토타입으로 설정한 예시이다.

이런 경우는 서로가 자신의 프로토타입이 되는 비정상적인 체인이 만들어지기 때문에 에러가 발생한다.

 

이러한 경우처럼, 서로 순환참조하는 프로토타입 체인이 만들어지면 종점이 존재하지 않기 때문에 무한루프에 빠지게 된다.

때문에 직접 접근할 수 없도록 접근자를 통해 접근하도록 제한된 것이다.

 

__proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.

 __proto__는 ES5까지 비표준이었다가 ES6에서 표준으로 채택됐다.

하지만 코드내에서 사요하는것은 권장하지 않는다.

모든 객체가 접근자 프로퍼티를 사용할 수 있는것은 아니기 때문이다.

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

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

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

 

따라서 접근자 프로퍼티 대신, getProtorypeOf 메서드를 사용하고 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용하는 것을 권장한다.

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

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함