📌 [모던 자바스크립트 13주 뿌시기] 41. 타이머 & 43. Ajax
![](/assets/img/JavaScript/2022-11-10/bookcover.png)
41. 타이머
호출 스케줄링
호출 스케줄링: 함수를 명시적으로 호출하지 않고 일정 시간이 경과된 이후에 호출되도록 함수 호출을 예약하는 것.
타이머 함수는 ECMAScript 사양에 정의된 빌트인 함수가 아닌 호스트 객체이다.
타이머 함수는 일정 시간이 경과된 이후 콜백 함수가 호출되도록 타이머를 생성한다.
setTimeOut
: 해당 함수가 생성한 타이머는 한 번만 동작한다.
setInterval
: 해당 함수가 생성한 타이머는 반복 동작한다.
자바스크립트는 싱글 스레드이기 때문에 타이머 함수는 비동기 처리 방식으로 동작한다.
타이머 함수
setTimeout/clearTimeout
const timeoutId = setTimeout(func|code[, delay, param1, param2, ...]);
두 번째 인수로 전달받은 시간(ms, 1/1000초)으로 단 한 번 동작하는 타이머를 생성한다.
전달받은 시간이 만료되면 첫 번째 인수로 전달받은 콜백 함수가 호출된다.
- 첫 번째 인수인 함수는 문자열로 코드를 전달할 수 있다. 이 경우, 타이머가 만료된 후 해석된다. (권장X)
- delay 시간이 설정된 타이머가 만료되면 콜백 함수가 즉시 호출되는 것이 보장되지는 않는다. (콜백 함수를 등록하는 시간을 지연하는 역할)
setTimeout
함수는 타이머 식별이 가능한 고유id
를 반환한다. (브라우저: 숫자 반환, Node.js: 객체 반환)setTimeout
함수의 id를clearTimeout
함수에 전달하여 타이머를 취소할 수 있다.
setInterval/clearInterval
const timerId = setInterval(func|code[, delay, param1, pram2, ...]);
두 번째 인수로 전달받은 시간(ms, 1/1000초)으로 반복 동작하는 타이머를 생성한다.
전달받은 시간이 만료되면 첫 번째 인수로 전달받은 콜백 함수가 반복 호출된다.
- 전달할 인수는
setTimeout
과 같다. setInterval
함수는 타이머 식별이 가능한 고유id
를 반환한다. (브라우저: 숫자 반환, Node.js: 객체 반환)setInterval
함수의 id를clearInterval
함수에 전달하여 타이머를 취소할 수 있다.
디바운스와 스로틀
디바운스와 스로틀이란?
짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화하여 과도한 이벤트 핸들러의 호출을 방지하는 프로그래밍 기법.
디바운스와 스로틀의 구현에는 타이머 함수가 사용된다.
디바운스
디바운스의 역할
짧은 시간 간격으로 이벤트가 연속해서 발생하면 이벤트 핸들러를 호출하지 않다가 일정 시간이 경과한 이후에 이벤트 핸들러가 한 번만 호출되도록 하는 것.
요약하자면, 디바운스는 짧은 시간 간격으로 발생하는 이벤트 > 그룹화, 마지막에 한 번만 이벤트 핸들러가 호출되도록 함.
<!DOCTYPE html>
<html>
<body>
<input type="text">
<div class="msg"></div>
<script>
const $input = document.querySelector('input');
const $msg = document.querySelector('.msg');
const debounce = (callback, delay) => {
let timerId;
// debounce 함수는 timerId를 기억하는 클로저를 반환한다.
return event => {
// delay가 경과하기 이전에 이벤트가 발생하면 이전 타이머를 취소하고
// 새로운 타이머를 재설정한다.
// 따라서 delay보다 짧은 간격으로 이벤트가 발생하면 callback은 호출되지 않는다.
if (timerId) clearTimeout(timerId);
timerId = setTimeout(callback, delay, event);
};
};
// debounce 함수가 반환하는 클로저가 이벤트 핸들러로 등록된다.
// 300ms보다 짧은 간격으로 input 이벤트가 발생하면 debounce 함수의 콜백 함수는
// 호출되지 않다가 300ms 동안 input 이벤트가 더 이상 발생하면 한 번만 호출된다.
$input.oninput = debounce(e => {
$msg.textContent = e.target.value;
}, 300);
</script>
</body>
</html>
문제 상황
- 사용자가
input
요소 내에 텍스트를 입력한다. input
이벤트 핸들러가 호출된다.- 이벤트 핸들러는 무거운 요청을 처리한다.
- 텍스트를 입력할 때마다 이벤트 핸들러가 호출되어 성능저하가 발생한다.
디바운스를 사용한 해결법
- 사용자가
input
요소 내에 텍스트를 입력한다. - 0.3초 전에 동일한 이벤트가 발생하면 이전 타이머를 취소하고 새 타이머를 재설정하는 디바운스 함수를 정의한다.
- 콜백 함수가 무거운 요청을 처리하더라도 사용자가 텍스트를 전부 작성한 이후(작성 후 0.3초 동안 이벤트가 없어야)에 처리가 된다.
디바운스 사용 예시
ajax
를 사용한 입력필드 자동완성- 버튼 중복 클릭 방지 처리
실무에서는 해당 예시의 debounce
함수보다 Underscore의 debounce
함수나 Lodash의 debounce
사용을 권장함.
스로틀
스로틀의 역할
짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 최대 한 번만 호출되도록 함.
요약하자면, 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트 > 그룹화, 일정 시간 단위로 핸들러가 호출되도록 호출 주기를 만듦.
<!DOCTYPE html>
<html>
<head>
<style>
.container {
width: 300px;
height: 300px;
background-color: rebeccapurple;
overflow: scroll;
}
.content {
width: 300px;
height: 1000vh;
}
</style>
</head>
<body>
<div class="container">
<div class="content"></div>
</div>
<div>
일반 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
<span class="normal-count">0</span>
</div>
<div>
스로틀 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
<span class="throttle-count">0</span>
</div>
<script>
const $container = document.querySelector('.container');
const $normalCount = document.querySelector('.normal-count');
const $throttleCount = document.querySelector('.throttle-count');
const throttle = (callback, delay) => {
let timerId;
// throttle 함수는 timerId를 기억하는 클로저를 반환한다.
return event => {
// delay가 경과하기 이전에 이벤트가 발생하면 아무것도 하지 않다가
// delay가 경과했을 때 이벤트가 발생하면 새로운 타이머를 재설정한다.
// 따라서 delay 간격으로 callback이 호출된다.
if (timerId) return;
timerId = setTimeout(() => {
callback(event);
timerId = null;
}, delay, event);
};
};
let normalCount = 0;
$container.addEventListener('scroll', () => {
$normalCount.textContent = ++normalCount;
});
let throttleCount = 0;
// throttle 함수가 반환하는 클로저가 이벤트 핸들러로 등록된다.
$container.addEventListener('scroll', throttle(() => {
$throttleCount.textContent = ++throttleCount;
}, 100));
</script>
</body>
</html>
문제 상황
- 사용자가
scroll
이벤트를 연속적으로 발생시킨다. - 이벤트가 과도하게 호출된다.
- 성능저하
스로틀을 사용한 해결법
- 사용자가
scroll
이벤트를 연속적으로 발생시킨다. - 0.1초 delay 이전에 동일한 이벤트가 발생하더라도 콜백 함수가 실행되지 않고 0.1초 delay가 지났을 때 콜백 함수를 호출한다.
스로틀 사용 예시
scroll
이벤트 처리- 무한 스크롤 UI 구현
실무에서는 해당 예시의 throttle
함수보다 Underscore의 throttle
함수나 Lodash의 throttle
사용을 권장함.
Ajax
Ajax란?
Ajax
: Asynchronous JavaScript and XML, 자바스크립트를 사용하여 브라우저가 서버에 비동기 방식으로 데이터를 요청하고, 서버가 응답한 데이터를 수신하여 웹 페이지를 동적으로 갱신하는 프로그래밍 방식.
- 브라우저에서 제공하는 Web API인
XMLhttpRequest
객체를 기반으로 동작 XMLhttpRequest는
HTTP 비동기 통신을 위한 메서드와 프로퍼티를 제공
기존의 웹 방식
html
태그로 시작하여 끝나는 완전한HTML
을 서버로부터 전송받아 웹 페이지를 전부 다시 렌더링한다.- 화면 전환이 필요한 경우, 서버로부터 새로운
HTML
을 받는다.
기존 웹 방식의 문제점
- 변경할 필요가 없는 부분까지 포함된 HTML을 매번 전송 받아야 했으므로 불필요한 데이터 통신이 발생했다.
- 변경할 필요가 없는 부분까지 다시 렌더링 하므로 화면 전환이 일어나면 화면 전체가 깜박이는 현상이 발생한다.
- 클라이언트와 서버와의 통신이 동기 방식으로 동작하므로 서버로부터 응답이 있을 때까지 다음 처리는 블로킹된다.
Ajax 방식의 이점
- 변경할 부분에만 필요한 데이터를 서버로부터 전송받기 때문에 불필요한 데이터 통신이 발생하지 않는다.
- 변경할 필요가 없는 부분을 다시 렌더링 하지 않는다. 때문에 깜박이는 현상이 발생하지 않는다.
- 클라이언트와 서버와의 통신이 비동기로 동작하므로 블로킹이 발생하지 않는다.
JSON
JSON
: JavaScript Object Notation, 클라이언트와 서버간의 HTTP 통신을 위한 텍스트 데이터 포맷.
언어에 종속되지 않으므로 대부분의 프로그래밍 언어에서 사용할 수 있다.
JSON 표기방식
{
"name": "Kim",
"age" : 20,
"alive" : true,
"hobby" : ["traveling", "tennis"]
}
- 객체 리터럴과 유사하다.
- 키와 값으로 구성된 순수 텍스트이다.
JSON
의Key
는 큰 따옴표로 묶어야 한다.JSON
의Value
는 객체리터럴과 같은 표기법이지만 문자열의 경우 무조건 큰따옴표로 묶어야 한다.
JSON.stringify
JSON.stringify
메서드는 객체를 JSON
포맷의 문자열로 변환한다.
직렬화: 객체를 문자열화 하는 것.
const obj = {
"name": "Kim",
"age" : 20,
"alive" : true,
"hobby" : ["traveling", "tennis"]
}
// 객체를 JSON 포맷 문자열로 변환
let json = JSON.stringify(obj);
// {"name":"Kim","age":20,"alive":true,"hobby":["traveling","tennis"]}
// 객체를 JSON 포맷 문자열로 변환(+ 들여쓰기)
json = JSON.stringify(obj, null, 2);
/*
{
"name": "Kim",
"age": 20,
"alive": true,
"hobby": [
"traveling",
"tennis"
]
}
*/
// ---------------------------------------------
// replacer 함수. 값의 타입이 Number면 필터링되어 반환되지 않는다.
function filter(key, value){
return typeof value === 'number' ? undefined : value;
}
// 두 번째 인수로 replacer 함수를 전달하면?
const strFilteredObject = JSON.stringify(obj, filter, 2);
/*
{
"name": "Kim",
"alive": true,
"hobby": [
"traveling",
"tennis"
]
}
*/
// ---------------------------------------------
// 배열도 변환이 가능하다.
const todos = [
{ id: 1, content: 'HTML', completed: false },
{ id: 2, content: 'CSS', completed: true },
{ id: 3, content: 'Javascript', completed: false }
];
json = JSON.stringify(todos, null, 2);
/*
[
{
"id": 1,
"content": "HTML",
"completed": false
},
{
"id": 2,
"content": "CSS",
"completed": true
},
{
"id": 3,
"content": "Javascript",
"completed": false
}
]
*/
JSON.parse
JSON.parse
메서드는 JSON
포맷의 문자열을 객체로 변환한다.
역직렬화: 문자열을 객체화 하는 것.
const obj = {
name: 'Lee',
age: 20,
alive: true,
hobby: ['traveling', 'tennis']
};
// 객체 > JSON 포맷의 문자열
const json = JSON.stringify(obj);
// JSON 포맷의 문자열 > 객체
const parsed = JSON.parse(json);
// { name: 'Lee', age: 20, alive: true, hobby: [ 'traveling', 'tennis' ] }
// ---------------------------------------------------------
const todos = [
{ id: 1, content: 'HTML', completed: false },
{ id: 2, content: 'CSS', completed: true },
{ id: 3, content: 'Javascript', completed: false }
];
// 베열 > JSON 포맷의 문자열
const json = JSON.stringify(todos);
// JSON 포맷의 문자열 > 배열
const parsed = JSON.parse(json);
/*
[
{ id: 1, content: 'HTML', completed: false },
{ id: 2, content: 'CSS', completed: true },
{ id: 3, content: 'Javascript', completed: false }
]
*/
XMLHttpRequest
XMLHttpRequest
: 자바스크립트를 사용하여 HTTP 요청을 전송하기 위해 사용되는 객체.
Web API이며, HTTP 요청 전송과 HTTP 응답 수신을 위한 다양한 메서드/프로퍼티를 제공한다.
XMLHttpRequest 객체 생성
XMLHttpRequest
생성자 함수를 호출하여 생성한다.
Web API이므로, 브라우저 환경에서만 정상적으로 실행된다.
const xhr = new XMLHttpRequest();
XMLHttpRequest 객체의 프로퍼티와 메서드
아래 링크 참고..
https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest
HTTP 요청 전송
HTTP 요청 전송 순서
XMLHttpRequest.Prototype.open
메서드로 HTTP 요청을 초기화- 필요에 따라 XMLHttpRequest.prototype.
setRequestHearder
메서드로 특정 HTTP 요청 헤더 값을 설정 XMLHttpRequest.prototype.send
메서드로 HTTP 요청을 전송
// 객체 생성
const xhr = new XMLHttpRequest();
// 1. 요청 초기화
// xhr.open(method, url[, async])
// method: HTTP 요청 메서드
// url: HTTP 요청을 전송할 URL
// async: 비동기 요청 여부(기본값 true)
xhr.open('GET', '/users');
// 2. HTTP 요청 헤더 설정
// 특정 HTTP 요청의 헤더 값을 설정
// open 메서드를 호출한 이후에 호출해야 한다.
// MIME 타입의 정보를 지정할 수 있다.
xhr.setRequestHeader('content-type', 'application/json');
// 3. 요청 전송
// GET 요청 전송: 데이터를 URL의 쿼리 문자열로 서버에 전송
// POST 요청 전송: 데이터를 요청 몸체에 담아 전송
// 데이터를 인수로 전달할 수 있으며, 이 경우 직렬화한 데이터를 전달해야 한다.
// HTTP 요청 메서드가 GET인 경우, send 메서드에 페이로드로 전달한 인수는 무시되고 요청 몸체는 null로 설정된다.
xhr.send();
HTTP 요청 메서드 |HTTP 요청 메서드|종류|목적|페이로드|
|—|—|—|—|
|GET|index/retrieve|모든/특정 리소스 취득|X|
|POST|create|리소스 생제|O|
|PUT|replace|리소스의 전체 교제|O|
|PATCH|modify|리소스의 일부 수제|O|
|DELETE|delete|모든/특정 리소스 삭제|X|
자주 사용되는 MIME 타입
|MIME 타입|서브타입| |—|—| |text|text/plain, text/html, text/css, text/javascript| |application|application/json, application/x-www-form-urlencode| |multipart|multipart/formed-data|
HTTP 응답 처리
서버가 전송한 응답을 처리하는 방법: XMLHttpRequest
객체가 발생시키는 이벤트를 캐치하기.
XMLHttpRequest
객체에 readyState
프로퍼티 값이 변경된 경우 발생하는 readystatechange
캐치하여 HTTP 응답을 처리한다.
// 객체 생성
const xhr = new XMLHttpRequest();
// 1. 요청 초기화
// 해당 URL은 Fake REST API를 제공하는 서비스
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
// 2. 요청 전송
xhr.send();
// readyState 프로퍼티가 변경될 때마다 발생하는 이벤트
xhr.onreadystatechange = () => {
// readyState는 HTTP 요청의 현재 상태를 나타냄
if (xhr.readyState !== XMLHttpRequest.DONE) return;
// 200: 정상응답, 200 이외: 에러 발생
if (xhr.status === 200) {
console.log(JSON.parse(xhr.response));
} else {
console.error('Error', xhr.status, xhr.statusText);
}
}
// --------------------------------------------
// HTTP 요청이 성공적으로 완료된 경우 발생
xhr.onload = () => {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.response));
} else {
console.error('Error', xhr.status, xhr.statusText);
}
}
출처
위키북스, 『모던 자바스크립트 Deep Dive』, 이웅모