객체 지향 프로그래밍, OOP(Object Oriented Programming)는 프로그램을 명령어 또는 함수의 목록으로 보는 전통적인 명령형 프로그래밍의 관점에서 벗어나, 여러 개의 독립적 단위, 즉 객체(Object)의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.
객체는 실세계의 사물이나 개념을 상태 데이터와 상태 데이터를 조작할 수 있는 동작으로 모델링하는 복합적인 자료구조다. 이때 객체의 상태 데이터를 프로퍼티(property), 동작은 메서드(method)라고 한다.
각 객체는 고유의 기능을 가지는 독립적인 부품이면서도, 다른 객체와 관계성을 가질 수 있다. 즉, 다른 객체와 메시지를 주고 받거나 데이터를 처리할 수도 있다. 또는 다른 객체의 상태 데이터나 동작을 상속받아 사용하기도 한다.
객체지향 프로그래밍의 4가지 원칙을 간단하게 표현하면 다음과 같다
자바와 같은 전통적인 객체 지향 프로그래밍 언어에서는 특정 기능을 객체로 구현할 때 위 4가지 특징을 가진 class를 구현하고, 필요한 곳에 class의 인스턴스 객체를 생성해 사용한다.
전통적인 OOP와 달리 자바스크립트는 class기반이 아니라 프로토타입(Prototype) 기반 상속을 구현한다.
프로토타입이란 객체의 상위(부모) 객체의 역할을 하는 객체로서, 자신과 연결된 하위 객체가 자유롭게 접근해 사용할 수 있는 공유 프로퍼티와 메서드를 제공한다.
자바스크립트의 프로토타입 기반 상속은 다음에 설명할 프로토타입 체인(Prototype chain)을 통한 ‘위임’ 이나 ‘연결’에 가깝다.
자바스크립트의 모든 객체는 자신의 프로토타입 객체와 연결되어 있고, 각 프로토타입 객체도 마찬가지로 자신의 프로토타입 객체와 연결되어있다. 그리고 모든 객체의 최상위에 위치한 프로토타입은 Object.prototype 다.
이렇게 하위 객체가 자신의 프로토타입 객체와 연결되는 구조를 프로토타입 체인(prototype chain)이라고 한다.
함수 내에 선언되지 않은 변수를 스코프체인을 통해 찾는 것처럼, 객체도 정의되지 않은 프로퍼티나 메서드를 사용하면 프로토타입 체인을 통해 상위 객체에서 해당 메서드/프로퍼티를 찾아 첫 번째로 발견되는 것을 사용한다.
예를 들어 배열 객체마다 정의하지 않은 map, forEach 등의 메서드를 사용할 수 있는 이유가 바로 이 프로토타입 체인 때문이다.
모든 배열 객체의 프로토타입인 Array.prototype 객체에 이 메서드들이 정의되어 있고, 프로토타입 체인을 통해 배열 객체가 이에 접근할 수 있기 때문이다.
각 객체에 존재하는 [[Prototype]] 내부 슬롯값이 객체의 프로토타입에 대한 참조인데, 다음과 같이 .__proto__ 접근자를 통해 확인할 수 있다.
배열 객체의 프로토타입인 Array.prototype 객체

Array.prototype 객체의 프로토타입인 Object.prototype (최상위 객체)

