객체 지향 프로그래밍 vs 절차 지향 프로그래밍
: 객체 지향 프로그래밍은 데이터와 기능을 한 곳에 묶어서 처리한다
: 속성과 메소드가 하나의 '객체'라는 개념에 포함
자바스크립트 내장 타입인 object와는 다르게 클래스라는 이름으로 부른다
프로그래밍 방법론
1. 절차 지향 프로그래밍
2. 객체 지향 프로그래밍
목표
1. 클래스와 인스턴스 : new, class
2. 객체 지향 프로그래밍 특징 이해
unshift 가 리턴하는 값은 배열의 length 를 리턴합니다
객체.메소드()
: 객체의 메소드 키에 대한 값으로 있는 함수를 호출한다
클래스
class ClassName{
constructor(property1, property2){
this.property1 = property1;
this.property2 = property2;
}
method(param){
console.log(param);
}
}
인스턴스
let instance = new ClassName('prop1', 'prop2');
instance.property1; //prop1
instance.method('param'); //param
cf. ES5은 메소드를 정의할 때 prototype이라는 원형 객체를 써야 했다
ex. 클래스.prototype.메소드 = function(){//body}
cf. this : 함수가 실행될 때 해당 scope마다 생성되는 고유한 실행 context
ex. new 키워드로 인스턴스 생성했을 때는 해당 인스턴스가 바로 this의 값이 된다
Array
배열은 모두 Array 클래스의 인스턴스다
let arr = new Array('el1', 'el2', 'el3');
//인스턴스 객체
arr.length //3
arr.push('el4')
//모두 메소드
절차적 언어
초기의 프로그래밍 언어 : 변수, 함수
순차적인 명령의 조합
객체 지향 언어
클래스라고 부르는 데이터 모델 이용 : 데이터와 기능을 한번에 묶어서 처리
현대 언어는 대부분 객체 지향
사람이 세계를 보고 이해하는 방법과 흡사하다
화면에 보이는 하나의 요소를 객체 단위로 구분시켜서 생각하면 보다 이해하기 쉬운 코드 작성 가능
OOP
프로그래밍 설계 철학 중 하나
OOP의 모든 것은 객체로 그룹화
이 객체는 한번 만들고 나면 메모리상에서 반환되기 전가지 객체 내의 모든 것이 유지된다
데이터와 기능이 함께 있다 : 메소드와 속성 존재
재사용성을 얻을 수 있다
OOP의 4가지 컨셉
1.캡슐화
vs. 절차적 코드
: 데이터의 형태가 바뀔 때에 코드의 흐름에 큰 영향을 미치게 되어 유지보수가 어렵다
2.추상화
3.상속
4.다형성
자바스크립트는 프로토타입 기반 언어다
프로토타입: 원형 객체
class Human {
**constructor(name, age)** {
this.name = name;
this.age = age;
}
**sleep()** {
console.log(`${this.name}은 잠에 들었습니다`);
}
> }
let kimcoding = new Human('김코딩', 30); //인스턴스
Human.prototype.constructor === Human; //true
Human.prototype === kimcoding.proto; //true
Human.prototype.sleep === kimcoding.sleep; //true
클래스의 프로토타입(원형 객체)
클래스의 프로토타입 = 인스턴스의 proto
cf. proto : 인스턴스의 클래스의 프로토타입
Human 클래스의 프로토타입의 생성자 함수 = Human 클래스
인스턴스의 proto = Human 클래스의 프로토타입
Human의 프로토타입의 메소드 = 인스턴스의 메소드
Must know concepts
객체 지향 프로그래밍의 특성 중 "상속"을 JavaScript에서 구현할 때에는 프로토타입 체인을 사용합니다. 예시를 들기 위해, 학생(Student)과 사람(Human)이라는 클래스가 각각 존재한다고 가정하겠습니다. 클래스 Human의 메소드와 속성을 객체로 구현한다면, 다음과 같습니다.
let kimcoding = new Human('김코딩', 30);
// 속성
kimcoding.age;
kimcoding.gender;
// 메소드
kimcoding.eat();
kimcoding.sleep();
[코드] 클래스 Human의 속성과 메소드 예시
학생은 학생이기 이전에, 사람입니다. 따라서 클래스 Student는 Human의 기본적인 메소드를 상속받을 수 있습니다. 다만, 학생은 일반적인 사람의 특징에 추가적인 특징이 필요합니다. 예를 들면 다음과 같습니다.
let parkhacker = new Student('박해커', 22);
// 속성
parkhacker.grade;
// 메소드
parkhacker.learn();
[코드] 클래스 Student의 속성과 메소드 예시
위 예시에서 나타나는 박해커(parkhacker) 씨는 Student입니다. 박해커 씨가 학생이라고 해서, age나 gender와 같은 속성이 존재하지 않거나, sleep() 이나 eat() 이라는 메소드를 사용할 수 없을까요? 그렇지 않습니다.
Student는 Human의 특징을 그대로 물려받습니다.
이렇게 속성과 메소드를 물려주는 클래스를 부모 클래스(여기서는 Human), 속성과 메소드를 물려받는 클래스를 자식 클래스(여기서는 Student), 그리고 이 과정을 상속이라고 합니다.
자바스크립트에서는 extends 와 super 키워드를 이용해서 상속을 구현할 수 있습니다. 다음 문서 JavaScript에서의 상속 중 "ECMAScript 2015 클래스"를 먼저 읽어 보고, 아래의 Action Items를 진행하세요.
"ECMAScript 2015 클래스"
ECMAScript 2015에서는 C++나 Java와 유사한 클래스 문법을 공개하여 클래스를 조금 더 쉽고 명확하게 재활용 할 수 있게 되었습니다. 이 절에서는 프로토타입 상속으로 작성한 Person과 Teacher 예제를 클래스 문법으로 변경하고 어떻게 동작하는지 설명하겠습니다.
Note: 대부분의 최신 브라우저에서 새로운 클래스 작성 방식을 지원합니다만 일부 구형 브라우저(Internet Explorer가 대표적)에서는 동작하지 않으므로 하위호환성을 위해 프로토타입 상속을 배워둘 필요가 있습니다.
Class-스타일로 재작성한 Person 예제를 보시죠:
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
greeting() {
console.log(`Hi! I'm ${this.name.first}`);
};
farewell() {
console.log(`${this.name.first} has left the building. Bye for now!`);
};
}
class 구문은 새로운 클래스를 작성함을 의미합니다. Class 블록 내에서 모든 기능을 정의할 수 있습니다.
constructor() 메소드는 Person 클래스의 생성자를 의미합니다.
greeting() and farewell()는 멤버 메소드입니다.
클래스의 메소드는 생성자 다음에 아무 메소드나 추가할 수 있습니다. 여기서는 읽기 쉬우라고 string 결합이 아닌 template literals을 사용했습니다.
이제 위에서 했듯이 new 연산자로 객체 인스턴스를 생성할 수 있습니다:
let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
han.greeting();
// Hi! I'm Han
let leia = new Person('Leia', 'Organa', 19, 'female' ['Government']);
leia.farewell();
// Leia has left the building. Bye for now
Note: 코드를 까보면 class 부분은 프로토타입 상속으로 변환이 됩니다. — 문법 설탕(syntactic sugar)의 일종인거죠. 하지만 읽기 쉽다는데 대부분 동의하실 겁니다.
class 문법으로 상속
위에서 사람을 나타내는 클래스를 만들었습니다. Person 클래스는 일반적인 사람이 가질 만한 특성들을 나열하고 있죠; 이 절에서는 Person을 class 문법으로 상속받아 Teacher 클래스를 만들 예정입니다. 이 작업을 하위 클래스 생성이라 부릅니다.
하위 클래스를 만드려면 Javascript에서 extends 키워드를 통해 상속 받을 클래스를 명시합니다.
super() 연산자를 안 쓰는 경우 :
class Teacher extends Person {
constructor(first, last, age, gender, interests, subject, grade) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
// subject and grade are specific to Teacher
this.subject = subject;
this.grade = grade;
}
}
super() 연산자를 쓰는 경우
:constructor()에서 첫번쨰로 super() 연산자를 정의하면 코드를 조금 더 읽기 쉬워집니다. 이는 상위 클래스의 생성자를 호출하며 super()의 매개변수를 통해 클래스의 생성자 실행해서 상위 클래스의 메소드, 속성을 상속받을 수 있는 코드입니다.
class Teacher extends Person {
**constructor(first, last, age, gender, interests, subject, grade)** {
**super(first, last, age, gender, interests);**
//생성자 함수에 모든 속성을 넣는다
//super에는 인자를 안 넣는 것이 좋다
// subject and grade are specific to Teacher
this.subject = subject;
this.grade = grade;
}
}
Teacher의 인스턴스를 생성하면 의도한대로 이제 Teacher와 Person 양 쪽의 메소드와 속성을 사용할 수 있습니다.
let snape = new Teacher('Severus', 'Snape', 58, 'male', ['Potions'], 'Dark arts', 5);
snape.greeting(); // Hi! I'm Severus.
snape.farewell(); // Severus has left the building. Bye for now.
snape.age // 58
snape.subject; // Dark arts
Person을 수정하지 않고 Teacher를 생성한 것처럼 또 다른 하위클래스도 생성할 수 있습니다.
Action Items
브라우저에서 DOM을 이용하면, document.createElement('DIV') 으로 새로운 div 엘리먼트를 만들 수 있습니다. 이렇게 생성된 div 엘리먼트는, HTMLDivElement라는 클래스의 인스턴스입니다.
모든 DOM 엘리먼트는 textContent와 같은 속성, 또는 append()와 같은 메소드가 있습니다. 각각의 모든 엘리먼트가 해당 메소드나 속성이 있다는 것을 통해, Element라는 공통의 부모 클래스가 있음을 짐작할 수 있습니다. 따라서 MDN의 문서에서는 다음과 같이 상속 관계를 함께 표현합니다.
div 엘리먼트의 클래스 모식도
[그림] div 엘리먼트의 클래스 모식도
화살표 방향은 부모 클래스를 가리킵니다. EventTarget의 부모로는, 모든 클래스의 조상인 Object가 존재합니다.
개발자 도구를 사용하면, 해당 메소드나 속성이 어떤 클래스로부터 비롯되었는지 확인할 수 있습니다.
개발자 도구에서 어떤 클래스로부터 비롯되었는지 확인할 수 있습니다
[그림] 개발자 도구에서 어떤 클래스로부터 비롯되었는지 확인할 수 있습니다.
위 그림에서 확인할 수 있듯이 innerText 속성은 HTMLElement로부터, textContent 속성은 Node로부터 비롯되었습니다.
인스턴스의 proto를 이용하면, 이를 더 확실하게 확인할 수 있습니다. proto를 이용하면 부모 클래스의 프로토타입, 혹은 '부모의 부모 클래스'의 프로토타입을 탐색할 수 있습니다.
질문
addEventListener 속성은 어떤 클래스의 프로토타입에서 찾을 수 있나요?
remove 메소드는 어떤 클래스의 프로토타입에서 찾을 수 있나요?
모든 객체에 toString() 메소드가 존재하는 이유가 무엇인가요?
ex. DOM에서 button.addEventListener를 쓸 수 있는 이유
: button 자체에는 addEventListener는 없지만
button의 프로토타입 : htmlbuttonElement
의 프로토타입 : htmlElement
의 프로토타입 : Element
의 프로토타입 : Node
의 프로토타입 : EventTarget (이벤트 객체)
의 메소드로 addEventListener가 있다
즉 아무리 상위클래스여도 다른 조작 없이 바로 속성, 메소드를 쓸 수 있다
자바스크립트는 이처럼 프로토타입 기반 언어다. 클래스를 프로토타입을 이용해서 구현한다
객체 지향의 장점
디버깅하기 쉽다
재활용성이 높고
코드를 이해하기 쉽다
객체 지향의 단점
메모리 사용량이 많고 속도가 느리다
면접의 단골 질문!! OOP의 4가지 특성
1. 추상화 - 내부 구현은 복잡하지만 실제로 노출되는 부분은 단순한 것 [겉은 단순, 안은 복잡]
다형성 - 똑같은 메소드라 하더라도 다른 방식으로 구현될 수 있음 [같은 하나로 다양한 형태]
ex. 같은 부모에서 상속하면 자식이 같은 메소드로 다양한 형태를 리턴하는 것
=> 같은 클래스에서 다양한 인스턴스를 만들 수 있는 것
캡슐화 - 데이터(속성)과 기능(메소드)를 하나의 객체 안에 넣어서 묶는 것 [객체(캡슐) = 속성 + 메소드 ]
안에 있는 성분들을 알 수 없다
cf. 캡슐화의 특성: 은닉화
#으로 private을 구현할 수 있고 getter setter을 이요하면 도니다
mdn에 나와있긴 한데 객체지향 prototype class가 가장 중요함
은닉화하는 방법
private은 자바에서 정말 많이 쓴다. js에서는 되도록 쓰지 말자
grub.privateFood로는 'candy'에 접근할 수 없다
cf. getter setter를 사용하면 은닉화된 부분에 접근할 수 있다
상속 - 부모 클래스의 특징을 자식 클래스가 물려받는 것 [자식 = 부모 + a]
sprint beesbeesbess
1. 다형성
부모 클래스 Grub
class Grub{
constructor(){
this.age =0;
this.color = 'pink';
this.food = 'jelly';
}
eat(){
return 'Mmmmmmm' + this.food;
}
}
자식 클래스 Bee
class Bee extends Grub{
constructor(){
super();
}
eat(){
return super.eat() + 'great'//Mmmmmmm jelly great'
다형성 : 부모의 eat()메소드를 가지고 자식 클래스에서 다양하게 만들 수 있다
}
}
프로토타입[원형 객체]
인스턴스에는 proto
클래스에는 prototype
프로토타입은 원형 객체다
클래스를 정의하면 자동 생성되고 프로토타입 안에 클래스의 생성자 함수, 메소드가 들어간다
=> 상속을 가능하게 해준다 ( 부모와 자식 연결)
ex. Object -prototype- Grub -prototype- Bee
1)인스턴스에서 프로토타입에 접근할 때 : 인스턴스.proto [이거 때문에 체이닝이 일어나는 것]
=> 한단계 높은 프로토타입을 찾아가는 것 ?? 이거 콘솔 찍어보기
2)클래스에서 프로토타입에 접근할 때 : 클래스.prototype
3) 프로토타입 체이닝
Ex.
bee에서 toString을 쓰면 프로토타입 체인 때문에 메소드를 넣어준다
bee.proto : Grub 안에 최상위 객체인 Object가 있다
[프로토타입 체이닝]
그러므로 최상위 객체인 Object의 메소드를 쓸 수 있다
ES5 function으로 구현해서 ES6 class로 구현
사실 함수라서 호이스팅이 가능하다
Grub.prototype.constructor === Grub
오잉??
prototype chaining
bee._proto_.proto=== Grub.prototype
[Bee.prototype]의 proto는 부모의 prototype
function Car(){
this.color = 'yellow'; 원래는 파라미터로 받아오는 게 더 일반적
}
constructor로 디폴트 파라미터를 정해주었으니 super(age, color)하면 그대로 받는다
super(age, color); //부모에서
BEST PRACTICE
이렇게 매개변수로 전달 받는 것이 맞다. 아예 값을 정해놓으면 다양하게 쓸 수가 없다.
ex. this.age = 5로 해놓으면 상속을 받았을 때 다른 값을 넣을 수가 없다
상속 받을 때
1) super()
: 매개변수 필요 없음
: 안에 전달받는 속성을 인자로 명시해도 되지만 부모 클래스의 순서와 맞춰서 명시해야 하기때문에 비추
: super 사용 시 프로토타입으로 연결된다
(속성, 메소드가 프로토타입에 들어 있다)
2) 하위 클래스에서 속성
(1) 상위 클래스의 속성을 받는다
: 하위 클래스의 constructor의 인자가 없어도 상속된다
: constructor를 안 쓰고 상속을 받으면 자동으로 부모의 constructor를 실행시킨다
하지만 안 쓰면 혼날 수 있다!!
constructor(){
super();를 써줘야 한다!
}
(2) prototype 객체를 연결한다
: 속성, 메서드를 받는다 그러므로 메소드 상속을 명시하지 않아도 된다
(3)grub1.prototype.eat()으로 쓰면 안된다
왜냐하면 this로 인스턴스에 연결했기 때문에 프로토타입에 연결이 안 되어 있다
(4) super로 상속받을 때 자식 클래스의 constructor 매개변수로 부모 클래스와 겹치는 속성이 있으면 인스턴스에서 넣은 값으로 갱신된다
즉 super로 상속받은 속성은 default값과 같은 역할을 한다
3) Grub.prototype.constructor === Grub
constructor에 class Grub가 들어간다
즉 클래스를 만들면 본인 prototype 안에 constructor로 본인 클래스가 들어간다