자바스크립트는 객체지향언어 이며 자바스크립트를 이루고 있는 거의 모든 것이 객체다.
객체지향 프로그래밍은 실체를 인식하는 철학적 사고를 프로그래밍에 접목하려는 시도에서 시작하며 실체는 특징이나 성질을 나타내는 속성(attribute/poperty)를 가지고 있다.
속성중에서 필요한 속성만을 추려내어 표현하는 것을 추상화(abstraction)라 한다.
const circle = {
radius: 5, // 프로퍼티
getDiameter() { // 메서드
return 2 * this.radius;
}
};
console.log(circle.radius); // 5
console.log(circle.getDiameter) // 10
위의 예시처럼 객체지향 프로그래밍은 객체의 상태를 나타내는 프로퍼티와 상태 데이터를 조작할 수 있는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조라고 할 수 있다.
상속(ingeritance)은 객체지향 프로그래밍의 핵심 개념으로, 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는것을 말한다.
function Circle(radius) {
this.radius = radius;
}
// 프로토타입을 사용해 상위(부모) 객체 역할을 하는
// Circle.prototype의 모든 프로퍼티와 메서드를 상속받는다
Circle.prototype.getArea = function () {
return Math.PI * Math.pow(this.radius, 2);
}
const myCircle = new Circle(10);
console.log(myCircle.getArea());
__proto__
접근자 프로퍼티모든 객체는 __proto__
접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[prototype]]
내부에 간접적으로 접근할 수 있다.
__proto__
접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
non-constructor인 화살표 함수와 ES6 메서드 축약표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 안으며 프로토타입도 생성하지 않는다.
구분 | 소유 | 값 | 사용 주체 | 사용 목적 |
---|---|---|---|---|
__proto__ 접근자 프로퍼티 | 모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용 |
prototype 프로퍼티 | constructor | 프로토타입의 참조 | 생성자 함수 | 생성자 함수가 자신의 생성할 객체(인스턴스)의 프로토타입을 할당하기위해 사용 |
프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.
생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
전역객체란?
전역 객체는 코드가 실행되기 이전 단계에서 자바스크립트 엔진에 의해 생성되는 특수한 객체이다. 브라우저 내에서는 window, node.js 내에서는 global 객체를 가리킨다.
이처럼 객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어 존재하며 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[prototype]]
내부 슬롯에 할당된다.
객체는 다음과 같은 다양한 생성 방법이 있다.
const obj = { x: 1 };
객체 리터럴에 의해 생성된 obj 객체는 Object.prototype을 프로토타입으로 갖게 된다.
마찬가지로 생성자 함수에 의해 생성된 객체 역시 Object.prototype이다.
const obj = new Object();
obj.x = 1;
new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 생성 방식과 마찬가지로 추상 연상 OrdinaryObjectCreate가 호출된다.이떄, 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee')
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hello ${this.name}`);
}
const me = new Person('Jeong');
// hasOwnProperty는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty('name')); // true
Object.getPrototypeOf(me) === Person.prototype; // true
me 객체는 Person.prototype 뿐만 아니라 Object.prototype도 상속받는다.
자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[prototype]]
내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라 한다. 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즙이다.
프로토타입 체인은 상속과 프로퍼티 검색을 위한 매커니즘이며 스코프 체인은 식별자 검색을 위한 매커니즘 이라고 할 수 있다.
오버라이딩이란 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식이다.
const Person = (function () {
function Person(name) {
this.name = name;
}
// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
sayHello() {
console.log(`Hello, ${this.name}`);
}
};
return Person;
}());
const me = new Person('Jeong');
console.log(me.constructor == Person); // false
console.log(me.constructor == Object); // true
프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
const Person = (function () {
function Person(name) {
this.name = name;
}
// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor: Person,
sayHello() {
console.log(`Hello, ${this.name}`);
}
};
return Person;
}());
const me = new Person('Jeong');
console.log(me.constructor == Person); // true
console.log(me.constructor == Object); // false
프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살린다.
function Person(name) {
this.name = name;
}
const me = new Person('Jeong');
// 프로토타입으로 교체할 객체
const parent = {
sayHello() {
console.log(`Hello ${this.name}`);
}
};
// me 객체의 프로토타입을 parent 객체로 교체한다.
Object.setPrototypeOf(me, parent);
// 위 코드는 아래의 코드와 동일하게 동작한다.
// me.__proto__ = parent;
me.sayHello(); // Hello Jeong
console.log(me.constructor === Person) // false
console.log(me.constructor === Object) // true
앞의 19.9.1절과 같이 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
function Person(name) {
this.name = name;
}
const me = new Person('Jeong');
// 프로토타입으로 교체할 객체
const parent = {
// constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor: Person,
sayHello() {
console.log(`Hello ${this.name}`);
}
};
// 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결을 설정
Person.prototype = parent;
// me 객체의 프로토타입을 parent 객체로 교체한다.
Object.setPrototypeOf(me, parent);
// 위 코드는 아래의 코드와 동일하게 동작한다.
// me.__proto__ = parent;
me.sayHello(); // Hello Jeong
console.log(me.constructor === Person) // false
console.log(me.constructor === Object) // true
객체 instanceof 생성자 함수
우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 ture로 평가되고, 그렇지 않은 경우에는 false로 평가된다.
생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다.
const person = {
name: 'Lee',
address: 'Seoul'
};
console.log('name' in person) // true
console.log('age' in person) // false
console.log(Reflect.has(person, 'name')) // true
console.log(Reflect.has(person, 'toString')) // true
in 연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인한다.
ES6에서 도입된 Reflect.has 메서드는 in연산자와 동일하게 사용 가능하다.
객체의 모든 프로퍼티를 순회하며 열거하려면 for ... in 문을 사용한다.
const person = {
name: 'Jeong',
address: 'Ulsan'
__proto__: { age: 20 }
};
for (const key in person) {
console.log(key + ':' + person[key]);
}
// name: Jeong
// address: Ulsa
// age: 20
const person = {
name: 'Jeong',
address: 'Ulsan'
__proto__: { age: 20 }
};
console.log(Object.keys(person)); // ["name", "address"]
console.log(Object.values(person)); // ["Jeong", "Ulsan"]
console.log(Object.entries(person)); // ["name", "address"], ["Jeong", "Ulsan"]