S2 Unit 2. javascript 객체 지향 프로그래밍

나현·2022년 9월 21일
1

학습일지

목록 보기
19/53
post-thumbnail

💡 이번에 배운 내용

  • Section 2.
    서버와 통신이 가능한 구조적인 Web App을 만들 수 있다.
  • Unit 2. Javascript 객체 지향 프로그래밍: 자바스크립트로 직접 객체 지향 프로그래밍을 구현하며, 객체 지향 프로그래밍의 장점과 클래스, 인스턴스, prototype 등의 개념을 학습한다.

느낀점

잘 사용하면 아주 유용할 것 같은 개념을 학습했는데, 거대한 벽을 만난 듯한 느낌이다! 무언가를 설계한다는 것은 쉽지 않은 일이다. 다행히 내용을 천천히 읽어보고 학습한 내용을 정리하는 과정에서 어렴풋이 이해는 갔지만, 막상 실습하거나 사용해 보려니 익숙치는 않다. 이제는 힘쎄고 강한 실습뿐이야!


키워드

클래스, 인스턴스, prototype, this, constructor, new operator, OOP, 캡슐화, 추상화, 상속, 다형성


학습내용

Ch1. 객체 지향

프로그래밍 방법에는 절차 지향 프로그래밍, 객체 지향 프로그래밍이 있다.
javascript의 객체를 활용해 객체 지향 패턴으로 프로그래밍이 가능한데, 이 때 사용하는 객체는 일반 객체나 자바스크립트의 내장 객체와 구분하여 클래스(Class)라고 한다.
클래스에 대해 알기 전, 클로저 모듈 패턴에 대해 알아 둘 필요가 있다.
보통 객체 내의 메서드를 호출할 때 객체.메서드() 처럼 호출한다. 객체 안의 메서드를 자주 사용한다면, 객체를 일일이 복사하는 것보다 클로저를 이용해 원하는 함수를 리턴하여 재사용하는 것이 낫다. 이렇게 클로저를 이용해 원하는 기능을 리턴하여 재사용하는 것을 클로저 모듈 패턴이라고 한다.

1) 클래스, 인스턴스

위에서 언급한 클로저 모듈패턴과 원리는 비슷한데, 객체 및 외부함수의 역할을 하는 것이 클래스, 클래스를 활용해 재사용하는 경우 하나를 인스턴스라고 한다.

클래스를 만드는 방법은 ES5와 ES6에 차이가 있다. 기존에 ES5에는 class라는 생성자가 따로 없었지만 ES6에서는 class 생성자를 사용해 클래스를 생성한다.

<클래스, 인스턴스 한 눈에 보기>

//1. 클래스 생성하기

//1) ES5
function TV(brand, name, size){ //클래스는 보통 대문자로 시작한다.
  //인스턴스 생성시 받은 전달인자를 할당
  this.brand=brand; //this는 인스턴스 객체를 의미한다.
  this.name=name;
  this.inch=size; 
}
//prototype이라는 원형 객체를 사용해 메서드를 생성한다.
//tv의 전원을 켜는 기능
TV.prototype.on=function(){
  console.log(this.brand+' '+this.name+'의 전원을 켭니다.');
} 
TV.prototype.off=function(){} //tv의 전원을 끄는 기능
TV.ptototype.changeChanel=function(){} //tv의 채널을 바꾸는 기능

//2) ES6
class TV{
  //생성자. 이곳에 인스턴스의 전달인자를 할당
  constructor(brand, name, size){ 
  	this.brand=brand;
    this.name=name;
  	this.inch=size; 
  }
  on(){
  	console.log(`${this.brand} ${this.name}의 전원을 켭니다.`);
  }
  off(){}
  changeChanel(){}
}

//-------------------//
//2. 인스턴스 생성 - new 키워드를 통해 생성한다.
let lgtv=new TV('LG', '올레드TV', '55inch');
lgtv.on(); //LG 올레드TV의 전원을 켭니다.
let samsungtv=new TV('SAMSUNG', 'Neo QLED', '128cm');
samsungtv.on(); //SAMSUNG Neo QLED의 전원을 켭니다.

