Study JavaScript 0617 - new Function 문법, 호출 스케줄링(setTimeout, setInterval)

변승훈·2022년 6월 17일
0

Study JavaScript

목록 보기
31/43

1. new Function 문법

함수 표현식과 함수 선언문 이외에 함수를 만들 수도 있는 방법이 하나 더 있다. 잘 사용하는 방법은 아니지만, 이 방법 외에는 대안이 없을 때 사용한다.

1. 문법

new Function 문법을 사용하면 함수를 만들 수 있다.

let func = new Function ([arg1, arg2, ...argN], functionBody);

새로 만들어지는 함수는 인수 arg1...argN과 함수 본문 functionBody로 구성된다.

인수 두 개가 있는 함수를 직접 만들어 보면서 new Function 문법에 대해 이해해보자!

let sum = new Function('a', 'b', 'return a + b');

console.log( sum(1, 2) ); // 3

인수가 없고 함수 본문만 있는 함수를 만들어보자.

let sayHi = new Function('alert("Hello")');

sayHi(); // Hello

기존에 사용하던 방법과 new Function을 사용해 함수를 만드는 방법의 가장 큰 차이는 런타임에 받은 문자열을 사용해 함수를 만들 수 있다는 것이다.

함수 표현식과 함수 선언문은 개발자가 직접 스크립트를 작성해야만 함수를 만들 수 있었지만, new Function이라는 문법을 사용하면 어떤 문자열도 함수로 바꿀 수 있다. 서버에서 전달받은 문자열을 이용해 새로운 함수를 만들고 이를 실행하는 것도 가능하다.

let str = ... 서버에서 동적으로 전달받은 문자열(코드 형태) ...

let func = new Function(str);
func();

서버에서 코드를 받거나 템플릿을 사용해 함수를 동적으로 컴파일해야 하는 경우, 복잡한 웹 애플리케이션을 구현할 때와 같이 아주 특별한 경우에 new Function을 사용할 수 있다.

2. 클로저

함수는 특별한 프로퍼티 [[Environment]]에 저장된 정보를 이용해 자기 자신이 태어난 곳을 기억한다. [[Environment]]는 함수가 만들어진 렉시컬 환경을 참조한다.

그런데 new Function을 이용해 함수를 만들면 함수의 [[Environment]] 프로퍼티가 현재 렉시컬 환경이 아닌 전역 렉시컬 환경을 참조하게 된다.

따라서 new Function을 이용해 만든 함수는 외부 변수에 접근할 수 없고, 오직 전역 변수에만 접근할 수 있다.

function getFunc() {
  let value = "test";

  let func = new Function('alert(value)');

  return func;
}

getFunc()(); // ReferenceError: value is not defined

일반적인 방법을 사용해 함수를 정의한 예시와 비교해보자!

function getFunc() {
  let value = "test";

  let func = function() { console.log(value); };

  return func;
}

getFunc()(); // getFunc의 렉시컬 환경에 있는 값 "test"가 출력

지금 당장은 new Function이 제공하는 특수한 기능이 익숙하지 않을 수 있는데, 실무에선 이 기능이 아주 유용하게 쓰인다.

new Function으로 만든 함수에 무언갈 넘겨주고 싶다면 인수를 사용하자!

2. 호출 스케줄링

일정 시간이 지난 후에 원하는 함수를 예약 실행(호출)할 수 있게 하는 것을 '호출 스케줄링(scheduling a call)'이라고 한다.

호출 스케줄링을 구현하는 방법은 두 가지가 있다.

  • setTimeout을 이용해 일정 시간이 지난 후에 함수를 실행하는 방법
  • setInterval을 이용해 일정 시간 간격을 두고 함수를 실행하는 방법

1. setTimeout

문법은 아래와 같다.

let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
  1. func|code: 매개변수, 실행하고자 하는 코드로 함수 또는 문자열 형태이며 대개 함수가 들어간다.
  2. delay: 실행 전 대기시간으로 단위는 millisecond이며 기본 값은 0이다.
  3. arg1, arg2…: 함수에 전달할 인수들이다.

예시를 통해 사용해보자!

function sayHi() {
  console.log('안녕하세요.');
}
setTimeout(sayHi, 1000);

