1장 리액트 개발을 위해 꼭 알아야 할 스크립트 (2)

1.3. 클래스 1.4. 클로저
강석우's avatar
Feb 24, 2024
1장 리액트 개발을 위해 꼭 알아야 할 스크립트 (2)

1.3. 클래스

1.3.1 클래스란 무엇인가?

특정한 객체를 만들기 위한 일종의 템플릿과 같은 개념

constructor

생성자로 객체를 생성하는 데 사용하는 메서드이다.
하나만 존재할 수 있으며, 여러 개 사용시 에러가 발생한다. 생략도 가능하다.

프로퍼티

클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성값을 의미한다.
기본적으로 인스턴스 생성 시에 constructor 내부에 빈 객체가 할당되어 있는데 이 빈 객체에 프로퍼티의 키와 값을 넣어서 활용할 수 있게 도와준다.

class Car {
  constructor(name) {
   // 값을 받으면 내부에 프로퍼티로 할당된다.
   this.name = name
  }
}

const myCar = new Car('자동차') // 프로퍼티 값을 넘겨주었다.

getter 와 setter

  • getter

    • 클래스에서 무언가 값을 가져올 때 사용한다.

    • 사용하기 위해서는 get을 앞에 붙여야하고 뒤에 getter의 이름을 선언해주어야 한다.

  • setter

    • 클래스 필드에 값을 할당할 때 사용한다.

    • set이라는 키워드를 먼저 선언하고, 그 뒤를 이어서 이름을 붙인다.

인스턴스 메서드

클래스 내부에서 선언한 메서드이다.
자바스크립트의 prototype에 선언되므로 프로토타입 메서드로 불리기도 한다.

  • 새로 생성한 객체에서 인스턴스 메서드에 접근할 수 있다.

프로토타입이란 객체가 갖고있는 속성 또는 메서드들에 대한 정보 라고 볼 수 있다.
new 등을 이용해서 객체를 새로 생성하게 되었을 때 해당 객체는 기존 객체의 프로토타입을 상속받게 된다.
자바스크립트는 명세서에서 명명한
[[Prototype]]이라는 숨김 프로퍼티를 갖고 있는데 다른 객체를 참조하게 될 경우 참조 대상을 프로토타입이라 한다.
참조 - https://ko.javascript.info/prototype-inheritance

프로토타입 체이닝 - 직접 객체에서 선언하지 않았음에도 프로토타입에 있는 메서드를 찾아서 실행을 도와주는 것

프로토타입과 프로토타입 체이닝 속성 덕분에 생성한 객체에서도 직접 선언하지 않은, 클래스에 선언한 메서드를 호출할 수 있다.

정적 메서드

정적 메서드는 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드다.

class Car {
  static hello() {
   console.log('안녕하세요!')
  }
}

const myCar = new Car()

myCar.hello() // Uncaught TypeError: myCar.hello is not a function
Car.hello() // 안녕하세요!

정적 메서드 내부의 this는 클래스로 생성된 인스턴스가 아닌 자신을 가리키기 때문에 this 를 사용할 수 없다.

정적 메서드는 인스턴스를 생성하지 않아도 사용할 수 있다는 점, 생성하지 않아도 접근할 수 있기 때문에 객체를 생성하지 않더라도 여러곳에서 재사용이 가능하다는 장점이 있다.

상속

extends 를 사용해서 선언하지 않은 메서드를 사용할 수 있다.
typescript 에서 interface 의 extends 로 사용되고 있다.

class Car {
  constructor(name) {
   this.name = name
  }
  honk() {
   console.log(`${this.name} 경적을 울립니다!`)
  }
}

class Truck extends Car {
  constructor(name) {
   // 부모 클래스의 constructor, 즉 Car의 constructor를 호출한다.
   super(name)
  }
  load() {
   console.log('짐을 싣습니다.')
  }
}


 

1.4. 클로저

1.4.1 클로저의 정의