위의 개념을 정리하자면 아래와 같다.

  • 클래스: 클로저 모듈 패턴처럼 재사용이 가능한 객체로 필요한 데이터와 메서드(기능)를 하나로 추상화한, 일종의 객체 템플릿이다.
  • 인스턴스: 클래스를 바탕으로 만든 하나의 객체이다.
  • prototype: ES5에서 클래스의 메서드를 만들 때 사용하는 원형(original form) 객체다.
  • this: 인스턴스 생성시, 함수가 실행될 때마다 생성되는 실행 context이다. 쉽게 말해 new키워드로 생성한 객체를 의미한다.
    (ex. 위 예제에서 lgtv.brand는 'LG'이다.)
  • constructor: 생성자 (함수). 인스턴스를 생성하고 초기화하는 메서드.
  • new 키워드: 클래스의 인스턴스를 생성할 때 사용한다.

주의할 것! 메서드 작성시 화살표 함수는 사용하지 말아야 한다.(참고: 화살표 함수)

참고 링크

2) 객체 지향 프로그래밍(Object-oriented programming, OOP)

객체 지향 프로그래밍(bject-oriented programming, OOP), 절차 지향 프로그래밍은 일종의 프로그래밍 설계 방식이다.
절차 지향 프로그래밍(별개의 변수, 함수로 순차 작동)과 다르게 객체 지향 프로그래밍은 객체로 데이터, 기능을 한 번에 묶어서 처리할 수 있다.
엄밀히 말해 자바스크립트는 객체 지향 '언어'는 아니지만 객체 지향의 원리와 특징을 사용해 프로그래밍이 가능하다.
대표적으로 위에서 다룬 클래스, 인스턴스로 활용이 가능하다.

객체 지향 프로그래밍의 주요 개념

  • 캡슐화 Encapsulation
    데이터와 메서드를 하나의 단위로 묶는 것을 의미하며 자바스크립트로 클래스로 활용할 수 있다. 캡슐화의 특징은 은닉(hiding)으로 상세한 속성 및 데이터, 구현 방법은 숨기는 것을 의미한다. (마치 클로저처럼)
  • 추상화 Abstraction
    복잡한 구현 내용은 숨기고 간단하고 단순한 기능만 노출시킨다는 개념이다. 이 추상화의 원리를 통해 인터페이스를 단순하게 만들어 직관적이고 쉬운 개념만 노출시킨다. 캡슐화가 은닉에 초점이 맞춰져 있다면, 추상화는 복잡하거나 필요없는 개념은 숨기고 노출 시킨 개념이 직관적, 단순해야 함에 초점이 맞춰져 있다.
  • 상속 Inheritance
    기본 클래스, 즉 부모 클래스로부터 파생된 클래스, 즉 자식 클래스가 그 속성과 메서드를 물려받는 것을 의미한다. 상속받은 자식 클래스에는 속성, 메서드를 추가할 수도 있다.
  • 다형성 Polymorphism
    하나의 클래스에 다양한 인스턴스를 생성하고, 파생된 클래스에 기본 클래스의 메서드를 다양하게 사용할 수 있다는 개념이다.

위의 4가지 개념을 활용하면 코드의 재사용성을 높이고, 복잡한 코드를 단순하게 사용할 수 있으며 불필요한 코드를 줄일 수 있다.
때문에 프로그래밍시 필요한 속성, 메서드를 하나로 모아 일종의 객체 단위로 설계하려고 하고, 위의 4가지 개념을 반영하여 설계한다면 객체 지향 프로그래밍이 가능할 것이다.

3) 객체 지향 언어와의 차이점

위에 언급한대로 자바스크립트는 객체 지향의 원리와 특징을 사용해 프로그래밍이 가능하지만, '객체 지향 언어'는 아니다. 그 이유는 아래와 같다.

  • 은닉의 한계:
    자바스크립트는 클로저 모듈 패턴을 통해 은닉화 할 수 있을 뿐이다. 최근에 타 언어처럼 내부에서만 사용하는 속성에 사용하는 private과 비슷한 기능이 도입되긴 했으나, 아직 지원하는 브라우저가 적어 한계가 있다.
  • 추상화의 한계:
    타 언어는 클래스의 내용을 단순화시켜 사용할 수 있는 인터페이스(interface)라는 기능을 제공하나 자바스크립트에서는 해당 기능이 존재하지 않는다.
    인터페이스 기능이 있다면 직관적으로 메서드의 의도를 유추할 수 있고 복잡한 코드를 숨긴채 필요한 내용만을 노출시키기 편리하다.
    특히 어떤 클래스를 외부에 공개할 때 이 인터페이스 기능이 용이한데, 그 예로
    API(Application Programming Interface)가 있다.(API는 추후 학습 예정이다.)

Ch2. prototype

