자바스크립트 객체지향 기본기
- Javascript
- 2021. 11. 1. 21:49

코드잇의 자바스크립트 객체지향 기본기 강의를 듣고 정리한 내용입니다.
문제가 될 시 삭제하겠습니다.
코드잇 자바스크립트 객체지향 기본기
1. 객체와 클래스
01. 객체 지향 프로그래밍이란 ?
- 객체 : 객체의 상태를 나타내는 변수와 함수가 있음
- 객체 지향 프로그래밍 : 프로퍼티와 메소드로 이루어진 각 객체들의 상호 작용을 중심으로 코드를 작성하는 것
- 절차 지향 프로그래밍 : 변수와 함수를 가지고 작업의 순서에 맞게 코드를 작성하는 것
02. 1-1. object literal
- Object Literal : 객체를 나타내는 문자열
- index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>자바스크립트 객체 지향</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
- index.js
console.log('test');
const user = {
// property 속성 : 객체의 상태를 담음
email: 'chris@google.com',
birthdate: '1991-05-11',
// method 함수
buy(item) {
console.log(`${this.email} buys ${item.name}`); //this 는 현재 객체의 email
},
};
const item = {
name: '스웨터',
price: 30000,
};
// 객체의 property 속성과, method 함수 실행
console.log(user.email); // chris@google.com
console.log(user.birthdate); // 1991-05-11
user.buy(item); // chris@google.com buys 스웨터
- 실행결과
test
index.js:21 chris@google.com
index.js:22 1991-05-11
index.js:12 chris@google.com buys 스웨터
03. 1-2. Factory function
- index.js
- 객체를 생성하는 Factory function을 만들고, 그 안에서 Object literal로 객체를 생성하여 리턴하는 방법입니다.
function createUser(email, birthdate) {
const user = {
// email property 와 email 파라미터가 같은 경우에는 email: email, -> email, 로 변경해도 됨
email: email,
birthdate: birthdate,
buy(item) {
console.log(`${this.email} buys ${item.name}`);
},
};
return user;
}
const item = {
name: '스웨터',
price: 30000,
};
// Factory Function
const user1 = createUser('minimi@google.com', '1991-05-11');
const user2 = createUser('test@google.com', '1995-01-11');
console.log(user1.email);
console.log(user2.email);
user1.buy(item);
user2.buy(item);
- 결과값
minimi@google.com
index.js:22 test@google.com
index.js:7 minimi@google.com buys 스웨터
index.js:7 test@google.com buys 스웨터
04. Constructor function
- 생성자 함수
// constructor function
// this는 해당 객체를 의미
// 객체 생성용 메서드는 User 처럼 맨 앞글자를 대문자로 함(관습)
function User(email, bitrhdate) {
this.email = email;
this.birthdate = birthdate;
this.buy = function (item) {
console.log(`${this.email} buys ${item.name}`);
};
}
const item = {
name: '스웨터',
price : 30000,
};
// new 를 붙여야 객체를 생성할 수 있음
const user1 = new User('minimi@gmail.com', '1991-03-21');
console.log(user1.email);
console.log(user1.birthdate);
user1.buy(item);
05. 객체 만들기3 : Class
class User {
// this를 사용하여 변수를 할당한다.
constructor(email, birthdate){
this.email = email;
this.birthdate = birthdate;
}
// 메소드는 constructor 안이 아닌 밖에 위치해아 한다.
buy(item) {
console.log(`${this.email} buys ${item.name}`);
}
}
const item = {
name: '스웨터',
price: 30000,
};
const user1 = new User('minimi@google.com', '1992-03-21');
console.log(user1.email);
console.log(user1.birthdate);
user1.buy(item);
const user2 = new User('minimi2@google.com', '1992-03-21');
console.log(user2.email);
console.log(user2.birthdate);
user2.buy(item);
2. 객체 지향 프로그래밍의 4개의 기둥
01. 추상화
추상화 : 어떤 구체적인 존재를 원하는 방향으로 간략화해서 나타내는 것
03. 캡슐화
캡슐화 : 객체의 특정 프로퍼티에 직접 접근하지 못하도록 막는 것
- setter메소드
class User {
constructor(email, birthdate) {
this.email = email;
this.birthdate = birthdate;
}
buy(item) {
console.log(`${this.email} buys ${item.name}`);
}
get email() {
return this._email;
}
set email(address) { // 그냥 email은 setter 메소드의 이름이 됨
if (address.includes('@')) { // 값에 대한 유효성 검사
this._email = address; // _email 에 address를 저장함
} else {
throw new Error('invalid email address');
}
}
}
const item = {
name: '스웨터',
price: 30000,
};
const user1 = new User('chris123@google.com', '1992-03-21')
user1.email = 'chris_robert@google.com';
console.log(user1._email); // -> getter 메소드(get email()) 메소드가 구현되어 있으면 console.log(user1.email); 로 사용 가능
04. 캡슐화 더 알아보기
1. 완벽한 캡슐화를 하는 법
이전 영상에서는 다음 코드로 캡슐화를 배웠습니다.
class User {
constructor(email, birthdate) {
this.email = email;
this.birthdate = birthdate;
}
buy(item) {
console.log(`${this.email} buys ${item.name}`);
}
get email() {
return this._email;
}
set email(address) {
if (address.includes('@')) {
this._email = address;
} else {
throw new Error('invalid email address');
}
}
}
const user1 = new User('chris123@google.com', '1992-03-21');
user1.email = 'newChris123@google.com';
console.log(user1.email);
이제 이 코드를 보면 _email 프로퍼티에 직접 접근하지 말고, email이라는 getter/setter 메소드로만 접근해야 한다는 것이 눈에 잘 보입니다. 하지만 사실 완벽한 캡슐화가 된 상태는 아닙니다. 왜냐하면 보호하려는 프로퍼티 _email에
console.log(user1._email);
user1._email = 'chris robert';
이런 식으로 여전히 직접 접근할 수는 있기 때문입니다.
사실 자바스크립트에는 캡슐화를 자체적으로 지원하는 문법이 아직 없습니다.(Java는 private이라는 키워드가 있어서 언어의 문법 차원에서 캡슐화를 지원합니다.)
하지만 JavaScript에서도 다른 방식으로 우회해서 완벽한 캡슐화를 할 수는 있는데요. 클로저(Closure)라고 하는 개념을 응용해서 적용하면 됩니다. 잠깐 아래 코드를 보세요.
function createUser(email, birthdate) {
let _email = email;
const user = {
birthdate,
get email() {
return _email;
},
set email(address) {
if (address.includes('@')) {
_email = address;
} else {
throw new Error('invalid email address');
}
},
};
return user;
}
const user1 = createUser('chris123@google.com', '19920321');
console.log(user1.email);
지금 이 코드를 보면 createUser라고 하는 Factory function이 보입니다. 그런데 생성하려는 user 객체 안에 _email 프로퍼티가 있는 게 아니라,
(1) createUser 함수 안에,
(2) 그리고 user 객체 바깥에 _email이라는 변수가 있죠?
대신에 user 객체 안에는 _email 변수의 값을 읽고 쓸 수 있는 email이라는 getter/setter 메소드가 있습니다.
지금 마지막 부분에서 createUser라는 Factory function으로 user1이라는 객체를 생성하고, user1 객체의 email getter 메소드를 호출했는데요. 이 코드의 실행 결과를 확인해보면,
이렇게 _email 변수의 값이 잘 출력됩니다. 함수 안의 변수의 값을 이미 리턴된 객체에서 읽은 건데요. 어떻게 이게 가능한 걸까요? 이것은 자바스크립트의 클로저(Closure)라고 하는 것 덕분에 가능합니다.
클로저란 자바스크립트에서 어떤 함수와 그 함수가 참조할 수 있는 값들로 이루어진 환경을 하나로 묶은 것을 의미하는데요. 예를 들어, 지금 createUser 함수가 실행되는 시점에 email이라는 getter/setter 메소드는 _email 이라는 변수의 값에 접근할 수 있는 상태입니다. 그리고 여기서 핵심은 이 email getter/setter 메소드들은 메소드를 갖고 있는 객체가 리턴된 이후더라도 여전히 _email에 접근하는 것이 가능하다는 점입니다. 바로 이렇게 함수가 정의된 당시에 참조할 수 있었던 변수들을 계속 참조할 수 있는 상태의 함수를 클로저라고 합니다. 이 클로저는 다른 프로그래밍 언어에서는 쉽게 찾아보기 힘든 자바스크립트만의 특징인데요.(물론 클로저 개념이 있는 다른 언어들도 있습니다)
보통 다른 프로그래밍 언어였다면 createUser 함수 내부가 실행될 때만 email getter/setter 메소드가 _email 변수에 접근할 수 있었겠지만, 자바스크립트에서는 클로저라는 개념으로 해당 환경을 함수와 함께 그대로 유지시켜주는 것입니다.
만약 클로저가 아닌 경우에는 _email 변수에 접근할 수 없습니다. 만약 이런 식으로
function createUser(email, birthdate) {
let _email = email;
const user = {
birthdate,
get email() {
return _email;
},
set email(address) {
if (address.includes('@')) {
_email = address;
} else {
throw new Error('invalid email address');
}
},
};
return user;
}
const user1 = createUser('chris123@google.com', '19920321');
console.log(user1._email);// _ 추가
user1 객체의 _email 프로퍼티에 접근하려고 하면, user1 객체 자체 내에는 _email이라고 하는 프로퍼티가 없고, 바깥의 _email 변수에 현재 접근할 수도 없기 때문에
undefined가 출력됩니다.
이런 식으로 자바스크립트에서는 클로저를 사용해서 완벽한 캡슐화를 할 수 있습니다. 신기하죠? 사실 자바스크립트로 프로그래밍을 할 때 캡슐화가 얼마나 중요한지, 꼭 해야하는지에 관해서는 논란이 많습니다. 하지만 어떤 상황이든 이런 식으로 완벽하게 캡슐화를 할 수 있다 정도는 알아두는 게 좋습니다.
2. 메소드도 캡슐화할 수 있어요
이때까지 우리는 프로퍼티를 보호하기 위해 getter/setter 메소드를 활용하거나, 좀더 완벽한 캡슐화를 위해 클로저를 사용할 수 있다는 것을 배웠습니다. 그런데 사실 프로퍼티 뿐만 아니라 메소드를 캡슐화하는 것도 가능합니다. 잠깐 이 코드를 볼까요?
function createUser(email, birthdate) {
const _email = email;
let _point = 0;
function increasePoint() {
_point += 1;
}
const user = {
birthdate,
get email() {
return _email;
},
get point() {
return _point;
},
buy(item) {
console.log(`${this.email} buys ${item.name}`);
increasePoint();
},
};
return user;
}
const item = {
name: '스웨터',
price: 30000,
};
const user1 = createUser('chris123@google.com', '19920321');
user1.buy(item);
user1.buy(item);
user1.buy(item);
console.log(user1.point);
저는 _point라는 변수를 추가했는데요. 사용자가 물건을 살 때마다 1포인트씩 적립해 줄 목적으로 만든 변수입니다. 그리고 point getter 메소드도 지금 정의해둔 상태입니다. _point 변수를 1씩 늘려주는 함수는 바로 밑에 보이는 increasePoint라는 함수입니다.
이 increasePoint 라는 함수는 유저 객체의 buy 메소드 안에서 쓰이고 있는데요. buy 메소드를 실행할 때 그 안에서 increasePoint 함수도 호출을 해주는 겁니다. 맨 마지막 부분의 코드들을 보면 user1 객체의 buy 메소드를 호출하고 point getter 메소드를 호출하고 있는데요. 이 코드를 실행해보면
이렇게 스웨터를 3번 구매했을 때, 포인트는 총 3점이 쌓이게 됩니다.
자, 여기서 중요한 점은 지금 increasePoint라는 함수가 보호받고 있는 함수라는 점입니다. 지금 user1 객체로 바로 increasePoint 함수를 호출할 수는 없습니다. 호출하려고 하면
function createUser(email, birthdate) {
const _email = email;
let _point = 0;
function increasePoint() {
_point += 1;
}
const user = {
birthdate,
get email() {
return _email;
},
get point() {
return _point;
},
buy(item) {
console.log(`${this.email} buys ${item.name}`);
increasePoint();
},
};
return user;
}
const item = {
name: '스웨터',
price: 30000,
};
const user1 = createUser('chris123@google.com', '19920321');
user1.buy(item);
user1.buy(item);
user1.buy(item);
console.log(user1.point);
user1.increasePoint();// user1 객체로 increasePoint 직접 호출
이렇게 그런 함수가 없다는 에러가 출력됩니다. 왜냐하면 user1 객체에는 increasePoint라는 메소드가 없기 때문입니다. 지금 저는 increasePoint가 유저 객체 안에서 적절한 곳에 사용되어야 하고, 아무렇게나 함부로 호출해서는 안 되는 메소드라고 가정하고 이렇게 캡슐화를 한 것입니다. 이런 식으로 메소드(정확하게 말하자면 increasePoint가 메소드는 아니니까 함수라고 할 수 있겠죠?)도 프로퍼티와 마찬가지로 클로저를 통해 캡슐화를 해서 보호할 수 있다는 사실, 잘 기억하세요.
06. 상속
- 자식이 부모의 프로퍼티와 메소드를 물려받을 때
- 코드의 재사용 성이 좋아진다.
class User {
constructor(email, birthdate) {
this.email = email;
this.birthdate = birthdate;
}
buy(item) {
console.log(`${this.email} buys ${item.name}`);
}
}
class PremiumUser extends User {
constructor(email, birthdate, level) {
this.level= level;
}
streamMusicForFree() {
console.log(`Free music streaming for ${this.email}`);
}
}
const item = {
name: '스웨터',
price: 30000,
}
const pUser1 = new PremiumUser('chris123@googl.com', '1992-03-21')
console.log(pUser1.email);
console.log(pUser1.birthdate);
console.log(pUser1.level);
pUser1.buy(item);
pUser1.streamMusicForFree();
07. super
- 자식 클래스의 생성자 함수 안에서 부모클래스의 생성자 함수를 먼저 실행해주어야 함
class User {
constructor(email, birthdate) {
this.email = email;
this.birthdate = birthdate;
}
buy(item) {
console.log(`${this.email} buys ${item.name}`);
}
}
class PremiumUser extends User {
constructor(email, birthdate, level) {
super(email, birthdate);
this.level= level;
}
streamMusicForFree() {
console.log(`Free music streaming for ${this.email}`);
}
}
const item = {
name: '스웨터',
price: 30000,
}
const pUser1 = new PremiumUser('chris123@googl.com', '1992-03-21')
console.log(pUser1.email);
console.log(pUser1.birthdate);
console.log(pUser1.level);
pUser1.buy(item);
pUser1.streamMusicForFree();
09. 다형성
- 다형성 : 많은 형태를 갖고 있는 성질
class User {
constructor(email, birthdate) {
this.email = email;
this.birthdate = birthdate;
}
buy(item) {
console.log(`${this.email} buys ${item.name}`);
}
}
class PremiumUser extends User {
constructor(email, birthdate, level) {
super(email, birthdate);
this.level= level;
}
streamMusicForFree() {
console.log(`Free music streaming for ${this.email}`);
}
buy(item) {
console.log(`${this.email} buys ${item.name} with a 5% discount`);
}
}
// 오버라이딩 (overriding)
const item = {
name: '스웨터',
price: 30000,
}
const user1 = new User('sdf@google.com', '19910511');
const pUser1 = new PremiumUser('asd@google.com', '19911210');
user1.buy(item);
pUser1.buy(item);
10. 부모 클래스의 메소드가 필요하다면?
- super 키워드를 사용한다.
class User {
constructor(email, birthdate) {
this.email = email;
this.birthdate = birthdate;
}
buy(item) {
console.log(`${this.email} buys ${item.name}`);
}
}
class PremiumUser extends User {
constructor(email, birthdate, level, point) {
super(email, birthdate);
this.level= level;
this.point = point;
}
streamMusicForFree() {
console.log(`Free music streaming for ${this.email}`);
}
buy(item) {
super.buy(item);
this.point += item.price * 0.05;
}
}
12. instanceof 연산자
- instanceof 메소드로 어느 클래스로 만든 객체인지를 확인할 수 있음
- 상속받아서 만든 객체는 부모클래스 객체임을 확인할 수 있다.
const users = [user1, pUser1, user2, pUser2];
users.forEach(user) => {
console.log(user instanceof User);
});
13. static 프로퍼티와 static 메소드
- static 프로퍼티
- static 메소드
클래스에 직접적으로 딸려있는 프로퍼티와 메소드
객체가 아닌 클래스 자체로 접근 !
class Math {
static PI = 3.14;
static getCircleArea(radius) {
return Math.Pi * radius * radius;
}
}
Math.PI = 3.141592;
Math.getRectangleArea = function (width, height) {
return width * height;
}
console.log(Math.PI); // 3.14
console.log(Math.getCircleArea(5)); // 78.5