자바스크립트의 OOP
자바스크립트의 객체는 프로토타입 객체(Prototype object)와 연결(linked)되어 있다
이러한 연결을 ‘Prototypal inheritance’라는 용어로 일컫는다.
일반적인 OOP에서 객체(instance)는 클래스로부터 인스턴스화(instantiated) 된다고 표현한다.
프로토타입은 메소드(behavior)를 가지고 있으며, 프로토타입에 연결된 모든 Object는 이 메소드에 접근할 수 있다.
일반적인 OOP의 클래스의 상속 개념이랑은 다르니 참고하자.
자바스크립트 OOP에서 behavior는 프로토타입 오브젝트에 위임(delegated) 된다.
일반적인 OOP에서는 복사(copied) 된다.
자바스크립트에서 OOP를 하기 위해 프로토타입을 만드는 방법은 크게 세가지로 나눠볼 수 있다.
생성자 함수와 new 연산자(Constructor Functions and new Operator)
const Person = function (firstName, birthYear) {
// Instance properties
this.firstName = firstName;
this.birthYear = birthYear;
// // Never do this - copied to every instance
// this.calcAge = function () {
// console.log(2037 - this.birthYear);
// }
};
const jonas = new Person('Jonas', 1991);
console.log(jonas);
// 1. New {} is created
// 2. function is called, this = {}
// 3. {} linked to prototype
// 4. function automatically return {}
console.log(jonas instanceof Person); // true
생성자 함수와 new 연산자를 사용한 객체(인스턴스)의 생성은 네 단계로 이루어진다.
1. New empty object({}) is created
2. Constructor function is called, this keyword is set to the new object.
3. New object is linked to prototype(__proto__ property)
4. Function automatically return new object.
프로토타입
Person.prototype.calcAge = function () {
console.log(2037 - this.birthYear);
}
jonas.calcAge();
matilda.calcAge();
생성자 함수 내부에 메소드를 작성하면 모든 인스턴스에 해당 내용이 복사되므로 비효율적이다. prototype 프로퍼티를 통해 외부에서 메소드를 작성해주면 모든 인스턴스에서 접근이 가능하다.
console.log(jonas.__proto__);
console.log(jonas.__proto__ === Person.prototype); // true
console.log(Person.prototype.isPrototypeOf(jonas)); // true
__proto__ 프로퍼티를 사용하면 원형 프로토타입을 가리킬 수 있다.
.isPrototypeOf(인스턴스) 메소드는 해당 프로토타입의 인스턴스인지를 boolean 타입으로 리턴한다.
Person.prototype.species = 'Homo Sapienns'
console.log(jonas.hasOwnProperty('firstName')); // true
console.log(jonas.hasOwnProperty('species')); // false
.hasOwnProperty 메소드는 프로토타입 안에 명명된 Instance property만 true를 리턴한다.
Prototypal Inheritance
객체는 생성자 함수에서 작성한 프로퍼티나 메소드를 갖고 있지만, 호출된 프로퍼티나 메소드가 없으면 Prototype에서 찾아보게 된다. 따라서 Prototype에 설정된 calcAge 메소드가 호출될 수 있는 것이다.
Prototype Chain
Prototype 역시 Prototype을 가지고 있는데, 그것이 Object.prototype이다.
jonas.__proto__.__proto__; // Object.protytpe
이러한 객체 간 연결성을 Prototype Chain이라고 부른다. JS 엔진은 스코프 체인처럼 호출한 프로퍼티나 메소드가 없으면 상위 프로토타입에서 찾아보게 된다.
JS 빌트인 객체(e.g. array)의 prototypal inheritance
const arr = [3, 6, 4, 5, 6, 9, 3];
console.log(arr.__proto__);
Array.prototype.unique = function () {
return [...new Set(this)]
}
console.log(arr.unique());
array의 수많은 메소드를 사용할 수 있는 것은 Array.prototype이 가진 메소드를 호출할 수 있기 때문이다.
이러한 방식으로 모든 array가 사용할 메소드를 추가할 수 있다. 하지만 빌트인 오브젝트를 수정하는 것은 위험한 일이므로 사용하지 말자.
ES6 클래스
// class expressob
// const PersonCl = class {
// }
// class declaration
class PersonCl {
constructor(firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
}
// 프로토타입 프로퍼티로 추가되는 메소드
calcAge() {
console.log(2037 - this.birthYear);
}
}
const jessica = new PersonCl('Jessica', 1996);
console.log(jessica);
jessica.calcAge();
console.log(jessica.__proto__ === PersonCl.prototype);
JS의 클래스는 내부 작동 방식은 여전히 Prototype과 똑같은 방식이지만 신택스를 다른 언어처럼 좀 더 친숙하고 간결하게 사용할 수 있도록 등장한 개념이다.
메소드를 추가하고 싶으면 프로토타입의 메소드로 추가하지 않고, class 내부에서 선언하면 되므로 코드가 조금 더 간결해진다.
클래스는 호이스팅 되지 않으므로 선언 이전에 사용하지 못한다.
클래스는 일급 시민 객체(first-class citizens)이다.
클래스는 strict 모드에서 실행된다.
Setter / Getter
const account = {
owner: 'jonas',
movements: [200, 530, 120, 300],
// getter
get latest() {
return this.movements.slice(-1).pop();
},
// setter
set latest(mov) {
this.movements.push(mov);
}
}
// 사용할 때 프로퍼티처럼 사용할 수 있다.
console.log(account.latest);
account.latest = 50;
setter, getter를 사용하면 메소드의 형식으로 코드를 작성하지만, 호출할 때는 프로퍼티처럼 사용할 수 있다.
class PersonCl {
constructor(fullName, birthYear) {
this.fullName = fullName;
this.birthYear = birthYear;
}
// 프로토타입 프로퍼티로 추가되는 메소드
calcAge() {
console.log(2037 - this.birthYear);
}
// Set a property that already exist
set fullName(name) {
if (name.includes(' ')) this._fullName = name;
else alert((`${name} is not full name`));
}
get fullName() {
return this._fullName;
}
}
주로 Data validation을 해야할 때 사용한다. 이미 생성자 함수 내에 존재하는 프로퍼티명을 사용할 때의 패턴을 기억하자.
Static Methods
Array.from(document.querySelectorAll('h1'));
Number.parseFloat(12);
정적 메소드는 객체(인스턴스)가 아닌 클래스(프로토타입)에서 직접 호출할 수 있는 메소드를 의미한다.
Person.hey = function () {
console.log('Hey!');
}
class PersonCl {
constructor(fullName, birthYear) {
this.fullName = fullName;
this.birthYear = birthYear;
}
calcAge() {
console.log(2037 - this.birthYear);
}
static hey() {
console.log('Hey');
}
}
외부에서 직접 선언해주거나 static 키워드를 사용해서 내부에서 선언할 수 있다.
Object.create
const PersonProto = {
calcAge() {
console.log(2037 - this.birthYear);
},
init(firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
}
};
const steven = Object.create(PersonProto);
console.log(steven); // {} which has __proto__
steven.name = 'Steven';
steven.birthYear = 2002;
steven.calcAge();
console.log(steven.__proto__);
const sarah = Object.create(PersonProto);
sarah.init('Sarah', 1979);
sarah.calcAge();
Object.create는 생성자 함수나 클래스와는 다른 방식으로 프로토타입과 객체와 연결하는 방식이다.
Object.create를 사용하면 자동으로 .__proto__를 통해 PersonProto라는 프로토타입과 연결된다.
Related