자바스크립트에서 객체를 상속하기 위해 프로토타입(prototype)을 사용한다.
프로토타입은 원형, 즉 original form이라는 뜻이다.
이처럼 프로토타입은 모든 객체들이 속성, 메서드를 상속받기 위해 템플릿처럼 사용하는 객체를 의미한다.
프로토타입 객체와 그 상위 객체도 프로토타입 객체를 통해 속성과 메서드를 상속받을 수 있는데, 이처럼 다른 객체에 정의된 속성과 메서드를 프로토타입을 통해 상속받는 것을 프로토타입 체인(prototype chain)이라고 한다.
정리하자면 아래와 같다.

  • 프로토타입(prototype): 모든 객체들이 속성, 메서드를 상속받기 위해 템플릿처럼 사용하는 객체(prototype object).
    상속되는 속성, 메서드는 각 객체가 아닌 각 객체의 생성자가 가진 prototype이라는 속성에 정의되어 있다.
  • 프로토타입 체인(prototype chain): 다른 객체에 정의된 속성과 메서드를 프로토타입을 통해 상속받는 것. 속성과 메서드를 다른 객체로 복사하는 것이 아니고, 말 그대로 체인처럼 연결되었다고 비유할 수 있다.
function TV(brand, name, size){
  this.brand=brand;
  this.name=name;
  this.inch=size; 
}

let lgtv=new TV('LG', '올레드TV', '55inch');

console.log(TV.prototype===lgtv.__proto__); //true
console.log(lgtv.__proto__); 
console.log(lgtv.__proto__.__proto__);
...

위 예제를 보면 인스턴스가 상속받은 클래스, 즉 객체를 계속해서 확인할 수 있는데, 최상의 객체로부터 메서드를 상속받을 수 있다.
그래서 저렇게 .__proto__를 사용해서 프로토타입이 연결되어 상속되는 것을 프로토타입 체인이라고 한다.

위의 정리한 것처럼 모든 객체들이 프로토타입 객체를 가지기에 자바스크립트를 프로토타입 기반 언어(prototype-based language)라고 부르기도 한다.

1) 클래스, 인스턴스, 프로토타입의 관계

아래는 정리한 내용을 종합하여 클래스, 인스턴스, 프로토타입 간의 관계를 정리한 그림이다.

  • .prototype: 프로토타입도 하나의 객체로, 프로토타입 체인을 통해 상속하고자 하는 속성과 메소드가 담겨있다.
  • constructor: 생성자를 통해 인스턴스를 생성할 때 받은 전달인자를 내부의 속성, 즉 내부 변수에 할당할 수 있다.
  • new oprator: 인스턴스는 new 키워드로 생성하며, 프로토타입을 통해 클래스의 속성, 메서드를 상속받는다.
  • __proto__ : 인스턴스의 프로토타입을 확인할 수 있다.
    ex. 임의의 배열을 생성하여 arr.__proto__로 확인해보면 클래스 Array로부터 상속받은 메서드들을 확인할 수 있다.

여기서 '클래스.prototype===그 클래스의 인스턴스.__proto__'의 결과는 true이다.

2)클래스 간 상속과 프로토타입 체인

프로토타입 체인과 상속을 이해했다면, 본격적인 상속을 구현해보기에 앞서 다음의 개념을 확실히 알고 있어야 한다.

  • 자식 클래스가 상속받기 위해 부모 클래스의 속성을 가져올 때, 같은 내용을 복사하는 게 아니라 접근하는 것이다. 그렇지 않으면 재사용의 의미가 없고 코드가 길어진다.
  • 메서드를 상속받기 위해서는 부모 클래스의 프로토타입 객체를 자식 클래스의 프로토타입 객체로 상속받아야 한다.
  • 부모로부터 프로토타입 객체를 상속받았다면 constructor, 즉 생성자는 부모의 것이다. 때문에 생성자를 부모 클래스의 것이 아닌 자식 클래스의 생성자로 바꿔야 한다.

상속에 관한 설명은 다음의 링크를 참고하면 좀 더 이해가 쉽다.
javascript MDN - Classes in Javascript

