on
자바스크립트 객체지향 기본기
자바스크립트 객체지향 기본기
코드잇의 자바스크립트 객체지향 기본기 강의를 듣고 정리한 내용입니다.
문제가 될 시 삭제하겠습니다.
코드잇 자바스크립트 객체지향 기본기
1. 객체와 클래스
01. 객체 지향 프로그래밍이란 ?
객체 : 객체의 상태를 나타내는 변수와 함수가 있음
객체 지향 프로그래밍 : 프로퍼티와 메소드로 이루어진 각 객체들의 상호 작용을 중심으로 코드를 작성하는 것
절차 지향 프로그래밍 : 변수와 함수를 가지고 작업의 순서에 맞게 코드를 작성하는 것
02. 1-1. object literal
Object Literal : 객체를 나타내는 문자열
index.html
자바스크립트 객체 지향
index.js
console.log('test'); const user = { // property 속성 : 객체의 상태를 담음 email: '[email protected]', 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); // [email protected] console.log(user.birthdate); // 1991-05-11 user.buy(item); // [email protected] buys 스웨터
실행결과
test index.js:21 [email protected] index.js:22 1991-05-11 index.js:12 [email protected] 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('[email protected]', '1991-05-11'); const user2 = createUser('[email protected]', '1995-01-11'); console.log(user1.email); console.log(user2.email); user1.buy(item); user2.buy(item);
결과값
[email protected] index.js:22 [email protected] index.js:7 [email protected] buys 스웨터 index.js:7 [email protected] 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('[email protected]', '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('[email protected]', '1992-03-21'); console.log(user1.email); console.log(user1.birthdate); user1.buy(item); const user2 = new User('[email protected]', '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('[email protected]', '1992-03-21') user1.email = '[email protected]'; 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('[email protected]', '1992-03-21'); user1.email = '[email protected]'; 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('[email protected]', '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 메소드를 호출했는데요. 이 코드의 실행 결과를 확인해보면,
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId;=4462&directory;=Untitled.png&name;=Untitled.png
이렇게 _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('[email protected]', '19920321'); console.log(user1._email);// _ 추가
user1 객체의 _email 프로퍼티에 접근하려고 하면, user1 객체 자체 내에는 _email 이라고 하는 프로퍼티가 없고, 바깥의 _email 변수에 현재 접근할 수도 없기 때문에
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId;=4462&directory;=Untitled%201.png&name;=Untitled+1.png
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('[email protected]', '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 메소드를 호출하고 있는데요. 이 코드를 실행해보면
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId;=4462&directory;=Untitled%202.png&name;=Untitled+2.png
이렇게 스웨터를 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('[email protected]', '19920321'); user1.buy(item); user1.buy(item); user1.buy(item); console.log(user1.point); user1.increasePoint();// user1 객체로 increasePoint 직접 호출
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId;=4462&directory;=Untitled%203.png&name;=Untitled+3.png
이렇게 그런 함수가 없다는 에러가 출력됩니다. 왜냐하면 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('[email protected]', '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('[email protected]', '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('[email protected]', '19910511'); const pUser1 = new PremiumUser('[email protected]', '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
from http://minimilab.tistory.com/78 by ccl(A) rewrite - 2021-11-01 22:01:36