__proto__
)객체 지향 프로그래밍(OOP, Object-oriented programming)
객체 지향 프로그래밍을 철저하게 적용한 프로그래밍 언어 : Java, C#
자바스크립트에서 OOP를 구현하는 방법은 조금 독특
이번 유닛에서는 자바스크립트로 직접 객체 지향 프로그래밍을 구현하며, 객체 지향 프로그래밍의 장점과 각종 용어들을 이해합니다.
var elements = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
// 이 문장은 배열을 반환함: [8, 6, 7, 9]
elements.map(function(element) {
return element.length;
});
// 위의 일반적인 함수 표현은 아래 화살표 함수로 쓸 수 있다.
elements.map((element) => {
return element.length;
}); // [8, 6, 7, 9]
// 파라미터가 하나만 있을 때는 주변 괄호를 생략할 수 있다.
elements.map(element => {
return element.length;
}); // [8, 6, 7, 9]
// 화살표 함수의 유일한 문장이 'return'일 때 'return'과
// 중괄호({})를 생략할 수 있다.
elements.map(element => element.length); // [8, 6, 7, 9]
// 이 경우 length 속성만 필요하므로 destructuring 매개변수를 사용할 수 있다.
// 'length'는 우리가 얻고자 하는 속성에 해당하는 반면,
// lengthFooBArX'는 변경 가능한 변수의 이름일 뿐이므로
// 원하는 유효한 변수명으로 변경할 수 있다.
// 내 설명 :: { length: lengthFooBArX } = el 인데 구조분해로
//({}의 length 키의 값)lengthFooBArX = (el의 length 키의 값)el.length 이 된다.
elements.map(({ length: lengthFooBArX }) => lengthFooBArX); // [8, 6, 7, 9]
// destructuring 파라미터 할당도 아래와 같이 작성할 수 있습니다.
// 이 예에서 정의한 객체내의 'length'에 값을 지정하지 않은 점에 주목하세요.
// 대신, "length" 변수의 리터럴 이름은 우리가 해당 객체에서 꺼내오고 싶은
// 속성이름 자체로 사용됩니다.
// 내 설명 :: 객체 구조 분해에서 var {p, q} = {p: 42, q: true}; 이면
// console.log(p); // 42 이다.
// 따라서 { length } = el의 {length: ~~} = el.length 이다.
elements.map(({ length }) => length); // [8, 6, 7, 9]
객체.메소드()
let counter1 = {
value: 0,
increase: function() {
this.value++ // 메소드 호출을 할경우, this는 counter1을 가리킴.
},
decrease: fuction() {
this.value--
},
getValue: function() {
return this.value
}
}
counter1.increase()
counter1.increase()
counter1.increase()
counter1.decrease()
counter1.getValue() // 2
counter1
을 여러개 만들 수 있다. function makeCounter() {
return {
value: 0,
increase: function() {
this.value++ // 메소드 호출을 할 경우, this는 makeCounter 함수가 리턴하는 익명의 객체입니다
},
decrease: function() {
this.value--
},
getValue: function() {
return this.value;
}
}
}
let counter1 = makeCounter()
counter1.increase()
counter1.getValue() // 1
let counter2 = makeCounter()
counter2.decrease()
counter2.decrease()
counter2.getValue() // -2
class Counter {
constructor() {
this.value = 0; // 생성자 호출을 할 경우, this는 new 키워드로 생성한 Counter의 인스턴스입니다
}
increase() {
this.value++
}
decrease() {
this.value--
}
getValue() {
return this.value
}
}
let counter1 = new Counter() // 생성자 호출
counter1.increase()
counter1.getValue() // 1
: 하나의 모델이 되는 청사진(blueprint)을 만들고, 그 청사진을 바탕으로 한 객체(object)를 만드는 프로그래밍 패턴
class
(생성자(constructor) 함수)인스턴스가 만들어질 때 실행되는 코드. 속성과 메소드를 정의한다.
(ES5 : 함수) 일반적인 함수를 정의하듯 만든다.
:function Car(color) { //인스턴스가 만들어질 때 실행되는 코드}
(ES6 : 생성자(constructor) 함수) : class
, constructor
이용
:class Car { constructor(color){//인스턴스가 만들어질 때 실행되는 코드 } }
instance
(인스턴스, instance object, 인스턴스 객체)let avante = new Car('blue');
: 객체지향 프로그래밍에서 생성자(constructor) 함수라고 불림. class 키워드 사용.
function Car(brand, name, color){
// 인스턴스가 만들어질 때 실행되는 코드
}
class Car{
constructor(brand, name, color){
// 인스턴스가 만들어질 때 실행되는 코드
// - class : 속성의 정의
// - this:인스턴스 객체를 의미. this에 할당한다는 것은,
// 만들어진 인스턴스에 해당 브랜드, 이름, 색상을 부여하겠다는 의미
// - parameter : 브랜드, 이름, 색상 등은 인스턴스 생성시 지정하는 값
this.brand = brand;
this.name = name;
this.color = color;
}
// 메소드 정의 :
refuel(){}
drive(){}
}
}
let avante = new Car ('hyundai', 'avante', 'black');
avante.color ; //black
avante.drive(); // 아반떼가 운전을 시작합니다.
// - 인스턴스는(let avante) Car라는 클래스의 고유한 속성과 메소드를 갖는다.
// - 인스턴스를 만들 때에는 new 키워드를 사용. -> 즉시 생성자 함수가 실행되며,
// 변수에 클래스의 설계를 꼭 닮은 새로운 객체, 즉 인스턴스가 할당.
OOP는 프로그램 설계 철학
.
OOP의 모든 것은 "객체"로 그룹화
됨.
OOP의 4가지 주요 개념을 통해 재사용
가능.
: 이 객체는 한번 만들고 나면, 메모리상에서 반환되기 전까지 객체 내의 모든 것이 유지.
데이터와 기능이 함께 있다 : 객체 내에는 메소드와 속성이 존재.
클래스 : 일종의 원형(original form)으로, 객체를 생성하기 위한 아이디어나 청사진. 세부사항이 들어가지 않은 것.
생성자(constructor) : 클래스는 객체를 만들기 위한 생성자(constructor)함수를 포함.
생성자를 통해 세부 사항(속성) 을 넣어줌. 함수에 인자를 넣듯, 속성을 넣는다.
인스턴스 : 클래스의 사례(instance object). 클래스를 통해 만들어진 객체.
: 애플리케이션을 만들 때 좋은 설계를 하기 위해서는, 기본적으로 이 객체지향을 이해하고 응용하는 것이 중요. 객체 지향 프로그래밍의 4가지 기본적 컨셉
1. Encapsulation (캡슐화)
: 데이터(속성)와 기능(메소드)을 하나의 단위(하나의 객체)로 묶는 것
캡슐화 특징 :
은닉화(hiding) : 구현은 숨기고, 동작은 노출시킴. 디테일한 구현(내부 데이터나 내부 구현)이나 데이터는 숨기고, 객체 외부에서 필요한 동작(메소드)만 노출. 은닉화의 특징을 살려서 코드를 작성하면 객체 내 메소드의 구현만 수정하고, 노출된 메소드를 사용하는 코드 흐름은 바뀌지 않도록 만드는 것이 가능. 더 엄격한 클래스는 속성의 직접적인 접근을 막고, 설정하는 함수(setter), 불러오는 함수(getter)를 철저하게 나누기도 한다.
느슨한 결합(Loose Coupling) 에 유리: 언제든 구현을 수정할 수 있음 . 코드 실행 순서에 따라 절차적으로 코드를 작성하는 것이 아니라, 코드가 상징하는 실제 모습과 닮게 코드를 모아 결합하는 것을 의미. 느슨한 결합을 추구하는 코드 작성법은 코드만 보고도 인스턴스 객체의 기능을 상상할 수 있게 작성하는 것.
캡슐화와 은닉화의 차이점
: 속성의 접근을 제어 하는 것을 은닉화, 메서드의 내부를 알지 못하도록 하는 것을 캡슐화.
은닉화 : 외부에서 객체의 속성을 함부로 접근하지 못하도록 하는 것. 중요사항이(변수든 메소드든 간에) 밖으로 드러나지 않도록 꼭꼭 감추는것.
캡슐화 : 메서드 안에서 어떠한 일이 일어나고 있는지 모르게 해야한다는 것. 중요사항을 감춘 상태에서 외부에 그것을 사용할수 있는 방법을 설정하고 외부와 직접적으로 의사소통을 의미.
2. Inheritance (상속)
: 상속은 부모 클래스의 특징을 자식 클래스가 물려받는 것. "기본 클래스(base class)의 특징을 파생 클래스(derive class)가 상속받는다"
3. Abstraction (추상화)
: 내부 구현은 아주 복잡한데, 실제로 노출되는 부분은 단순하게 만든다는 개념. 밖에 나온 부분인 인터페이스만 접근할 줄 알면 된다.
예 ) 전화라는 객체가 있다면, 그 안에는 스피커와 마이크가 존재하고, 서킷 보드 등이 존재하는 등 내부 구현이 되어있다. 실제로 사용할 때에는, 이러한 존재에 대해서는 생각하지 않고 단순히 수화기를 들고 버튼을 눌러서 해결하는 것으로 인터페이스(interface)를 단순화 가능하다.
추상화를 통해 인터페이스가 단순해짐. 너무 많은 기능들이 노출되지 않아서 예상치 못한 사용상의 변화가 일어나지 않도록 만들 수 있음.
추상화와 캡슐화 비교
추상화의 본질
: 클래스 정의 시, 메소드와 속성만 정의한 것을 인터페이스라고 부른다. 이것이 추상화의 본질이다.
4. Polymorphism (다형성)
: 똑같은 메소드라 하더라도, 다른 방식으로 구현되는 것.
예 ) HTML 엘리먼트(Textarea(TextBox), Select, Checkbox) 구현 시 메소드 render로 화면에 뿌려진다고 할 때, 공통의 부모인 HTML Element라는 클래스에 render 라는 메소드가 조금씩 다르게 작동함.
-> TextBox는 가로로 긴 네모 상자와 커서가 있는 형태, Select 박스는 눌렀을 때 선택지가 나온다. 같은 이름을 가진 메소드라도 조금씩 다르게 작동하는 것이 바로 다형성입니다.
-> 만일 언어 자체에서 다형성을 제공하지 않는다면, 기본(부모) 클래스에 종류별로 분기를 시켜서 하나하나 다르게 만들어야 할 것이다.
-> 또는 각각의 자식 클래스의 별도의 각기 다른 render 함수를 만들 수도 있겠지만, 엘리먼트라는 클래스의 본질상 "화면에 뿌린다"(render)는 개념은 부모가 갖고 있는 것이 합리적이다.
: 객체 지향 프로그래밍 패러다임은 사람이 세계를 보고 이해하는 방법과 매우 흡사하다. 코드 상에서, 혹은 화면에 보이는 하나의 요소를 객체 단위로 구분시켜서 생각하면 보다 이해하기 쉬운 코드를 작성할 수 있게 됩니다. OOP의 특성을 이해하고 잘 사용하면 좋은 설계를 할 수 있습니다.
질문
- 추상화와 캡슐화는 어떻게 다른가요?
: 캡슐화는 은닉화(보안)에 집중. 추상화는 단순화(사용의 편리)에 집중.- 인터페이스(Interface)란 무엇일까요?
: 클래스 정의 시, 메소드와 속성만 정의한 것을 인터페이스라고 부릅니다. 이것이 추상화의 본질- JavaScript에서 class 키워드를 사용하면, 메소드의 은닉이 가능한가요?
: 이전의 함수 중심적인 구조적 프로그래밍 언어에서는 프로그램 내부에서 데이터가 어디서 어떻게 변경되는지 파악하기 어려웠고, 그로 인해 유지 보수가 힘들었기 때문에 자료를 중심으로 함수가 종속되는 구조가 되기도 하였다. 객체 지향에서는 클래스 내부의 데이터를 외부에서 참조하지 못하도록 차단하여 이러한 폐단을 없앨 수 있다. 이렇게 내부의 데이터나 함수를 외부에서 참조하지 못하도록 차단하는 개념을 정보 은닉(Information Hiding)이라고 하며 이것이 바로 캡슐화라는 개념이다.
class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
sleep() {
console.log(`${this.name}은 잠에 들었습니다`);
}
}
let kimcoding = new Human('김코딩', 30);
// 실습해보세요
// 모든 생성자 함수는 constructor 속성을 지닌 객체를 프로토타입 객체로 가짐.
// 이 constructor 속성은 원본 생성자 함수 자신을 가리키고 있다.
Human.prototype.constructor === Human; // true
Human.prototype === kimcoding.__proto__; // true
Human.prototype.sleep === kimcoding.sleep; // true
Human이라는 클래스와 인스턴스, 그리고 프로토타입의 관계
Array(배열) 클래스와 인스턴스, 그리고 프로토타입의 관계
: Array 클래스의 인스턴스로 생각할 수 있고, 프로토타입에 다양한 메소드가 존재
: 객체 지향 프로그래밍의 특성 중 "상속"을 JavaScript에서 구현할 때에는 프로토타입 체인을 사용
// < 클래스 Human의 속성과 메소드 예시 >
let kimcoding = new Human('김코딩', 30);
// 속성
kimcoding.age;
kimcoding.gender;
// 메소드
kimcoding.eat();
kimcoding.sleep();
학생은 학생이기 이전에, 사람입니다. 따라서 클래스 Student는 Human의 기본적인 메소드를 상속받을 수 있습니다. 다만, 학생은 일반적인 사람의 특징에 추가적인 특징이 필요합니다. 예를 들면 다음과 같습니다.
// < 클래스 Student의 속성과 메소드 예시 >
let parkhacker = new Student('박해커', 22);
// 속성
parkhacker.grade;
// 메소드
parkhacker.learn();
위 예시에서 나타나는 박해커(parkhacker) 씨는 Student입니다. 박해커 씨가 학생이라고 해서, age나 gender와 같은 속성이 존재하지 않거나, sleep() 이나 eat() 이라는 메소드를 사용할 수 없을까요? 그렇지 않습니다. Student는 Human의 특징을 그대로 물려받습니다.
-> 이렇게 속성과 메소드를 물려주는 클래스를 부모 클래스(여기서는 Human), 속성과 메소드를 물려받는 클래스를 자식 클래스(여기서는 Student), 그리고 이 과정을 상속이라고 합니다.
extends
: 상속 받을 클래스를 명시.super
: constructor()에서 첫번째로 super() 연산자를 정의하면 코드를 조금 더 읽기 쉽다. 이는 상위 클래스의 생성자를 호출, super()의 매개변수를 통해 상위 클래스의 멤버를 상속받을 수 있는 코드.// extends 를 이용한 Person 상속
class Teacher extends Person {
constructor(first, last, age, gender, interests, subject, grade) {
//super를 이용한 상위 class Person의 속성값(Person의 constructor)
super(first, last, age, gender, interests);
// subject and grade are specific to Teacher
this.subject = subject;
this.grade = grade;
}
}
브라우저에서 DOM을 이용하면, document.createElement('DIV')
으로 새로운 div 엘리먼트
를 만들 수 있습니다. 이렇게 생성된 div 엘리먼트는, HTMLDivElement
라는 클래스의 인스턴스입니다.
모든 DOM 엘리먼트
는 textContent
와 같은 속성, 또는 append()
와 같은 메소드가 있습니다. 각각의 모든 엘리먼트가 해당 메소드나 속성이 있다는 것을 통해, Element라는 공통의 부모 클래스가 있음을 짐작 가능.
div 엘리먼트
의 클래스 모식도
개발자 도구를 사용하면, 해당 메소드나 속성이 어떤 클래스로부터 비롯되었는지 확인 가능.
:innerText
속성은 HTMLElement
로부터, textContent
속성은 Node
로부터 비롯 됨.
인스턴스의 __proto__
를 이용하면 부모 클래스의 프로토타입, 혹은 '부모의 부모 클래스'의 프로토타입을 탐색 가능.
질문
- addEventListener 속성은 어떤 클래스의 프로토타입에서 찾을 수 있나요?
: EventTarget- remove 메소드는 어떤 클래스의 프로토타입에서 찾을 수 있나요?
: Element- 모든 객체에 toString() 메소드가 존재하는 이유가 무엇인가요?
: 모든 객체에는 객체가 텍스트 값으로 표시되거나 객체가 문자열이 예상되는 방식으로 참조 될 때 자동으로 호출되는 toString() 메서드가 있습니다. 기본적으로 toString() 메서드는 Object에서 비롯된 모든 객체에 상속됩니다. 이 메서드가 사용자 지정 개체에서 재정의되지 않으면 toString()은[object type]
을 반환합니다. 여기서 type은 object type입니다.