MDN에서 클로저는 함수와 함수가 선언된 어휘적 환경(Lexical Scope)의 조합 이라 되어있다.
선언된 어휘적 환경 = '변수가 코드 내부 어디에서 선언되었는지'

1.4.2 변수의 유효 범위, 스코프

전역 스코프

브라우저 환경에서 전역 객체는 window, Node.js 환경에서는 global이 있는데, 바로 이 객체에 전역 레벨에서 선언한 스코프가 바인딩 된다.

아래는 책의 예시이다.

var global = 'global scope'
function hello() {
  console.log(global)
}

console.log(global) // global scope
hello() // global scope
console.log(global === window.global) // true

하지만 실제로 실행시켜보면 아래와 같이 나온다.

window.global 을 콘솔로 찍어보니

해결 못함 ( 30분 )

함수 스코프

자바스크립트는 함수레벨 스코프를 갖고있기 때문에 if(true) 문 안에서 변수를 선언해 준다고 하더라도 if 문 밖에서 해당 변수에 접근이 가능하다.

1.4.3 클로저의 활용

아래 첫번째 코드를 보면 counter 가 전역변수로 사용되었기 때문에 두번째의 코드로 변경하여 무분별하게 코드가 망가지는 것을 막을 수 있다.

var counter = 0
function handleClick() {
  counter++
}
function Counter() {
  var counter = 0
  return {
   increase: function () {
    return ++counter
   },
   decrease: function () {
    return --counter
   },
   counter: function () {
    console.log('counter에 접근!')
    return counter
   },
  }
}

var c = Counter()
console.log(c.increase()) // 1
console.log(c.increase()) // 2

1.4.4 주의할 점

  • 스코프를 잘 설정해주자

아래 코드는 5초뒤에 5만 다섯번 출력한다.
i 가 전역변수로 지정되어있기 때문에 이미 for 문이 다섯번 돌아가고 i 는 5로 업데이트 되어있는 상태에서 내부 함수를 실행하기 때문이다.
i 를 let 으로 선언해 준다면 정상적으로 1,2,3,4,5 가 순차적으로 1초마다 실행된다.

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
   console.log(i)
  }, i * 1000)
}
for (let i = 0; i < 5; i++) {
  setTimeout(function () {
   console.log(i)
  }, i * 1000)
}


아래의 함수는 즉시 실행함수를 활용한 것인데 for문이 실행 될 때마다 즉시 실행 함수가 생성되고 실행되기를 반복하기 때문에 정상적으로 1,2,3,4,5 가 순차적으로 1초마다 실행된다.
( 감탄했다 )

for (var i = 0; i < 5; i++) {
  setTimeout(
   (function (sec) {
    return function () {
     console.log(sec)
    }
   })(i),
   i * 1000,
  )
}

  • 클로저를 사용하는 데는 비용이 든다
    클로저를 사용할 경우 내부의 함수가 외부 함수의 선언적인 환경을 기억하고 있어야하기 때문에 저장하게 되는데 아래의 코드의 경우 일반적인 함수는 브라우저에서 작동시 1.5mb 밖에 들지 않지만 두번째 코드는 42mb 가 들게 된다.
    하지만 해당 함수가 실행 되는 것은 클릭시 이므로 저장해 놓을 필요가 전혀 없다.
    클로저는 사용하게 될 경우 비용이 들기 때문에 적절한 상황에서만 사용할 필요가 있다.

// 일반적인 함수
const aButton = document.getElementById("a");

function heavyJob() {
  const longArr = Array.from({ length: 10000000 }, (_, i) => i + 1);
  console.log(longArr.length);
}

aButton.addEventListener("click", heavyJob);

// 클로저라면?
function heavyJobWithClosure() {
  const longArr = Array.from({ length: 10000000 }, (_, i) => i + 1);
  return function () {
    console.log(longArr.length);
  };
}

const innerFunc = heavyJobWithClosure();
bButton.addEventListener("click", function () {
  innerFunc();
});
Share article

석우의 개발블로그