1. 객체와 클래스
객체 지향 프로그래밍: 객체 간의 상호작용을 중심으로 하는 프로그래밍
➡️ 객체 = 프로퍼티(객체의 상태) + 메소(객체의 행동)
//Object Literal
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,
};
// Factory Function
function createUser(email, birthdate) {
const user = {
email: email,
birthdate: birthdate,
buy(item) {
console.log(`${this.email} buys ${item.name}`);
},
};
return user;
}
const item = {
name: '스웨터',
price: 30000,
};
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);
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
'Programming Languge > JavaScript' 카테고리의 다른 글
[Codeit;] 쿠키, 세션 스토리지, 로컬 스토리지 이해하기 (0) | 2024.07.31 |
---|---|
[Codeit;] 메타 태그 이해하기 (0) | 2024.07.31 |
[Codeit;] 인터랙티브 자바스크립트 (0) | 2024.07.31 |
[Codeit;] CSS 레이아웃 (0) | 2024.07.30 |
[Codeit;] 모던 자바스크립트 (0) | 2024.06.23 |