Object / Class JS 21.04.08
in Dev on Java Script
Object review
- object property : object의 key : value를 의미한다.
// object
let obj = { a: 1 };
// { a: : 1 }은 property, a는 key, 1은 value
// add object property
obj.b = 2;
console.log(obj); //{ a: 1, b: 2}
// edit object property
obj.b = 3;
console.log(obj); //{ a: 1, b: 3}
// delete object property
delete obj.b;
console.log(obj); //{ a: 1}
method(function) call
객체.메소드() 과 같이 객체 내에 메소드를 호출하는 방법
let counter1 = {
value: 0,
// object key의 value로 function을 할당
increase: function () {
// this는 counter1 scope 내의 value를 가리킨다.
this.value++;
},
decrease: function () {
this.value--;
},
getValue: function () {
return this.value;
},
};
// call increase function of counter1
counter1.increase(); // value의 값 1 증가
counter1.increase(); // value의 값 1 증가
counter1.increase(); // value의 값 1 증가
// call decrease function of counter1
counter1.decrease(); // value의 값 1 감소
// call getValue function of counter1
counter1.getValue(); // 2
closure module : create new object every time using the closure
클로저 모듈 패턴 : 똑같은 기능을 하는 함수를 여러 개 만드는 방법 중 하나
// module화 : counter 생성 함수
function makeCounter() {
return {
value: 0,
increase: function () {
this.value++;
},
decrease: function () {
this.value--;
},
getValue: function () {
return this.value;
},
};
}
// 모듈화 한 위 함수를 각각 다른 변수들에 할당하여 각각 사용 가능
let counter1 = makeCounter();
// closure 사용
counter1.increase();
// = makeCounter().increase();
counter1.getValue(); // 1
let counter2 = makeCounter();
counter2.decrease();
counter2.getValue(); // -1
- 위 코드를 class로 만들어 보기
class MakeCounter {
constructor() {
this.value = 0;
}
increase() {
this.value++;
}
decrease() {
this.value--;
}
getValue() {
return this.value;
}
}
let counter1 = new MakeCounter();
counter1.increase();
counter1.getValue(); // 1
let counter2 = new MakeCounter();
counter1.decrease();
counter1.getValue(); // -1
객체 지향 프로그래밍
하나의 모델이 되는 일종의 형식(Class)을 만들고 그 형식을 바탕으로 객체(instance)를 생성하는 프로그래밍 패턴
비유하자면 붕어빵을 만들기 위해 붕어빵 틀(Class)를 만들고 이 틀에 각각의 다양한 붕어빵(instance)을 만들 수 있다. 붕어빵(instance)의 특징에 따라 다양한 맛(함수, 객체 키:값 추가)을 추가하거나 삭제 할 수 있다.
Class & instance
Class 특징
- 기본 형식(일종의 설계도, 포맷, 틀, 형식)
- property들을 담고 있는 constructor를 갖는다.
- class 키워드 안쪽 && constructor의 밖에 method(function)들을 갖을 수 있다. (사용법 : instance명.메소드명)
- constructor 함수 자체는 return값을 만들지 않는다.
Class는 다른 일반 function과 구분하기 위해 첫글자를 대문자로 작성. (일반 function은 적절한 동사를 포함하고 소문자로 만드는걸 추천)
instance: Class를 바탕으로 생성된 객체.
new 키워드 : 새로운 instance를 생성 할 때 사용. 새로운 instance를 만드는 방법
prototype : 설계도를 만들 때 쓰는 원형 객체 (Original form)
constructor : instance가 new로생성될 때 실행하는 생성자 함수
- this : function이 실행 될 때 해당 scope 마다 생성되는 고유한 실행 context. instance가 new로 생성 될 때, this가 가리키는 것이 해당 instance.
// class 기본 형식(일종의 설계도, 포맷, 틀, 형식)
// class는 다른 일반 function과 구분하기 위해 첫글자를 대문자로 작성.
class FishBreadMold {
// property들을 담고 있는 생성자(constructor) 함수를 갖는다.
constructor(flavor, price) {
// constructor 함수 자체는 return값을 만들지 않는다.
this.flavor = flavor;
this.price = price;
}
// method(function) 위치 : class 키워드 안쪽 && constructor의 밖에 들을 갖을 수 있다.
// (사용법 : instance명.메소드명)
howMuch() {
// 변수를 this 값으로 넣어줘야한다.
return `${this.flavor} 붕어빵은 ${this.price}입니다.`;
}
}
// new 키워드를 사용하여 붕어빵 틀(class)을 바탕으로 팥 붕어빵(instance) 생성
let redBean = new FishBreadMold("팥", "300원");
console.log(redBean); // FishBreadMold {flavor: "팥", price: "300원"}
redBean.howMuch(); // "팥 붕어빵은 300원입니다."
// new 키워드를 사용하여 붕어빵 틀(class)을 바탕으로 또 다른 슈크림 붕어빵(instance) 생성
let puff = new FishBreadMold("슈크림", "500원");
console.log(puff); // FishBreadMold {flavor: "슈크림", price: "500원"}
puff.howMuch(); // "슈크림 붕어빵은 500원입니다."
this
this는 함수 실행시 호출(invocation) 방법에 의해 결정되는 특별한 객체입니다. 함수 실행시 결정되므로, 실행되는 맥락(execution context)에 따라 this는 다르게 결정됩니다.
function 실행 종류와 this
- 일반 함수 호출 : this는 window/globalThis를 가리킨다.
let a = function () {
console.log(this);
};
a(); // a라는 함수명을 가진 함수를 호출.
// Window {window: Window, self: Window, document: document, name: "", location: Location, …}
- method 호출 : this는 부모 객체를 가리킨다
obj.b();
- new 키워드를 이용한 생성자 호출 : this는 새롭게 생성된 instance를 가리킨다.
new A(); // class A를 호출
- call 또는 .apply 호출 : this 값을 특정할 때 사용. this는 첫번째 인자로 전달된 객체를 가리킨다. 특히 apply의 경우 배열의 엘리먼트를 풀어서 인자로 넘기고자 할 때 유용함
a.call();
a.apply();
call, apply, bind
- call : this 값 및 각각 전달된 인수와 함께 함수를 호출 (apply()와 거의 동일하지만, call()은 인수 목록을, 반면에 apply()는 인수 배열 하나를 받는다)
syntax
call(첫번째 인자, 두번째 인자, 세번째 인자)
// 첫번째 인자가 this, 나머지는 인수 목록으로 받는다.
// '피,땀,눈물'을 this로 지정합니다.
"".split.call("피,땀,눈물", ",");
// 다음과 정확히 동일한 결과를 리턴합니다.
"피,땀,눈물".split(",");
- 보다 실용적인 예제
let allDivs = document.querySelectorAll("div"); // NodeList라는 유사 배열입니다.
// allDivs를 this로 지정합니다.
[].map.call(allDivs, function (el) {
return el.className;
});
// allDivs는 유사 배열이므로 map 메소드가 존재하지 않습니다.
// 그러나, Array prototype으로부터 map 메소드를 빌려와 this를 넘겨 map을 실행할 수 있습니다.
- apply
// null을 this로 지정한다. Math는 생성자가 아니므로 this를 지정할 필요가 없다.
Math.max.apply(null, [5, 4, 1, 6, 2]); // 6
// spread operator의 도입으로 굳이 apply를 이용할 필요가 없다.
Math.max(...[5, 4, 1, 6, 2]); // 6
bind
.bind는 .call과 유사하게 this 및 인자를 괄호로 묶는다.(바인딩) 그러나 당장 실행하는 것이 아닌 바인딩된 함수를 리턴하는 함수입니다.
syntax
fn.bind(this값, 인자1, 인자2, ...)
- 사용 사례 1 : 이벤트 핸들러
users.forEach((user) => {
let btn = document.createElement("button");
btn.textContent = user.name;
btn.onclick = handleClick.bind(null, user);
target.appendChild(btn);
});
하지만 bind를 사용하지 않고 익명 함수로 문제를 해결 가능
users.forEach((user) => {
let btn = document.createElement("button");
btn.textContent = user.name;
btn.onclick = () => {
handleClick(user);
};
target.appendChild(btn);
});
- 사용 사례 2: setTimeout
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
printArea() {
console.log("사각형의 넓이는 " + this.getArea() + " 입니다");
}
printSync() {
// 즉시 사각형의 넓이를 콘솔에 표시합니다
this.printArea();
}
printAsync() {
// 1초 후 사각형의 넓이를 콘솔에 표시합니다
setTimeout(this.printArea, 2000);
}
}
let box = new Rectangle(40, 20);
box.printSync(); // '사각형의 넓이는 800 입니다'
box.printAsync(); // 에러 발생!
// Uncaught TypeError: this.getArea is not a functio at printArea (<anonymous>:12:36)
printAsync의 this는 전역 객체(window/globalThis)를 가리키므로 에러가 발생한다.
오류를 해결하기 위해서 bind를 사용해준다.
printAsync() {
// 1초 후 사각형의 넓이를 콘솔에 표시합니다
setTimeout(this.printArea.bind(this), 2000)
}
화살표 익명 함수 도입 이후로 요즘 bind는 잘 사용되지 않는다.
printAsync() {
// 1초 후 사각형의 넓이를 콘솔에 표시합니다
setTimeout(() => {
this.printArea()
}, 2000)
}