생성자 함수란 이름 그대로 객체(인스턴스)를 생성하는 함수다. 자바의 class를 생각할 수 있겠지만 전혀 다르다.
일단 자바스크립트의 함수는 일급객체로, 함수 객체만을 위한 내부 메서드와 내부 슬롯을 가지고 있다.
다음과 같이 일반함수로 정의된 함수는 내부에 [[constructor]] 라는 메서드를 가지고 있는데, [[constructor]] 를 가진 함수는 new 연산자와 함께 호출해서 객체를 생성하는 생성자 함수로 사용할 수 있다.
// 일반함수의 정의: 함수 선언문, 함수 표현식
function foo(){}
const bar = function(){}
new foo(); // -> 내부의 [[constructor]]가 실행, 객체를 생성한다
foo(); // -> 내부의 [[Call]]이 호출, 일반 함수처럼 실행되고 객체를 생성하지 않는다
new bar(); // -> bar {}
function Person(firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
}
Person.prototype.calcAge = function() {
console.log(2037 - this.birthYear);
}
const sunny = new Person('Sunny', 1994);
위 코드에서 일반함수 Person을 new 연산자와 함께 호출하면 다음 과정이 발생한다.
빈 객체{}가 생성되고 함수 실행컨텍스트의 this가 생성된 객체에 바인딩 된다
생성된 객체의 .__proto__ 프로퍼티에 생성자 함수의 .prototype 객체가 바인딩 된다
.prototype 는 new 연산자와 함께 호출할 수 있는 함수에만 존재하는 프로퍼티로, 함수 호출을 통해 생성할 객체의 프로토타입을 가지고 있다..prototype 에 프로퍼티로 추가해야 한다. Person.prototype.calcAge = function() {
console.log(2037 - this.birthYear);
} // 생성자 함수 내부에서 this, 즉 생성될 인스턴스의 프로퍼티로 추가한 값들은 인스턴스마다 새롭게 생성된다.
생성자 함수 내부 코드 실행이 완료되면 빈 객체를 바인딩한 this가 암묵적으로 반환된다.
// 객체의 .__proto__는 객체를 생성한 함수의 .prototype 프로퍼티다
console.log(sunny.__proto__ === Person.prototype); // true
// Person.prototype.calcAge
sunny.calcAge(); // 43
// 함수의 .prototype는 함수의 프로토타입이 아니라,
// 함수에 의해 생성되는 모든 객체들의 prototype이다
console.log(Person.prototype.isPrototypeOf(Person)); // false
console.log(Person.prototype.isPrototypeOf(sunny)); // true
Person.prototype.species = 'Homo Sapiens';
console.log(sunny.species); // 'Homo Sapiens'
// Object.prototype.hasOwnProperty
//: 프로토타입을 제외한 객체 고유의 프로퍼티중에 특정 프로퍼티가 있는지 확인한다
console.log(sunny.hasOwnProperty('species')); // false
자바스크립트의 class는 생성자 함수를 좀 더 전통 OOP의 class 처럼 사용할 수 있게끔 하는 syntatic sugar다. 즉, 내부적으로는 생성자 함수와 동일하게 프로토타입 기반 상속을 사용하지만 겉으로는 전통적인 OOP의 class에 가깝다.
💡 class vs 생성자 함수
class와 생성자 함수는 내부적으로 동일하게 동작하기 때문에, 둘 중 무엇을 사용할지는 전적으로 취향의 문제다. class가 프로토타입기반의 상속을 코드상에서 숨기기 때문에 싫어하는 사람도 있고, 생성자 함수보다 더 코드를 깔끔하게 작성하게 해주기 때문에 좋아하는 사람도 있다고 한다.
자바스크립트의 class는 사실 함수로, 다음과 같은 특징이 있다.
따라서 class는 표현식으로도 선언할 수 있다. (보통은 전통 OOP에서 하듯 선언한다)
// 일반적인 OOP식 선언
class PersonCl {
...
}
// 함수 표현식으로 선언
const PersonCl = class {}
class Person{
constructor(fullName, birthYear){
// new 연산자와 함께 호출시 이 부분이 호출된다
this.fullName = fullName;
this.birthYear = birthYear;
}
// constructor 함수 밖의 메서드는 모두 Person.prototype에 추가된다
calcAge(){
console.log(2037 - this.birthYear)
}
greet(){
console.log(`hey ${this.fullName}`);
}
}
const jessica = new Person('Jessica', 1996)
위 코드에서 Person 클래스를 new 연산자와 함께 호출하면 생성자 함수 호출때와 마찬가지로 빈 객체가 생성되고, this가 바인딩 된다.
생성자 함수와 다른 점은, class는 호출시 constructor 함수 밖에 선언된 메서드를 자동으로 생성자의 .prototype에 추가한다. 즉, 다음 코드를 내부적으로 실행한다.
Person.prototype.calcAge = function(){
console.log(2037 - this.birthYear)
}
Person.prototype.greet = function(){
console.log(`hey ${this.fullName}`);
}
<생성된 인스턴스 확인>
jessica.calcAge(); // 41
jessica.greet(); // 'hey Jessica'
console.log(jessica.__proto__ === Person.prototype); // true
앞서 생성자 함수와 class는 호출시 자동으로 함수의.prototype 객체를 생성된 객체의 프로토타입으로 추가했다.
Object.create 은 생성자함수, class보다 덜 선호되는 방식이지만, 생성하는 객체의 프로토타입을 특정 객체로 직접 지정할 수 있다.
// 프로토타입으로 사용할 객체 선언
const PersonProto = {
calcAge() {
console.log(2037 - this.birthYear);
},
// 객체의 초기값세팅 메서드
init(firstName, birthYear){
this.firstName = firstName;
this.birthYear = birthYear;
}
}
// PersonProto를 프로토타입으로 갖는 빈 객체를 생성한다
const steven = Object.create(PersonProto); // PersonProto라는 프로토타입 객체에 연결된 새 객체 생성
// 생성한 객체의 초기값을 직접 세팅한다
steven.init("Steven", 2002");
위 코드에서 눈에 띄는 부분은 아마 PersonProto 의 init 메서드일 것이다. 이는 생성된 객체의 프로퍼티를 세팅하기 위해 구현한 메서드다.
init 메서드는 steven 객체의 프로토타입인 PersonProto의 메서드로, 프로토타입 체인을 통해 steven 객체가 사용할 때 this가 steven 객체를 가리키게 되므로 steven 객체의 프로퍼티를 세팅하게 된다.
Object.create는 빈 객체를 생성하고 프로토타입을 지정하는 것 까지만 하기 때문에, 객체 프로퍼티를 세팅하는 로직을 따로 작성해야 한다는 번거로움이 있다.
<생성된 인스턴스 확인>
steven.calcAge(); // 35
console.log(steven.__proto__ === PersonProto); // true