위의 개념을 토대로 프로토타입 체인과 상속에 필요한 메서드는 아래와 같다.

  • 부모클래스.call(자식클래스 객체,[생성자 매개변수]);
    call()은 부모 클래스의 생성자의 속성을 가져오는 메서드다.
    call()메서드를 이용해 부모 클래스의 속성을 가져오면 자식 클래스는 그 외에 필요한 속성만 추가하면 된다.
    아래와 같이 자식 클래스 안에서 사용하며 이 때 자식클래스 객체는 주로 this를 사용한다. (단, this의 값이 확실한지 확인하도록 한다.)

    <상속 예제 - Device와 TV 클래스>

    //부모클래스
    function Device(brand, name){
    this.brand=brand;
    this.name=name;
    }
    //자식 클래스 - brand, name은 상속받았으므로 따로 추가하지 않는다.
    function TV(brand, name, size){
    Device.call(this, brand, name);
    this.size=size; //자식 클래스에 필요한 속성만을 추가함
    }
    //자식 클래스의 인스턴스
    let lgtv=new TV('LG', '올레드TV', '55inch');
    console.log(lgtv.brand); //'LG'
    console.log(lgtv.name); //'올레드TV'
    console.log(lgtv.size); //'55inch'

    참고: javascript MDN - Function.prototype.call()

  • 자식클래스.prototype = Object.create(부모클래스.prototype);
    create()메서드를 활용해 부모 클래스의 프로토타입 객체를 자식 클래스에 상속받을 수 있다.
    위의 예제에서 메서드는 상속받지 못했기 때문에 메서드를 상속받으려면 create()를 사용해 부모 클래스의 프로토타입 객체를 먼저 상속받아야 한다.
    TV.prototype=Object.create(Device.prototype); 
    참고: javascript MDN - Object.create()
  • 자식클래스.prototype.constructor = 자식클래스;
    위처럼 create()를 사용하면 자식 클래스의 프로토타입 생성자가 부모로 되어 있다. 때문에 자식 클래스의 생성자로 바꿔주기 위해 constructor에 자식클래스를 재할당해야한다.
    TV.prototype.constructor=TV; 

3) class를 이용한 상속

2) 번의 예제(<상속 예제 - Device와 TV 클래스>)를 class 문법을 이용하여 정의하고 상속받을 수 있다. 이 때 사용하는 메서드는 위와 다르다.

//부모클래스
class Device{
constructor(brand, name){
  this.brand=brand;
  this.name=name;
}
}
//자식 클래스 - brand, name은 상속받았으므로 따로 추가하지 않는다.
class TV extends Device{ //부모 클래스를 명시하고 상속받는다
constructor(brand, name, size){
  super(brand, name); //call()과 비슷
  this.size=size; //자식 클래스에 필요한 속성만을 추가함
}
}
//자식 클래스의 인스턴스
let lgtv=new TV('LG', '올레드TV', '55inch');
console.log(lgtv.brand); //'LG'
console.log(lgtv.name); //'올레드TV'
console.log(lgtv.size); //'55inch'
  • extends : 자식클래스를 선언할 때 부모클래스로부터 상속받기 위한 구문이다.
    참고: javascript MDN - extends
  • super : call()메서드처럼 부모클래스의 생성자로부터 속성을 상속받기 위한 구문이다.
    참고: javascript MDN - super

extends로 클래스를 상속받았다면, 부모의 클래스의 메서드를 자동으로 상속받을 수 있다.

4) DOM과 프로토타입 상속

아래의 예제처럼 .__proto__를 사용해 DOM에도 상속관계가 있음을 확인해 볼 수 있다.

const div = document.createElement('div');

//1. HTMLDivElement
console.log(div.__proto__);

//2. HTMLElement
console.log(div.__proto__.__proto__); 

//3. Element
console.log(div.__proto__.__proto__.__proto__); 

//4. Node
console.log(div.__proto__.__proto__.__proto__.__proto__); 

//5. EventTarget
console.log(div.__proto__.__proto__.__proto__.__proto__.__proto__); 

//6. constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} ...
console.log(div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); 

//7. null
console.log(div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); 

정리해보면 생성된 div 엘리먼트는 어떤 부모 클래스에게 상속받았는지 직계 부모부터 조상까지 차례로 확인할 수 있다.

  • HTMLDivElement-HTMLElement-Element-Node-EventTarget

질문해보기

1. getter, setter란?
getter와 setter는 쌍으로 동작하며 getter는 현재 값을 반환하고, setter는 그 현재값을 수정한다.

class TV{
  constructor(brand, name, size, ott){
    this.brand=brand;
    this.name=name;
    this.size=size;
    this.ott=ott;
  }
  get ottCheck(){
    return this.ott;
  }
  set ottCheck(newOtt){
    this.ott=newOtt;
  }  
}

참고: javascript MDN - getter, setter

profile
프론트엔드 개발자 NH입니다. 시리즈로 보시면 더 쉽게 여러 글들을 볼 수 있습니다!

0개의 댓글