
자바스크립트는 프로토타입 기반의 객체 지향 프로그래밍 언어이며, 자바스크립트를 이루고 있는 거의 모든 것이 객체다.
객체지향 프로그래밍은 프로그래밍을 명령어 또는 함수의 목록으로 보는 절차지향적 관점에서 벗어나 여러 개의 독립적 단위, 즉 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.
객체는 객체의 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라고 할 수 있다. 이때 상태 데이터를 프로퍼티, 동작을 메서드라 부른다.
자바스크립트는 프로토타입을 기반으로 상속을 구현한다. 상속은 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말한다. 동일한 생성자 함수에 의해 생성된 모든 인스턴스가 동일한 메서드를 중복 소유하는 것은 메모리를 불필요하게 낭비한다.
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());
console.log(circle2.getArea());
Circle 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입의 모든 프로퍼티와 메서드를 상속받는다. getArea는 단 하나만 생성되어 Circle이 생성하는 모든 인스턴스는 getArea 메서드를 상속받아 사용할 수 있다. 이처럼 상속은 코드의 재사용이란 관점에서 매우 유용하다.
모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조다.
객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장된다.
모든 객체는 하나의 프로토타입을 갖는다. 그리고 모든 프로토타입은 생성자함수와 연결되어 있다

[[Prototype]] 내부 슬롯에는 직접 접근할 수 없지만, 위 그림처럼 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입에 간접적으로 접근할 수 있다.console.log({}.__proto__ === Object.prototype); //true
함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다. 따라서 non-constructor인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 을 생성하지 않는다.
모든 객체가 가지고 있는 proto 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(Person.prototype === me.__proto__); //true
모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다. 이 연결은 함수 객체가 생성 될 때 이뤄진다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(me.constructor === Person); //true
const obj = {};
const add = function (a, b) {
return a + b;
};
const arr = [1, 2, 3];
const regexp = /is/gi;
console.log(obj.constructor === Object); //true
리터럴 표기법에 의해 생성된 객체도 물론 프로토타입이 존재한다. 하지만 이 경우 constructor이 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수 없다.
리터럴 표기법에 의해 생성된 객체는 가상적인 생성자 함수를 갖는다. 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재하기 때문이다.
프로토타입은 생성자 함수가 생성되는 시점에 생성됨
constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(Person.prototype); //{constructor: f}
function Person(name) {
this.name = name;
}
반면 non-constructor는 프로토타입이 생성되지 않는다.
const Person = (name) => {
this.name = name;
};
console.log(Person.prototype); //undefined
모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성된다. 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성된다.
전역 객체
전역 객체는 코드가 실행되기 이전 단계에 자바스크립트 엔진에 의해 생성되는 특수한 객체다. 브라우저에서는 window, node.js에서는 global 객체를 의미한다.
전역 객체는 표준 빌트인 객체들과 환경에 따른 호스트 객체(Web API 또는 node.js의 호스트 API), 그리고 var로 선언한 전역 변수와 전역 함수를 프로퍼티로 갖는다. Math, Reflect, JSON을 제외한 표준 빌트인 객체는 모두 생성자 함수다.
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hi! my name is ${this.name}`);
};
const me = new Person('Lee');
console.log(me.hasOwnProperty('name')); //true
console.log(Object.getPrototypeOf(me) === Person.prototype); //true
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype); //true
자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라 한다.
프로토타입 체인의 최상위에는 언제나 Object.prototype이 있다. 이를 프로토타입 체인의 종점이라 한다.
프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘이라고 할 수 있다.
이에 반해, 식별자는 스코프 체인에서 검색한다. 따라서 스코프 체인은 식별자 검색을 위한 메커니즘이라고 할 수 있다.
프로토타입 프로퍼티와 같은 ㅣㅇ름의 프로퍼티를 인스턴스에 추가하면, 프로토타입 체인을 따라 검색하여 덮어 쓰는 것이 아니라 인스턴스의 프로퍼티로 추가한다. 이때 인스턴스 메서드는 프로토타입 메서드를 오버라이딩 했고 프로토타입 메서드는 가려진다.
이처럼 상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라 한다.
오버라이딩
상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
단, 하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능하다. 프로토타입에 직접 접근해야 가능하다.
객체 instanceof 생성자 함수
생성자 함수의 prototype에 바인딩된 객체가 좌변 객체의 프로토타입 체인 상에 존재하면 true로 평가된다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(me instanceof Person); //true
console.log(me instanceof Object); //true
정적 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말한다.
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('static method');
};
const me = new Person('Lee');
Person.staticMethod(); // static method
me.staticMethod(); // me.staticMethod is not a function
Person 생성자 함수 객체가 소유한 프로퍼티/메서드를 정적 프로퍼티/메서드라고 한다. 정적 프로퍼티/메서드는 인스턴스로 참조하거나 호출할 수 없다.

정적 프로토타입/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니기 때문이다.
만약 메서드 내에서 this를 사용하여 인스턴스를 참조할 필요가 없다면 정적 메서드로 변경하여도 동작한다.
const person = {
name: 'Lee',
address: 'Seoul',
};
console.log('name' in person); //true
console.log('age' in person); //false
console.log('toString' in person); //true
in연산자는 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인하므로 주의가 필요하다.
ES6에서 도입된 Reflect.has 메서드를 사용할 수도 있다.
const person = {
name: 'Lee',
address: 'Seoul',
};
console.log(Reflect.has(person, 'name')); //true
console.log(Reflect.has(person, 'toString')); //true
객체에 특정 프로퍼티가 존재하는지 확인할 수 있다.
const person = {
name: 'Lee',
address: 'Seoul',
};
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('toString')); // false
객체 고유의 프로퍼티 키인 경우에만 true를 반환한다.
객체의 모든 프로퍼티를 순회하며 열거하려면 for…in문을 사용한다.
for(변수선언문 in 객체) {...}
const person = {
name: 'Lee',
address: 'Seoul',
};
for (const key in person) {
console.log(key + ':' + person[key]);
}
// name:Lee
// address:Seoul
순환 대상 객체의 프로퍼티 뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거한다.
단, Object.prototype의 프로퍼티는 열거되지 않는다. 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false이기 때문이다. [[Enumerable]] 은 프로퍼티의 열거 가능 여부를 나타낸다.
프로퍼티 키가 심벌인 프로퍼티는 열거하지 않는다.
객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환
const person = {
name: 'Lee',
address: 'Seoul',
};
console.log(Object.keys(person)); //[ 'name', 'address' ]
객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환
const person = {
name: 'Lee',
address: 'Seoul',
};
console.log(Object.values(person)); //[ 'Lee', 'Seoul' ]
객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환
const person = {
name: 'Lee',
address: 'Seoul',
};
console.log(Object.entries(person)); //[ [ 'name', 'Lee' ], [ 'address', 'Seoul' ] ]