Object / Class JS 21.04.08


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)
}

this는 부모 객체를 먼저 조회! 부모 객체가 없는 경우 window, global 참조.






© 2020.11. by creamer

Powered by CREAMer