객체지향? 프로그래밍 방법론중에 하나
프로그램을 작성할 때 객체들을 만들어 서로 소통하도록하는 방법
객체 : 데이터의 묶음
객체 지향의 객체 : 우리가 표현하고자 하는 구체적인 사물을 추상적으로 표현한것
객체는 행동과 상태를 가진다.
행동은 메소드, 상태는 프로퍼티 정도
// 나라는 사람 추상화 하기
const kon = {
name : '곤',
mbti : 'ENFJ',
says : function() {
console.log(`나는 ${this.name}을 위해 살아간다!`)
},
//행동과 상태 부여하기
isStudying : function(javascript){
console.log('오늘도 가보자고')
javascript.levelup();
}
}
//새로운 객체 만들기 (상호작용)
const javascript = {
level: 1,
levelup: function(){
this.level++
}
}
//능력 발휘
kon.isStudying(javascript); // 오늘도 가보자고
javascript // {level: 2, levelup: ƒ}
객체와 객체가 서로 메소드를 통해 상호작용하게 하는것이 바로 객체지향 프로그래밍!
하 지 만
우리가 만들어낸 객체는 한번 생성하고 나면 끝 . . .
kon과 같은 프로퍼티를 가지는 객체를 생성하려면 작성했던 코드를 반복해야한다.
어떻게 효율적으로 객체를 만들 수 있을까?
생성자? 객체를 만들 때 new 연산자와 함께 사용하는 함수
생성자 장점 :
생성자를 통해 생성된 객체는 같은 프로퍼티와 메서드를 공유한다!
생성자 함수 특징 :
대문자로 시작(약속)
new 키워드를 통해 객체를 생성
생성자는 함수이기 때문에 기본적으로 함수가 필요
function NewFactory(name){
this.name = name;
this.sayYourName = function() {
console.log('삐리비리 제 이름은 ${this.name}입니다.');
}
}
Factory 생성자 함수는 따로 return 값을 가지지 않지만 new 키워드가 앞에 붙게되면 실행되었을 때 자동적으로 객체를 생성하고 반환
이렇게 반환되어 만들어진 객체 <- 인스턴스(instance)
생성자 함수와 객체의 관계는 instanceof 로 확인 가능
함수안에서의 this는 함수를 호출한 객체를 참조
생성자 함수 앞에 new 연산자가 사용되었네? 함수안의 this는 생성자가 만들어낸 객체인 인스턴스를 참조!
이제 객체 즉, 인스턴스를 생성해보자~
const robot1 = new NewFactory('게리');
const robot2 = new NewFactory('뮤라');
const robot3 = new NewFactory('빙키');
필요에 따라서 배열(Array), 객체(Object)를 사용하는 것처럼
필요할 때 사용할 수 있는 우리만의 객체를 만들어서 사용할 수 있도록 도와주는 것이 바로 생성자!
function NewFactory(food) {
const this.chosenFood = food[Math.floor(Math.random() * food.length)];
this.saysYourFood = function() {
console.log(`오늘의 메뉴는 ${this.chosenFood}입니다.`);
}
}
const food = ["김치볶음밥", "떡볶이", "버터갈릭감자튀김"]
const foodBot = new NewFactory(food);
foodBot.saysYourFood(); // 오늘의 메뉴는 떡볶이입니다. (셋 중에 하나 랜덤!)
헷갈렸던 부분!
food[Math.floor(Math.random() * food.length)
Math.random()은 0~1까지 숫자 중에 랜덤으로 내준다.
food.length는 메뉴 숫자만큼 3
Math.random() food.length 해보면
0.111111 3 = 0.333333
0.999999 * 3 = 2.99....
Math.floor는 주어진 수 이하의 가장 큰 정수 반환.
food[Math.floor(Math.random() * food.length)
food 어레이에서 인덱스 0,1,2 중에 랜덤 뽑기 가능!
손쉽게 객체를 생산할 수 있긴 한데, 사실 객체의 메서드를 등록할 때마다 새로운 함수를 생성하는 것임 . . .
그럼 어떡하지?
자원 낭비 해결을 위해 프로토타입 등장!
프로토타입?
prototype은 특정 객체에 대한 참조.
즉 어떠한 공간을 가르키고 있다
생성자 함수가 인스턴스 생성
-> 그 안에는 숨겨진 프로퍼티인 [[Prototype]] 존재, 코드상에서는 __proto__
로 표현
__proto__
프로퍼티는 자신을 만든 생성자 함수의 prototype
을 참조
즉, new 키워드를 통해 생성자 함수의 prototype
과 인스턴스의 __proto__
가 연결
프로토타입 - 생성자 함수 안에! (함수는 프로토타입, 프로토 가지고 있음)
프로토 - 객체 안에! (객체는 프로토만 가지고 있음)
function Test(){
};
const obj = new Test();
obj.__proto__ === Test.prototype // true
다른 예시로 연결 확인하기
function NewFactory(name){
this.name = name;
}
const robot1 = new NewFactory('뮤라');
NewFactory.prototype.sayYourName = function(){
console.log(`삐리비리. 제 이름은 ${this.name}입니다.`);
}
robot1.__proto__ === NewFactory.prototype; // true
생성자함수에서 Prototype은 공간이다.
생성자 함수로 만든 인스턴스들은 그 Prototype 공간 이용이 가능하다.
이전에는 애초에 다른 공간상에 함수를 여러번 만든것 vs 지금은 처음부터 함수를 생성할 때 같은 공간에 등록해놓고 사용
메서드를 생성할 때 생성자 함수 내에 작성하는 것 보다 prototype으로 작성해서 메모리를 아낄 수 있다!
const food = ["김치볶음밥", "떡볶이", "버터갈릭감자튀김"]
function NewFactory(food) {
this.chosenFood = food[Math.floor(Math.random() * food.length)];
}
NewFactory.prototype.saysYourFood = function() {
console.log(`오늘의 메뉴는 ${this.chosenFood}입니다.`);
}
const foodBot = new NewFactory(food);
foodBot.saysYourFood();
foodBot.__ptoro__ === NewFactory.prototype
const kon = {
name : '곤',
mbti : 'ENFJ',
says : function() {
console.log(`나는 ${this.name}을 위해 살아간다!`)
},
isStudying : function(javascript){
console.log('오늘도 가보자고')
javascript.levelup();
}
}
const javascript = {
level: 1,
levelup: function(){
this.level++
}
}
kon.isStudying(javascript); // 오늘도 가보자고
javascript // {level: 2, levelup: ƒ}
생성자를 이용해 바꾼 코드
function Me(name, mbti) {
this.name = name,
this.mbti = mbti
}
Me.prototype.says = function() {
console.log(`나는 ${this.name}을 위해 살아간다!`)
}
Me.prototype.isStudying = function (javascript) {
console.log('오늘도 가보자고')
javascript.levelup();
}
const kon = new Me('곤','ENFJ');
kon // Me {name: '곤', mbti: 'ENFJ'}
function Language(level) {
this.level = level;
}
Language.prototype.levelup = function() {
console.log('오늘도 가보자고')
this.level++;
}
const javascript = new Language(1);
javascript // Language {level: 1} 인스턴스 생성!
javascript.levelup() // Language {level: 2} 렙업 함수 발동!
javascript.__proto__ === Language.prototype // true
자바스크립트의 상속은 기본적으로 prototype을 통해 일어난다.
const obj = {
name: 'test'
}
console.log(obj.hasOwnProperty('name'));
const arr = [1,2,3];
console.log(arr.hasOwnProperty('name'));
Array 함수의 proto 가 Object 함수의 prototype을 참조하고 있기 때문에 Array의 prototype에 존재하지 않는 Object 객체의 프로퍼티와 메서드를 사용할 수 있다.
자신에게 존재하지 않는 프로퍼티나 메서드를 프로토타입을 통해 추적하는 과정 ㅡ 프로토타입 체이닝
ES6 부터 class 등장!
클래스의 결과물은 인스턴스를 생성하는것입니다. 생성자를 이용한 타입 생성과 그 결과가 정확하게 일치합니다.
// function Robot(name) {
// this.name = name;
// }
// Robot.prototype.sayYourName = function () {
// console.log(`삐리비리. 제 이름은 ${this.name}입니다.`);
// }
class Robot {
// 클래스의 생성자 함수입니다. 하나의 클래스는 하나의 생성자만 정의할 수 있습니다.
// 그리고 생성자 함수는 new 키워드가 호출될때 자동으로 실행됩니다.
constructor(name) {
this.name = name;
}
// 메소드를 정의합니다. 메소드는 클래스가 생성한 인스턴스를 통해 사용할 수 있습니다.
sayYourName() {
console.log(`삐리비리. 제 이름은 ${this.name}입니다.`);
}
}
내부적인 동작은 동일하지만 더 보기 좋고 편리하게 개선된 문법 ㅡ 슈가신텍스 (Syntactic sugar)
클래스의 상속은 extends 키워드 사용
상속을 받는 클래스 : ‘파생 클래스’(derived classes)
부모 클래스의 프로퍼티를 상속받기 위해 super 함수를 사용, 이때 super는 부모 생성자를 참조
super 사용시 주의할 점
- 파생 클래스에 생성자 함수를 사용하고 싶다면 반드시 super 함수 사용
- 파생클래스에 생성자 함수가 없다면 super 함수가 자동으로 호출되어 부모 클래스의 프로퍼티를 상속 받게 함
- 생성자 함수에서 this 값을 사용할 경우 super 함수는 반드시 this 보다 먼저 실행
- 파생 클래스가 아닌 클래스에서 사용하려고 해도 에러가 발생
// class 문법은 상속입니다.
class Robot {
constructor(name) {
this.name = name;
}
sayYourName() {
console.log(`삐리비리. 제 이름은 ${this.name}입니다.`)
}
}
class BabyRobot extends Robot {
constructor(name) {
super(name);
// this.ownName = '베이비봇';
}
sayBabyName() {
// 상속을 받게되면 부모 클래스의 메소드를 사용할 수 있게 됩니다. 때문에 this로 접근 할 수 있습니다.
this.sayYourName();
console.log('Suceeding you, Mother!');
}
}
const babyRobot = new BabyRobot('베이비봇');
babyRobot.sayBabyName();
// 삐리비리. 제 이름은 베이비봇입니다.
// Suceeding you, Mother!
상속을 통해 BabyRobot내에 sayYourName이 들어와있으므로 해당 인스턴스의 메서드를 사용한다
class GrilledVege {
constructor(ingredient1, ingredient2) {
this.ingredient1 = ingredient1,
this.ingredient2 = ingredient2
}
taste() {
console.log(`${this.ingredient1}와 ${this.ingredient2} 맛이 아주 좋아!`)
}
}
class AddTruffle extends GrilledVege {
taste() {
super.taste() + console.log("트러플 맛도 나네요.")
}
}
const truffleVege = new AddTruffle('양배추','버섯');
truffleVege.taste(); //
비공개 프로퍼티? 객체 안의 중요한 정보를 안전하게 보호해서 프로그램이 뜻하지 않게 변경되는 것을 막는 역할을 한다
class를 통해 인스턴스를 만들었을 때 보통 아무런 제약없이 인스턴스의 프로퍼티에 접근하는 것이 가능하고, 프로퍼티의 값을 수정하는 것도 가능
class Robot {
constructor(name) {
this.name = name;
}
sayYourName() {
console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
}
}
const bot = new Robot('smith');
bot.name // smith <- 인스턴스의 프로퍼티에 접근
bot.name = 'jay';
bot.name // jay <- 프로퍼티의 값 수정
하지만 중요한 데이터를 조심스럽게 다뤄야 한다면?
데이터를 외부에서 함부로 수정할 수 없게 만들고 싶을 때!
비공개 프로퍼티로 데이터를 변경할 수 있다.
#달고 프로퍼티를 비공개로 전환
이제 #password 의 값에 접근하고 수정하려면 반드시 해당하는 기능을 하는 메서드를 사용해야함
값을 불러오는 메서드: getter 메서드
값을 수정하는 메서드: setter 메서드
class Robot {
#password
constructor(name, pw) {
this.name = name;
this.#password = pw;
}
sayYourName() {
console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
}
getPassword() {
return this.#password
}
setPassword(pw) {
this.#password = pw;
}
}
아래처럼 get, set 키워드를 이용해 아래처럼 코드를 단순화!
class Robot {
#password
constructor(name, pw) {
this.name = name;
this.#password = pw;
}
sayYourName() {
console.log(`삐리비리. 제 이름은 ${this.name}입니다.`);
}
get password() {
return this.#password
}
set password(pw) {
this.#password = pw;
}
}
const bot = new Robot('곤', 1234);
get, set은 프로퍼티를 가공해야할 때 편리하다!
마치 객체의 프로퍼티에 접근하듯 값을 다룰수 있다.
get 과 set을 사용할 때 주의할 점!
get과 set을 사용하면 마치 객체의 프로퍼티를 수정하는것 같아서 간편!
하지만 해당 코드를 직접 작성하지 않은 협업자들에게는 오해를 일으킬 수 있다.(get, set 안에 어떤 로직이 들어있는지 파악하지 못하고 단순히 객체의 프로퍼티를 수정한다는 착각을 일으킬 수 있음)
때문에 협업 시에는 주석이나, 가이드 문서를 만들어 충분한 정보를 제공할 것