아래와 같이 함수에 인수를 넘겨줄 수도 있다.

function sayHi(who, phrase) {
  console.log( who + ' 님, ' + phrase );
}
setTimeout(sayHi, 1000, "홍길동", "안녕하세요."); // 홍길동 님, 안녕하세요.

setTimeout의 첫 번째 인수가 문자열이면 자바스크립트는 이 문자열을 이용해 함수를 만든다.
하지만 되도록이면 아래와 같은 화살표 함수를 사용하자!

setTimeout(() => console.log('안녕하세요.'), 1000);

여기서 주의할 점은 setTimeout에 함수를 넘길 때, 함수 뒤에 ()를 넣지 말아야 한다는 점이다!
아래와 같이 사용하지 말자!

// 잘못된 코드
setTimeout(sayHi(), 1000);

1. clearTimeout으로 스케줄링 취소하기

setTimeout을 호출하면 '타이머 식별자'가 반환되는데 스케줄링을 취소하고 싶으면 이 식별자를 사용하면 된다.

let timerId = setTimeout(...);
clearTimeout(timerId);

아래의 예시를 보자!

let timerId = setTimeout(() => console.log("아무런 일도 일어나지 않습니다."), 1000);
console.log(timerId); // 타이머 식별자

clearTimeout(timerId);
console.log(timerId); // 위 타이머 식별자와 동일함 (취소 후에도 식별자의 값은 null이 되지 않는다.)

2. 대기 시간이 0인 setTimeout

setTimeout(func, 0)이나 setTimeout(func)을 사용하면 setTimeout의 대기 시간을 0으로 설정할 수 있다.
이런 경우 무슨 의미가 있냐? 라는 사람이 있을 것이다. 대기 시간을 0으로 설정하면 어떻게 사용할 수 있을까?

바로 func을 ‘가능한 한’ 빨리 실행할 수 있다.
다만, 이때 스케줄러는 현재 실행 중인 스크립트의 처리가 종료된 이후에 스케줄링한 함수를 실행한다.

이런 특징을 이용하면 현재 스크립트의 실행이 종료된 ‘직후에’ 원하는 함수가 실행될 수 있게 할 수 있다.

예시의 코드로 Hello와 World가 순서대로 출력되는것을 확인해보자!

setTimeout(() => console.log("World"));

console.log("Hello");

2. setInterval

setInterval 메서드는 setTimeout과 동일한 문법을 사용한다.

let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)

인수역시 동일하지만 setInterval 메서드는 setTimeout과 달리 함수를 "주기적"으로 실행하게 만드는 것에 차이점이 있다.

// 2초 간격으로 메시지를 보여줌
let timerId = setInterval(() => console.log('째깍'), 2000);

// 5초 후에 정지
setTimeout(() => { clearInterval(timerId); console.log('정지'); }, 5000);

중첩 setTimeout

위의 setTimeout 파트에서 설명해야 하지만 setInterval이 뒤에 나와 있어서 부득이하게 아래로 설명을 내렸다.

중첩 setTimeout방법은 setInterval보다 유연하다.
호출 결과에 따라 다음 호출을 원하는 방식으로 조정해 스케줄링이 가능하기 때문이다.

여기서 핵심은 중첩 setTimeout을 이용하는 방법은 지연 간격을 보장하지만 setInterval은 이를 보장하지 않는다.

예시로 아래는 setInterval을 이용했다.

let i = 1;
setInterval(function() {
  func(i++);
}, 100);

이 예시는 중첩 setTimeout을 이용했다.

let i = 1;
setTimeout(function run() {
  func(i++);
  setTimeout(run, 100);
}, 100);

이 둘을 실행해보면 차이를 느낄수 있다.

setInterval을 사용하면 func호출 사이의 지연 간격이 실제 명시한 간격(100ms)보다 짧아진다.
이는 func을 실행하는 데 ‘소모되는’ 시간도 지연 간격에 포함시키기 때문이다.

중첩 setTimeout을 사용하면 명시한 지연(여기서는 100ms)이 보장된다.

profile
잘 할 수 있는 개발자가 되기 위하여

0개의 댓글