필자가 공부한 내용을 적었습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>실습[보드판매량 마진구함] - 비동기처리</title>
<link rel="stylesheet" href="board.css">
<!-- 외부 자바스크립트 파일 참조하기
문제제기 - DOM Tree그리기전에 DOM API를 사용하게 되는 것 - 시점문제
해결방법 - 순서지향적인 , 절차지향적인 언어이다.
DOM구성이 완료된 후에 html문서의 태그정보를 접근해야 한다.
브라우저도 캐시메모리를 사용한다. -cache - localStorage, sessionStorage -개발자 필요한 정보 몰래 관리
CPU - Intel, AMD - cache크기 차이도 있다 - 부동소수점 연산 처리 속도 차이 - 병렬처리
브라우저 - DOM(API-유지보수, 재사용성-직관적이지 않다) + BOM(API) + javascript
-->
</head>
<body>
<!--
ES6 - 모듈화 (type=module)
자바스크립트 소스도 쪼갠다 - 클라우드 , 스프링, 카카오소셜로그인, 카카오페이지
-->
<script>
window.addEventListener('load', (event) => {
//버튼의 주소번지 따기
const btnMargin = document.querySelector("#btnMargin");
btnMargin.addEventListener('click', () => {
getBoardSold();
})
})
</script>
<h2>보드 판매량</h2>
<table width="300px" height="80px">
<tr>
<th width="120px">보드 판매량</th>
<td width="180px"><span id="boardSold">10</span></td>
</tr>
<!--
소비자가-구매가=보드 한개당 마진 금액
한개당 마진 금액*판매량=마진금액계산
-->
<tr>
<th>구매가</th>
<td><span id="cost">100</span>원</td>
</tr>
<tr>
<th>소비자가</th>
<td><span id="price">120</span>원</td>
</tr>
</table>
<h2>마진금액 : <span id="cash">0</span>원</h2>
<button type="submit" id="btnMargin">마진은?</button>
<script src="board1.js"></script>
</body>
</html>
boardSellAccount.html
30
board1.js
//비동기 통신 객체를 생성해서 담을 변수 선언
let xhrObject = null; //const상수선언 - 재정의불가함 - ECMAScript6
// 비동기 통신객체 생성하는 함수를 구현
function createRequest() {
//서버에 요청을 할 수 있는 객체를 생성한다.
try {
xhrObject = new XMLHttpRequest();
} catch (error) {
console.log(error);
}
if (xhrObject == null) {
alert('비동기 통신 객체 생성 에러');
}
} ////////////// end of createRequest
//어떤 조건(0->1->2->3->4)을 만족하면 시스템이 호출해주는 함수 - 콜백(CallBack) - 비동기처리 - 가중치
//개발자가 지정한 콜백함수를 시스템이 알 수 있도록 추가 코딩이 필요하다
function accountProcess() {
console.log(`accountProcess호출 => ${xhrObject.readyState}`); //0->1->2->3(다운중)->4(완료)
if (xhrObject.readyState == 4) {
if (xhrObject.status == 200) {
//200 -> OK
//20을 읽어온다. - boardSellAction.html(백엔드- 자바-스프링)
const newTotal = xhrObject.responseText; //새로 받아온 값을 담음
console.log(`${newTotal}`); //20 관전포인트 - 프론트와 백엔드 접점
const boardSoldEl = document.querySelector('#boardSold');
replaceText(boardSoldEl, newTotal); //10-->20
//구매가
const costEl = document.querySelector('#cost');
let cost = getText(costEl);
//소비자가
const priceEl = document.querySelector('#price');
let price = getText(priceEl);
//보드 한개당 마진 금액 계산
let cashPerBoard = price - cost;
let cash = cashPerBoard * newTotal;
console.log(`마진금액은 ${cash}`);
//마진금액을 붙여본다
const cashEl = document.querySelector('#cash');
replaceText(cashEl, cash);
}
}
}
// 마진은 버튼을 눌렀을 때 호출
function getBoardSold() {
const msg = 'getBoardSold호출';
console.log(`${msg}`);
//비동기객체 생성
createRequest();
//let url = './boardSellAccount.html';
const url = 'http://127.0.0.1:9000/board/boardSellAccount.jsp';
//집계를 담당하는 페이지를 호출
xhrObject.open('GET', url, true);
//콜백함수 지정 - readyState가 변할 때마다 브라우저 호출 - 상태 체크 프로세스
xhrObject.onreadystatechange = accountProcess; //함수도 객체다!!
xhrObject.send();
}
//@param - element - 태그 정보 - 태그 주소번지
function getText(el) {
let text = ''; //ES6 ->ECMAScript2015 - 적용안되는 브라우저이면 babel필요하다 또는 번들러 parcel(웹팩-리액트)
if (el != null) {
//파라미터로 받아온 element가 널이 아니면 존재하니
if (el.childNodes) {
//자스에서는 0이 아닌것 모두 false이다.
// el null스킵, undefined, "", NaN
console.log(el + ', ' + el.childNodes.length); //1 1
for (let i = 0; i < el.childNodes.length; i++) {
//el.childNodes.length
//costEl, priceEl
//브라우저는 같은이름이 여러개이면 자동으로 배열로 전환해준다
let childNode = el.childNodes[i]; //el.childNodes[0], el.childNodes[1]
//너 텍스트 노드니?
if (childNode.nodeValue != null) {
text = text + childNode.nodeValue;
}
}
}
}
return text; //100 120
}
//기존 TextNode의 값을 다른 문자열로 바꾸기
/***********************************************
param1 :document.getElementById("boardSold")
param1 :document.querySelector("#boardSold")
param2 :xhrObject.
************************************************/
function replaceText(el, value) {
//el-> boardSoldEl(보드판매량), cashEl(마진)
if (el != null) {
//span
clearText(el); //기존에 있던 10을 지워주세요.
//새로운 텍스트 노드 15를 생성하기
var newNode = document.createTextNode(value); //15
//위에서 생성한 텍스트 노드 값을 el에 붙이는 함수 호출하기
el.appendChild(newNode); //el boardSoldEl-> <span>10</span> <span id=boardSold or cash></span> <span>20</span>
}
}
//기존 태그안에 문자열 지우는 함수 구현
function clearText(el) {
if (el != null) {
if (el.childNodes) {
//자바스크립트에서는 0이아닌건 모두 참이다
for (let i = 0; i < el.childNodes.length; i++) {
let childNode = el.childNodes[i];
el.removeChild(childNode); //해당 el삭제하기 - DOM API -> 직관적이지 않다-> 유지보수어렵다->쓰기싫다
}
}
}
}
사용자에 의해 이벤트가 발생하면 핸들러에 의해 바로 자바스크립트를 불러온다.
그 후 자바스크립트에서는 XmlHttpRequest
객체를 이용하여 서버로 요청을 보내는데, 그동안 웹 브라우저는 응답
을 기다릴 필요 없이 다른 작업을 수행할 수 있다(비동기 방식).
서버측에서 처리를 마치고 XmlHttpRequest 객체를 전달 받으면 이를 토대로 Ajax요청을 처리하게 되는 것이다
(1) open(method, url, async)
: 서버로 보낼 Ajax 요청의 형식을 결정하는 메소드
(2) send()
: Ajax 요청을 서버로 전송하는 메소드
Get 방식일 경우에는 인자를 넣지 않고, Post 방식일 경우에는 querystring인자를 넣는다.
querystring은 서버로 데이터를 전달하기 위해 url뒤에 첨부되는 데이터이다
(3) Flow
XMLHttpRequest.UNSENT
: XMLHttpRequest 객체가 생성됨, 숫자 0
으로도 표기 가능
XMLHttpRequest.OPENED
: open 메소드
가 성공적으로 실행됨, 숫자 1로도 표기 가능
XMLHttpRequest.HEADERS_RECEIVED
: 모든 요청에 대한 응답이 도착함, 숫자 2로도 표기 가능
XMLHttpRequest.LOADING
: 요청한 데이터를 처리 중임, 숫자 3으로도 표기 가능
XMLHttpRequest.DONE
: 요청한 데이터의 처리가 완료되어 응답할 준비가 완료됨, 숫자 4로도 표기 가능
onreadystatechange
: readyState 값이 변할 때마다 자동으로 호출할 함수. 요청에서 응답까지 모든 과정이 정상적으로 이루어졌다면 총 5번이 호출될 것이다. 따라서, 이 메소드 내부에서 readyState값에 따라 알맞게 처리해줄 수 있다.
function getBoardSold() {
const msg = 'getBoardSold호출';
console.log(`${msg}`);
//비동기객체 생성
createRequest();
//let url = './boardSellAccount.html';
const url = 'http://127.0.0.1:9000/board/boardSellAccount.jsp';
//집계를 담당하는 페이지를 호출
xhrObject.open('GET', url, true);
//콜백함수 지정 - readyState가 변할 때마다 브라우저 호출 - 상태 체크 프로세스
xhrObject.onreadystatechange = accountProcess; //함수도 객체다!!
xhrObject.send();
}
xhrObject.onreadystatechange = accountProcess;
onreadystatechange
콜백 함수를 지정 해주는 것인데, readyState가 변할 때 마다 브라우저를 "호출" 한다.
readyState
가 변한다 ==> 라는게 뭐지?
위에서 ajax 동작과정을 보게 되면 0->1->2->3->4(done) 과정을 살펴 볼 수 있다.
이 5가지의 동작들을 readyState라고 하는데, 이것들이 변할 떄 마다, accountProcess를 호출한다. 라고 해석하면 된다.
해당 코드에서 살펴보면 xhrObject.onreadystatechange
를 사용하여 accountProcess
함수를 설정하면 XMLHttpRequest 객체의 readyState가 변경될 때마다 accountProcess 함수가 호출 된다.
accountProcess
함수 내에서 xhrObject.readyState
의 값이 4일 때만 특정 동작을 수행하도록 조건문을 사용하여 필터링 한 것이다. 이 지점이
function accountProcess() {
console.log(`accountProcess호출 => ${xhrObject.readyState}`); //0->1->2->3(다운중)->4(완료)
if (xhrObject.readyState == 4) {
if (xhrObject.status == 200) {
//200 -> OK
//20을 읽어온다. - boardSellAction.html(백엔드- 자바-스프링)
const newTotal = xhrObject.responseText; //새로 받아온 값을 담음
console.log(`${newTotal}`); //20 관전포인트 - 프론트와 백엔드 접점
const boardSoldEl = document.querySelector('#boardSold');
replaceText(boardSoldEl, newTotal); //10-->20
//구매가
const costEl = document.querySelector('#cost');
let cost = getText(costEl);
//소비자가
const priceEl = document.querySelector('#price');
let price = getText(priceEl);
//보드 한개당 마진 금액 계산
let cashPerBoard = price - cost;
let cash = cashPerBoard * newTotal;
console.log(`마진금액은 ${cash}`);
//마진금액을 붙여본다
const cashEl = document.querySelector('#cash');
replaceText(cashEl, cash);
}
}
}
여기서 조건문을 분기하여 작성한 것을 볼 수 있다. 즉, 4번인 상태에서만 동작하도록 개발자(필자)가 설정 한 것이다.
4번 인 상태(DONE) && 200인 상태(isOk) 밑 조건을 수행한다.
newTotal
은 정말 중요한 부분이니 자세히 설명하도록 하겠다.
function accountProcess() {
console.log(`accountProcess호출 => ${xhrObject.readyState}`); //0->1->2->3(다운중)->4(완료)
if (xhrObject.readyState == 4) {
if (xhrObject.status == 200) {
//200 -> OK
//20을 읽어온다. - boardSellAction.html(백엔드- 자바-스프링)
const newTotal = xhrObject.responseText; //새로 받아온 값을 담음
console.log(`${newTotal}`); //20 관전포인트 - 프론트와 백엔드 접점
const boardSoldEl = document.querySelector('#boardSold');
replaceText(boardSoldEl, newTotal); //10-->20
//구매가
const costEl = document.querySelector('#cost');
let cost = getText(costEl);
//소비자가
const priceEl = document.querySelector('#price');
let price = getText(priceEl);
//보드 한개당 마진 금액 계산
let cashPerBoard = price - cost;
let cash = cashPerBoard * newTotal;
console.log(`마진금액은 ${cash}`);
//마진금액을 붙여본다
const cashEl = document.querySelector('#cash');
replaceText(cashEl, cash);
}
}
}
newTotal은 xhrObject.resposeText를 받아온다. 이 지점이 어떤 의미를 갖을까?
xhrObject
는 XmlHttpRequest
객체이고, 해당 값을 읽어온다(responseText) 그렇다면 해당 값이 뭔데?
http://127.0.0.1:9000/board/boardSellAccount.jsp
./boardSellAccount.html
이 boardSellAccount.html이다. html문서에는 현재 값이 30이 저장되어있다.
정리하면,
boardSell.html
<script>
window.addEventListener('load', (event) => {
//버튼의 주소번지 따기
const btnMargin = document.querySelector("#btnMargin");
btnMargin.addEventListener('click', () => {
getBoardSold();
})
})
</script>
load
는 이벤트를 기다리고, 이벤트가 발생했을 때, 내부의 함수를 실행한다. 그리고 웹 페이지가 모든 리소스를 로딩하고 완전히 준비 되었을 때 실행한다.
window.addEventListener 부분은 페이지가 완전히 로드 된 후 실행된다.
HTML #btnMargin
아이디 요소를 btnMargin에 할당하며, 버튼을 클릭 했을 때 이벤트 처리를 하기 위해서 선언했다.
그 버튼을 클릭 했을 때, getBoardSold();
실행한다.
마진은? 누르면 해당 이벤트가 발생한다는 얘기다.
getBoardSold();
를 실행한다.
createRequest()
비동기 객체생성
open
xhrObject.onreadystatechange = accountProcess;
콜백함수를 accountProcess
로 지정
이 떄 readyState에 대해 조건을 분기한다. (done == 4)
readyState에(0~4)까지 변화 하도록 설계 -> 4번일때만 실행되도록 걸어놨다. (==200)
newTotal = xhrObject.responseText(boardSellAccount)
값을 넣는다.
boardSellAccount에 대한 정보는 객체에 저장되어 있고, (open - url) 값을 넣어줬다.
근데 send가 뒤에 있는데 이걸 어떻게 반영해?
if (xhrObject.readyState == 4) {
if (xhrObject.status == 200)
이 말은(4번 조건 및 200번 ok) 서버에 요청한 데이터의 처리가 완료되어 응답할 준비가 완료됐다는 얘기다.
처음으로 xhrObject
를 생성하면 readyState
는 0(Uninitialized)이 된다.
xhrObject.open()
을 호출하면 readyState
가 1(Opened)로
변경된다.
xhrObject.send()
가 호출되면 요청이 서버로 전송되고, readyState가 2(Sent)로 변경된다.
서버가 응답을 보내면 readyState가 3(Receiving)
으로 변경된다.
마지막으로 응답이 완료되고 데이터를 수신한 경우 readyState는 4(Done)로 변경
되고 accountProcess 호출된다.
4번 상태 == 객체의 정보를 쥐고 았더.
그리고 나서 newTotal이 xhrObject.responseText; 이 값을 읽어 올 수 있는 것이다. 이 부분이 백앤드에서 처리 해야 될 부분이다. 해당 객체를 읽어 올 수 있는 부분이다.
xhrObject.responseText;(30)를 쥐고 있고 newTotal에 들어간다.
boardSoldEl : 해당 HTML 아이디를 값을 받아와서 상수에 넣는다. (span)태그의 주소값 (10)
그리고 나서 replaceText를 실행한다.
마진은? 눌렀을 때, 보드 판매량 값이 change 되어야 한다. 그 새로운 값은 어디에 있어?
boardSellAccount에(30) 있고, 기존의 값 replaceText(boardSoldEl, newTotal)한다. --> 이 부분에서 change한다.
function replaceText(el, value) {
//el-> boardSoldEl(보드판매량), cashEl(마진)
if (el != null) {
//span
clearText(el); //기존에 있던 10을 지워주세요.
//새로운 텍스트 노드 15를 생성하기
var newNode = document.createTextNode(value); //15
//위에서 생성한 텍스트 노드 값을 el에 붙이는 함수 호출하기
el.appendChild(newNode); //el boardSoldEl-> <span>10</span> <span id=boardSold or cash></span> <span>20</span>
}
}
//기존 태그안에 문자열 지우는 함수 구현
function clearText(el) {
if (el != null) {
if (el.childNodes) {
//자바스크립트에서는 0이아닌건 모두 참이다
for (let i = 0; i < el.childNodes.length; i++) {
let childNode = el.childNodes[i];
el.removeChild(childNode); //해당 el삭제하기 - DOM API -> 직관적이지 않다-> 유지보수어렵다->쓰기싫다
}
}
}
}
(el, value) 안에 해당 주소번지를 가지고 교체를 하는데, 우선 화면에 10이 노출 되어 있으니 이 부분을 clear(e1) 해야한다. == 기존 태그안에 문자열을 지워야 한다. 중요한 부분은 지워야 한다는 것이다.
e1(boardSoldEl) 쥐고 clearText 함수를 실행한다. 그러면 e1.childNodes가 존재한다면 해당 for문을 돌면서 자식 객체들을 삭제한다.
childNodes: 자식 노드에 접근
현재 요소의 자식 노드가 포함된 NodeList를 반환한다 이때 반환되는 NodeList에는 요소 노드
뿐만 아니라 주석 노드와 같은 비 요소 노드
를 포함한다.
<div>
<!-- 내용 시작 -->
<p>은하수를 여행하는 히치하이커를 위한 안내서</p>
<p>오리엔트 특급살인</p>
</div>
-----------------------------------
document.querySelector("div").childNodes
크롬 브라우저를 열고 콘솔 창에서 div 요소의 childNodes 프로퍼티를 확인했다. 7개의 노드가 있는 노드 리스트가 나타난다. 이중에 요소 노드는 2개다.
나머지는 주석과 줄 바꿈이다.
아니 그래서 어떻게 span에서 10을 접근하는데? 자식도 아닌디?
DOM은 트리 구조로 표현되며, 웹 페이지의 각 요소(태그) 와 텍스트는 노드로 표현이 된다. span태그 내에 있는 10은 텍스트 노드로 존재하며, span 엘리먼트의 자식 노드 중 하나다. (검증완료)
여기서 "10"을 childNodes로 접근하는 이유는 DOM에서 자식 노드에 접근하기 위한 메커니즘 중 하나다.
10은 span 엘리먼트의 자식 노드 중 하나로 인식되어 이 속성을 통해 접근할 수 있다.
이제 clearText(el); 하게 되고 해당 값을 제거 한 다음에
el.appendChild(newNode);를 실시한다. e1(boardSoldEl) - (span)
그런데 여기에다가 newNode를 붙인다. 이 말은
<span> - el
<span> - newNode (x)
</span>
</span>
을 하겠다는게 아니라, newNode는 텍스트 노드이기 떄문에(서버로부터 받은 값)
<span>newNode</span> --> 바꿔치기 성공
그리고 나서 교체완료가 됐다. 정리하면 현재까지 replaceText(boardSoldEl, newTotal);
줄까지 분석 완료했다.
replace했다.
다음은
function accountProcess() {
console.log(`accountProcess호출 => ${xhrObject.readyState}`); //0->1->2->3(다운중)->4(완료)
if (xhrObject.readyState == 4) {
if (xhrObject.status == 200) {
//200 -> OK
//20을 읽어온다. - boardSellAction.html(백엔드- 자바-스프링)
const newTotal = xhrObject.responseText; //새로 받아온 값을 담음
console.log(`${newTotal}`); //20 관전포인트 - 프론트와 백엔드 접점
const boardSoldEl = document.querySelector('#boardSold');
replaceText(boardSoldEl, newTotal); //10-->20
//구매가
const costEl = document.querySelector('#cost');
let cost = getText(costEl);
//소비자가
const priceEl = document.querySelector('#price');
let price = getText(priceEl);
//보드 한개당 마진 금액 계산
let cashPerBoard = price - cost;
let cash = cashPerBoard * newTotal;
console.log(`마진금액은 ${cash}`);
//마진금액을 붙여본다
const cashEl = document.querySelector('#cash');
replaceText(cashEl, cash);
}
}
}
--------------------------------------
//구매가
const costEl = document.querySelector('#cost');
let cost = getText(costEl);
//소비자가
const priceEl = document.querySelector('#price');
let price = getText(priceEl);
//보드 한개당 마진 금액 계산
let cashPerBoard = price - cost;
let cash = cashPerBoard * newTotal;
console.log(`마진금액은 ${cash}`);
//마진금액을 붙여본다
const cashEl = document.querySelector('#cash');
replaceText(cashEl, cash);
밑 부분이 남았는데, 동작 매커니즘은 간단하다.
costEl로 html id값을 받아오고 저장한다. 이 말은 costEl가 (cost의 기존의 값 - 구매가)
<tr>
<th>구매가</th>
<td><span id="cost">100</span>원</td>
</tr>
의 값을 getText를 통해 받아오고 cost에 저장한다.
getText에 대해 알아보자.
function getText(el) {
let text = ''; //ES6 ->ECMAScript2015 - 적용안되는 브라우저이면 babel필요하다 또는 번들러 parcel(웹팩-리액트)
if (el != null) {
//파라미터로 받아온 element가 널이 아니면 존재하니
if (el.childNodes) {
//자스에서는 0이 아닌것 모두 false이다.
// el null스킵, undefined, "", NaN
console.log(el + ', ' + el.childNodes.length); //1 1
for (let i = 0; i < el.childNodes.length; i++) {
//el.childNodes.length
//costEl, priceEl
//브라우저는 같은이름이 여러개이면 자동으로 배열로 전환해준다
let childNode = el.childNodes[i]; //el.childNodes[0], el.childNodes[1]
//너 텍스트 노드니?
if (childNode.nodeValue != null) {
text = text + childNode.nodeValue;
}
}
}
}
return text; //100 120
}
text라는 변수를 선언 했다. 여기서 let으로 쓴 이유는 호출 시에 자유롭게 변경 할 수 있도록 설정하기 위해서다.
조건문1 el != null 이 말은 el이 값을 쥐고 있고, 자식 노드를 가지고 있다면(100-텍스트노드) - 검증완료
console.log(el + ', ' + el.childNodes.length); //1 1
for문을 돌면서 childNode 에 100을 담겠지 그리고 그 childNode노드가 값을 가지고 있다면, text에 100을 넣는다.
for (let i = 0; i < el.childNodes.length; i++) {
//el.childNodes.length
//costEl, priceEl
//브라우저는 같은이름이 여러개이면 자동으로 배열로 전환해준다
let childNode = el.childNodes[i]; //el.childNodes[0], el.childNodes[1]
//너 텍스트 노드니?
if (childNode.nodeValue != null) {
text = text + childNode.nodeValue;
}
}
text : ' ' + childNode.nodeValue(100) =
-> text = 100 --> return 100;
이런 매커니즘으로 작동한다. 그래서 cost는 100을 쥐고 있다.
price도 동일한 원리로 작동하며 price - cost를 한다.
replaceText 하는 원리다.
(설명 끝)
defer를 왜 사용하는가?
함수 단위로 관계를 설명하겠다.
createRequest
accountProcess
getBoardSold
clearText
replaceText
getText
window이벤트처리&& getBoardSold(마진을 눌렀을 때)
createRequest( 객체생성)
accountProcess (콜백함수 == 4)
생각보다 복잡하지만 로직을 파악하니깐 되게 재밌었다 ㅎㅎ 프로젝트를 하나 구상중인데 해당 부분을 참고하여 해야겠다
이상 끝!