2 알아두어야 할 자바스크립트
source: categories/study/nodejs/nodejs2.md
호출 스택이 실행 콘텍스트와 밀접한 관련이 있습니다.
그 내용과 ES2015+ 문법과 제 책에서 다루는 프론트엔드 자바스크립트, API들..
사실 이런 것들은 기본적으로 아시고 계셔야된다고 강의 초반에 말씀드리긴 했는데 복습차원에서 짚고 넘어가겠습니다.
만약 2강 내용을 모르신다면, 노드 공부는 잠시 멈추시고 자바스크립트 공부를 좀 더 하고 오셔야됩니다.
이정도는 아셔야 노드를 원활하게 진행하실 수 있습니다.
이번 장은 여러분들의 자바스크립트에 대한 이해도를 체크하시는 장이라고 생각하시면 됩니다.
2.1 호출 스택
2.1.1 호출 스택 알아보기 1
function first() {
second();
console.log('첫 번째');
}
function second() {
third();
console.log('두 번째');
}
function third() {
console.log('세 번째');
}
first();
위와 같이 세개를 선언을 했습니다.
선언을 했다라는 것은 메모리에 올렸다는 것입니다.
메모리는 임시 저장장치입니다.
컴퓨터가 잠깐 기억하고있다가 새로고침하면 까먹습니다.
그리고 first()
함수 호출로 함수를 실행합니다.
호출을 하면 메모리 속에서 선언을 했는지 찾아봅니다.
있으면 해당 함수를 실행하게됩니다.
-
위 코드의 순서 예측해보기
- 세 번째 -> 두 번째 -> 첫 번째
-
쉽게 파악하는 방법: 호출 스택 그리기
2.1.2 호출 스택 알아보기 2
-
호출 스택(함수의 호출, 자료구조의 스택)
- Anonymous는 가상의 전역 콘텍스트(항상 있다고 생각하는게 좋음)
Anonymous는 파일이 실행되면 생기고 파일이 끝나면 사라집니다.
즉, Anonymous까지 사라지면 자바스크립트의 모든 것이 실행 완료. - 함수 호출 순서대로 쌓이고, 역순으로 실행됨.
- 함수 실행이 완료되면 스택에서 빠짐.
- LIFO(Last In First Out) 구조라서 스택이라고 불림
- 호출 스택은 동기 - 순서대로 실행
호출 스택만 있을 때는 코드 파악하기가 쉬움
- Anonymous는 가상의 전역 콘텍스트(항상 있다고 생각하는게 좋음)
2.1.3 호출 스택 알아보기 3
function run() {
console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');
// 시작
// 끝
// 3초 후 실행
위 코드 실행 순서
- 메모리 어딘가에
run
이 저장 - console.log(‘시작’) - 호출스택에 쌓임 - 콘솔창에 ‘시작’을 찍고 다시 호출 스택에서 빠짐
- setTimeout 함수 - 호출스택에 쌓임 - 다시 바짐
- console.log(‘끝’) - 호출스택에 쌓임 - 콘솔창에 ‘끝’을 찍고 다시 호출 스택에서 빠짐
그런데 이렇게만 실행되는 거라면 뭔가 허전
3초 뒤에 run
이라는 함수를 실행을 해줘야되는데 앞에서 배운 호출스택 개념을 봤을 때 run
이 실행될 부분이 없음
호출 스택만으론 자바스크립트의 동작을 전부 설명할 수 없다는 뜻
setTimeout
이라는게 비동기라는 건데, 이 비동기를 설명하려면 이벤트 루프라는 걸 알아야됨
-
위 코드의 순서 예측해보기
- 시작 -> 끝 -> 3초 후 실행
- 호출 스택만으로는 설명이 안됨(run은 호출 안했는데?)
- 호출 스택 + 이벤트 루프로 설명할 수 있음
2.2 이벤트 루프 알아보기
2.2.1 교안 1
-
이벤트 루프 구조
- 이벤트 루프: 이벤트 발생(setTimeout 등)시 호출할 콜백 함수들(위 예제에선 run)을 관리하고, 호출할 순서를 결정하는 역할
- 테스크 큐: 이벤트 발생 후 호출되어야할 콜백 함수들이 순서대로 기다리는 공간
- 백그라운드: 타이머나 I/O 작업 콜백, 이벤트 리스너들이 대기하는 공간. 여러 작업이 동시에 실행될 수 있음.
2.2.2 교안 2
-
예제 코드에서 setTimeout이 호출될 때 콜백 함수 run은 백그라운드로
- 백그라운드에서 3초를 보냄
- 3초가 다 지난 후 백그라운드에서 테스크 큐로 보내짐
- setTimeout과 anonymous가 실행 완료된 후 호출 스택이 완전히 비워지면,
-
이벤트 루프가 테스크 큐의 콜백을 호출 스택으로 올림
- 호출 스택이 비워져야만 올림
- 호출 스택에 함수가 많이 차있으면 그것들을 처리하느라 3초가 지난 후에도 run 함수가 테스크 큐에서 대기하게됨 -> 타이머가 정확하지 않을 수 있는 이유
2.2.3 교안 3
-
run이 호출 스택에서 실행되고, 완료 후 호출 스택에서 나감
- 이벤트 루프는 테스크 큐에 다음 함수가 들어올 때까지 계속 대기
- 테스크 큐는 실제로 여러개고, 테스크 큐들과 함수들 간의 순서를 이벤트 루프가 결정함
2.2.4 부연설명
function run() {
console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');
다시 위 코드 실행 순서
- 메모리 어딘가에
run
이 저장 - 호출 스택에 anonymous 전역 콘텍스트가 쌓임
- console.log(‘시작’) - 호출스택에 쌓임 - 콘솔창에 ‘시작’을 찍고 다시 호출 스택에서 빠짐
- setTimeout 함수 - 호출스택에 쌓임
- setTimeout의 콜백 함수를 백그라운드로 보냄
- 백그라운드에 setTimeout의 콜백 함수인
run
이 쌓임, 3초 타이머와 함께 - setTimeout이 호출 스택에서 빠짐.
백그라운드로 간 타이머 3초와 호출스택에 있는 코드가 동시에 실행
console.log(‘끝’)이 실행될 때 타이머 3초를 세고있음
백그라운드 타이머 3초가 먼저 처리가 된다고 하더라도 항상 호출 스택이 먼저 처리되어야함 - console.log(‘끝’) - 호출 스택에 쌓임 - 콘솔창에 ‘끝’을 찍고 다시 호출 스택에서 빠짐
- 호출 스택에 anonymous 마저 빠짐, 여기서 끝? 아니다.
- 백그라운드에 있는 타이머가 3초 후에
run
함수를 테스크 큐로 보냄.
백그라운드에 있는 타이머는 사라짐 - 이벤트 루프가 이때 호출 스택이 완전히 비어있다면 테스크 큐에 있는 함수들을 하나하나 호출 스택으로 보내 실행을 시킴.
실행시키기 전에 다시 anonymous 전역 콘텍스트가 먼저 쌓임 - console.log(‘3초 후 실행’)이 스택에 쌓임 -> 3초 후 실행을 콘솔창에 찍은 후 -> 호출스택에서 사라짐
run
함수도 사라짐- anonymous 전역 콘텍스트도 사라짐
- 다시, 테스크 큐에 함수가 들어오면 이벤트 루프가 호출 스택에 아무것도 없을 때 함수를 보냄
호출 스택, 백그라운드, 테스크 큐에 아무것도 없어야 자바스크립트 실행이 완전히 종료된 것
노드도 자바스크립트 실행기이기 때문에 위의 순서대로 실행됩니다.
위의 설명은 쉽게 설명한거라 간단하지만 원래 호출 스택, 백그라운드, 테스크 큐, 이벤트 루프 하나하나가 원래는 좀 복잡합니다.
호출 스택 각각 this
, scope
등이 들어있기도 하고.. 자세히 파고들면 좀 복잡합니다.
-
질문: 백그라운드에 있는 함수들은 동기적으로 처리되는 건가요?
- 아니죠. 비동기적으로 처리되는 겁니다.
백그라운드는 다른 스레드가 처리합니다.
- 아니죠. 비동기적으로 처리되는 겁니다.
-
질문: 그렇다면 백그라운드가 다른 스레드라면 멀티 스레드랑 다름이 없잖아요?
그럼 호출 스택도 하나의 스레드, 백그라운드도 다른 하나의 스레드라면 멀티 스레드로 동작하는 거잖아요?
그럼 호출 스택 + 백르라운드 이렇게 멀티스레드로 돌리면 되지않나요?-
첫번째로 멀티 스레드 프로그래밍이 어려운데다가
노드에서 백그라운드로 보낼 수 있는 함수들을 제한을 해놨습니다.
예를 들어setTimeout
,setInterval
그리고네트워크 요청들(다른 서버로 갔다오는 요청들)
그리고 나중에 배우겠지만 하드디스크에있는 파일을 읽는fs
같은 명령어,
그리고 암호화하는crypto
라는 명령어, 그 다음에 압축하는zlib
이라는 명령어.
이런 정도만 백그라운드로 보낼 수 있습니다.동시에 실행될 수 있는 애들에 대해서 제한이 있고 나머지 여러분들이 작성한 코드는 모두가 동기적으로 돌아갑니다.
앞으로 강좌하면서 노드에서도 어떤 것들이 백그라운드로 갈 수 있는지에 대해 알려드리도록 하겠습니다.
-
-
질문: 테스크 큐는 하나인가요?
- 아뇨, 테스크 큐도 여러개로 나뉩니다.
테스크 큐가 있고 마이크로 테스크가 있는데 이것은 예시 코드로 보여드리겠습니다.
- 아뇨, 테스크 큐도 여러개로 나뉩니다.
function oneMore() {
console.log("one more");
}
function run() {
console.log("run run");
setTimeout(() => {
console.log("wow");
}, 0)
new Promise((resolve) => {
resolve("hi");
})
.then(console.log);
oneMore();
}
setTimeout(run, 5000);
// run run
// one more
// hi
// wow
- 호출 스택에
anonymous(전역 콘텍스트)
가 쌓임
프로그램이 실행되면 디폴트라고 생각하면됨 oneMore
,run
함수가 메모리에 저장setTimeout(run, 5000)
함수가 호출 스택에 쌓임setTimeout(run, 5000)
이 실행되고 호출 스택에서 다시 나가면서 백그라운드로타이머(run, 5000)
를 보냄- 코드를 다 실행했기 때문에 호출 스택의
anonymous
가 사라짐
그럼 여기서 프로그램 끝났어요?
아니죠. 백그라운드에 타이머 남아있죠.
백그라운드에서 5초를 세고run
함수를 테스크 큐로 보냅니다. - 이벤트 루프가 테스크 큐에 들어온
run
함수를 호출 스택에 아무 것도 없으므로 호출 스택으로 이동시킵니다.
호출 스택에 뭐라도 있으면 이벤트 루프는 테스크 큐에 있는 함수를 호출 스택으로 안 보냅니다.
보내면서 다시 anonymous(전역 콘텍스트)가 호출 스택에 먼저 쌓입니다.
그 다음에run
이 쌓입니다. run
함수 내부에 있는console.log('run run')
이 호출 스택에 쌓입니다.
콘솔 창에 run run을 찍고 호출 스택에서 사라집니다.setTimeout(() => {console.log('wow')}, 0)
함수가 호출 스택에 쌓입니다.
엇 타이머가 0초이면 바로 실행되는거 아닌가요?
아닙니다. 바로 실행되지 않습니다.
setTimeout은 무조건 백그라운드로 보냅니다.setTimeout(() => {console.log('wow')}, 0)
함수가 실행되고 백그라운드로타이머(console.log('wow'), 0)
를 보내고 호출스택에서 사라집니다.new Promise((resolve) => {resolve('hi')})
프로미스 함수가 실행됩니다.
프로미스는 내부 코드까진 ‘동기'입니다.
Promise 실행되는 순간 resolve도 같이 실행됩니다.
resolve(‘hi')까지 실행 후 끝납니다.- Promise 다음에
then()
이 있습니다.
Promise는then()
을 만나는 순간 비동기로 갑니다.
그래서resolve('hi')
함수 끝나고new Promise()
끝나고.then()
을 만나는 순간 비동기로 가기 때문에 - 백그라운드로
then console.log(resolve 데이터 hi)
가 갑니다.
즉, 이 시점에서 백그라운드에 있는 것은타이머(익명, 0), then console.log(hi)
- 그리고
oneMore
함수가 호출 스택에 쌓입니다.
그리고oneMore
함수 내부에 있는console.log('one more')
가 호출 스택에 쌓입니다.
콘솔 창에 one more을 찍고 console.log가 사라지고, oneMore도 사라집니다. -
run
도 실행이 끝났으므로run
도 호출스택에서 사라지고 anonymous도 다시 호출스택에서 사라집니다.이러면 프로그램 끝인가요?
아니죠. 백그라운드에 두개의 함수가 아직 남아있죠.
타이머(익명, 0)
,then console.log(hi)
여기서 중요한 것은 백그라운드는 누가 먼저 실행될지 모릅니다.
야 너는 그냥 평범한 타이머이고 얜 프로미스야 이러면서 먼저 이벤트 루프가 프로미스의then
을 호출 스택에 보냅니다.
그리고 먼저 실행됩니다.그래서 hi가 먼저 찍히고 익명 함수는 억울하지만, 어쩔 수 없어요. 프로미스가 우선순위가 더 먼저이기 때문에.
setTimeout으로 백그라운드 거쳐서 테스크 큐에 들어가는 콜백 함수나 Promise의 then을 통해서 백그라운드를 거쳐 테스크 큐로 가는 함수나 테스크 큐까지 가는 것은 똑같은데, 테스크 큐에 같이 있어도 새치기를 해서 Promise의 then/catch
가 호출 스택으로 먼저 간다는 것.
그런데 사실 이렇게만 생각하면 정확하진 않은게 테스크 큐 안에서도 순서가 더 있긴 합니다.
이벤트 루프가 테스크 큐에서 호출 스택으로 보내는 순서가 더 자세한 규칙들이 있긴 한데 거기까지는 솔직히 실무에서 그렇게 쓰이진 않고 제가 말씀드린Promise의 then쪽이 일반 타이머(setTimeout) 같은 것을 새치기를 한다.
비슷한 걸로 process.nextTick()
이란 것이 있는데, process.nextTick()
이것과 Promise의 then/catch
- 이 두개는 얄밉게도 타이머(setTimeout)
같은 애들을 새치기를 한다, 그렇게 생각하시면 됩니다.
Promise의 then/catch
, 타이머(setTimeout)
가 같이 테스크 큐로 들어간 경우엔 타이머(setTimeout)
을 새치기한다.process.nextTick()
도 마찬가지다.
-
그리고 백그라운드에서 동시 실행한다고 했는데, 자바스크립트는 싱글 스레드인데 백그라운드에서 어떻게 동시 실행을 하나요? 라고 생각하실 수도 있는데,
백그라운드는 자바스크립트가 아니라 C++입니다.
C++이나 아니면 운영체제 쪽이에요.
이 백그라운드 부분은 자바스크립트 엔진이 알아서 해주는 부분이기 때문에 이 부분은 사실 자바스크립트가 아닙니다. -
호출 스택만 자바스크립트이고 백그라운드와 테스크 큐는 다른 언어로 만들어져있고, 특히, 노드에서
libuv
알려드렸죠?
libuv
처럼 C++로 되어있는 부분이 있다 - 거기서 관리를 해주기 때문에 거기서 멀티 스레드로 돌아가게 해주는거지 자바스크립트 자체는 싱글 스레드이고
노드에서 14버전에서 나온worker-threads
나 브라우저에있는web-worker
얘네들을 써야만 자바스크립트에서 멀티 스레드를 하실 수가 있습니다.
그러니까 이런거 나오기 전까지는 싱글 스레드, 그리고 다른 언어의 백그라운드, 테스크 큐 - 이렇게해서 돌아갔다. 라고 보시면 됩니다.그래서 동시성이 있는거지 자바스크립트 자체에서는 없습니다.
-
setImmediate()
,setTimeout()
,process.nextTick()
은 3강에서 알려드리겠습니다.
위와 관련된 내용들이 3강에서 나옵니다.
아까setTimeout()
사용할 때 0초를 사용했는데, 노드에선setTimeout()
0초를 거의 안쓰고 이거를setImmediate()
함수로 사용합니다.
이 내용은 3강에서 다루도록 하겠습니다.
const
, let
이런 ES6+ 문법보다 실행 콘텍스트 + 이벤트 루프, 백그라운드, 테스크 큐에 대한 이해가 훨씬 더 중요합니다.
2.3 var, const, let
2.3.1 var, const, let 1
-
ES2015 이전에는 var로 변수를 선언
- ES2015부터는 const와 let이 대체
-
가장 큰 차이점: 블록 스코프(var은 함수 스코프)
if (true) { var x = 3; } console.log(x); // 3 if (true) { const y = 3; } console.log(y); // Uncaught ReferenceError: y is not defined
-
기존: 함수 스코프(function(){}이 스코프의 기준점)
- 다른 언어와는 달리 if나 for, while은 영향을 미치지 못함
- const와 let은 함수 및 블록({})에도 별도의 스코프를 가짐
현재는 var
를 안 써도 전혀 문제가 없습니다.
그래도 var
에 대해 알아둬야하는 이유가 옛날에 var
로 작성한 레거시 코드들 있죠?
그거 분석할 때는 특성을 아셔야되기 때문에 var
가 어떤 특성을 가지는지도 아셔야되지만, 여러분들이 직접 쓰실 때에는 var
를 쓰실 필요는 없습니다.
let
과 const
를 사용하시면 되는데, var
와 가장 큰 차이점은 스코프입니다.
- 블록 스코프 - 중괄호{}
자바스크립트 ES2015+에선 {} 중괄호를 기준으로 새로운 스코프가 생깁니다.
위 코드에서 var x = 3;
은 {} 블록 스코프 안에 들어있죠?
새로운 스코프 안에 들어있는겁니다.
그런데 문제가 var
는 이 블록 스코프를 무시합니다.
그렇기 때문에 블록 스코프 바깥에서도 x에 접근을 할 수가 있습니다.
그런데 const
와 let
으로 선언을 하면 얘네들은 블록 스코프를 존중을 해서 블록스코프 바깥에서 접근할 수 없습니다.
그럼 var
는 도대체 어떤 스코프를 갖고 있을까?
함수 스코프를 갖습니다. function
function으로 감쌌을 때 var
도 함수 바깥에서 접근할 수 없습니다.
function a() {
var y = 3;
}
console.log(y); // Uncaught ReferenceError: y is not defined
2.3.2 var, const, let 2
const a = 0;
a = 1; // Uncaught TypeError: Assignment to constant variable.
// 아래와 같은 것은 가능
const b = { name: 'hyungju-lee' };
b.name = 'LHJ';
let b = 0;
b = 1; // 1
const c; // Uncaught SyntaxError: Missing initializer in const declaration
-
const 상수
- 상수에 할당한 값은 다른 값으로 변경 불가
- 변경하고자 할 때는 let으로 변수 선언
- 상수 선언 시부터 초기화가 필요함
- 초기화를 하지 않고 선언하면 에러
웬만하면 const
사용 권장.
변수 값이 바뀔 때만 let
사용.
2.4 템플릿 문자열, 객체 리터럴
2.4.1 템플릿 문자열
-
문자열을 합칠 때 + 기호 때문에 지저분함
- ES2015 부터는 `백틱 사용 가능
- 백틱 문자열 안에
${변수}
처럼 사용
var num1 = 1;
var num2 = 2;
var result = 3;
var string1 = num1 + ' 더하기 ' + num2 + '는 \'' + result + '\'';
console.log(string1); // 1 더하기 2는 '3'
const num1 = 1;
const num2 = 2;
const result = 3;
const string1 = `${num1} 더하기 ${num2}는 '${result}'`;
console.log(string1); // 1 더하기 2는 '3'
백틱 문자열은 함수 호출 기능도 있습니다.
function a() {
console.log("실행");
}
a(); // 실행
a``; // 실행
위와 같이 백틱 문자로도 호출할 수 있습니다.
위와 같은 것을 뭐라고 부르냐면 태그드 탬플릿 리터럴(태그가 달린 탬플릿 리터럴)이라고 부릅니다.
위와 같이 함수를 호출하는 것도 최신 문법에 추가되었다는 것 기억하시면 될 것 같습니다.
2.4.2 객체 리터럴
-
ES5 시절의 객체 표현 방법
- 속성 표현 방식에 주목
var sayNode = function () {
console.log("Node");
}
var es = "ES";
var oldObject = {
sayJS: function () {
console.log("JS");
},
sayNode: sayNode,
};
oldObject[es + 6] = "Fantastic";
oldObject.sayNode(); // Node
oldObject.sayJS(); // JS
console.log(oldObject.ES6); // Fantastic
-
훨씬 간결한 문법으로 객체 리터럴 표현 가능
- 객체의 메소드에 :function을 붙이지 않아도됨
- { sayNode: sayNode }와 같은 것을 { sayNode }로 축약 가능
- [변수 + 값] 등으로 동적 속성명을 객체 속성 명으로 사용 가능
const sayNode = function () {
console.log("Node");
}
const es = "ES";
const newObject = {
sayJS() {
console.log("JS");
},
sayNode,
[es + 6]: "Fantastic",
}
newObject.sayNode(); // Node
newObject.sayJS(); // JS
console.log(newObject.ES6); // Fantastic
2.5 화살표 함수
2.5.1 화살표 함수
let
과 const
는 var
을 거의 완벽하게 대체하기 때문에 var
를 더이상 사용하지 않아도 됩니다.
하지만 화살표 함수는 function
을 완벽하게 대체하지 않습니다.
그래서 무조건 화살표 함수만 써야된다고 생각하면 안됩니다.
화살표 함수가 기존 function
에 비해 좋은 점은 더 간결하게 작성이 가능하다는 것입니다.
최신 문법에선 기존 문법을 간결하게하는 기능이 많이 추가가 되었습니다.
그래서 알아두면 코드를 많이 간결화할 수 있습니다.
function add1(x, y) {
return x + y;
}
const add2 = (x, y) => {
return x + y;
}
const add3 = (x, y) => x + y;
const add4 = (x, y) => (x + y);
function not1(x) {
return !x;
}
const not2 = x => !x;
-
add1, add2, add3, add4는 같은 기능을 하는 함수
- add2: add1을 화살표 함수로 나타낼 수 있음
- add3: 함수의 본문이 return만 있는 경우 return 생략
- add4: return이 생략된 함수의 본문을 소괄호로 감싸줄 수 있음
- not1과 not2도 같은 기능을 함(매개변수 하나일 때 괄호 생략)
const obj = (x, y) => {
return { x, y }
// return { x: x, y: y}를 위처럼 생략 가능
}
// 위의 코드를 아래와 같이 생략하면
// 자바스크립트 엔진이 {x, y}의 {}를 해석을 못합니다. 객체를 의미하는 건지 함수의 바디를 의미하는 건지.
const obj = (x, y) => {x, y};
// 그래서 이렇게 객체를 리턴하는 경우에만 소괄호가 필수
const obj = (x, y) => ({x, y});
2.5.2 화살표 함수
화살표 함수가 있는데 기존 function이 안 사라진 이유 - this
때문
function은 this를 스스로 갖는데, 화살표 함수는 부모의 this를 물려받는다.
아래 코드를 콘솔로 찍어보고 that을 this로 바꾸고나서 콘솔에 찍어보세요.
var relationship1 = {
name: "zero",
friends: ["nero", "hero", "xero"],
logFriends: function () {
var that = this; // relationship1을 가리키는 this를 that에 저장
this.friends.forEach(function (friend) {
console.log(that.name, friend);
})
},
}
relationship1.logFriends();
// zero nero
// zero hero
// zero xero
var relationship1 = {
name: "zero",
friends: ["nero", "hero", "xero"],
logFriends: function () {
this.friends.forEach(function (friend) {
console.log(this.name, friend);
})
},
}
relationship1.logFriends();
// nero
// hero
// xero
function은 function마다 자기만의 this를 갖기 때문에 부모의 this를 받으려면 위 코드처럼 that 이런 변수에 저장을 해서 전달을 했었습니다.
그런데 화살표 함수를 쓰면 자기만의 this를 갖지 않습니다.
무조건 부모의 this를 물려받습니다.
-
화살표 함수가 기존 function(){}을 대체하는 건 아님(this가 달라짐)
- logFriends 메서드의 this 값에 주목
- forEach의 function의 this와 logFriends의 this는 다름
- that이라는 중간 변수를 이용해서 logFriends의 this를 전달
const relationship2 = {
name: "zero",
friends: ["nero", "hero", "xero"],
logFriends() {
this.friends.forEach(friend => {
console.log(this.name, friend);
})
},
}
relationship2.logFriends();
// zero nero
// zero hero
// zero xero
위 코드를 보시면 화살표 함수 내에서 부모의 this를 물려받는 것을 보실 수 있습니다.
-
forEach의 인자로 화살표 함수가 들어간 것에 주목
- forEach의 화살표 함수의 this와 logFriends의 this가 같아짐
- 화살표 함수는 자신을 포함하는 함수의 this를 물려받음
- 물려받고 싶지 않을 때: function() {}을 사용
이러한 특징 때문에 function은 아직까지도 많이 사용합니다.
자기만의 this
를 가져야할 때 function을 사용합니다.
// 아래와 같은 경우에 function을 사용합니다.
const button = document.querySelector("button");
button.addEventListener("click", function () {
console.log(this.textContent);
})
// 아래와 같은 경우면 동작을 안합니다.
// 아래의 this는 해당 이벤트리스너 바깥에 있는 this를 물려받기 때문입니다.
this;
const button = document.querySelector("button");
button.addEventListener("click", () => {
console.log(this.textContent);
})
// 그래서 화살표 함수를 활용하려면 아래와 같이 바꿔줘야한다.
const button = document.querySelector("button");
button.addEventListener("click", (e) => {
console.log(e.target.textContent);
})
function과 화살표 함수는 this
에서 엄청난 차이점이 있다.
this
를 써야되는 상황이라면 function을 쓰시고 this
를 안써도되면 화살표 함수를 쓰시는 것을 권장드립니다.
2.6 비구조화 할당
2.6.1 교안 1
var candyMachine = {
status: {
name: "node",
count: 5,
},
getCandy: function () {
this.status.count--;
return this.status.count;
},
}
var getCandy = candyMachine.getCandy;
var count = candyMachine.status.count;
-
var getCandy와 var count에 주목
- candyMachine부터 시작해서 속성을 찾아 들어가야 함
2.6.2 교안 2
const candyMachine = {
status: {
name: "node",
count: 5,
},
getCandy() {
this.status.count--;
return this.status.count;
},
}
const { getCandy, status: { count } } = candyMachine;
-
const {변수} = 객체;로 객체 안의 속성을 변수명으로 사용 가능
- 단, getCandy()를 실행했을 때 결과가 candyMachine.getCandy()와는 달라지므로 주의
this
때문
- 단, getCandy()를 실행했을 때 결과가 candyMachine.getCandy()와는 달라지므로 주의
-
count처럼 속성 안의 속성도 변수명으로 사용 가능
2.6.3 교안 3
-
배열도 구조분해 할당 가능
var array = ["nodejs", {}, 10, true]; var node = array[0]; var obj = array[1]; var bool = array[3];
const array = ["nodejs", {}, 10, true]; const [node, obj, , bool] = array;
-
const [변수] = 배열; 형식
- 각 배열 인덱스와 변수가 대응됨
- const [node, obj, , bool] = array;
node는 array[0], obj = array[1], bool = array[3]
const example = {
a: 123,
b: {
c: 135,
d: 146
}
}
const a = example.a;
const d = example.b.d;
console.log(a); // 123
console.log(d); // 146
자바스크립트에서 위와 같은 상황이 꽤 많이 나옵니다.
그런데 위와 같이 쓰면 불편합니다. 코드 길이도 길어지고.
const example = {
a: 123,
b: {
c: 135,
d: 146
}
}
const {a, b: { d }} = example;
console.log(a); // 123
console.log(d); // 146
// 아래와 같이 다른 변수이름을 부여하는 것도 가능
// const {a, b: {d: e}} = example;
// console.log(e); // 146
그래서 위와 같은 문법이 생겼습니다.
이게 바로 distructuring
문법입니다.
배열도 아래와 같은식으로 distructuring
문법이 가능합니다.
var arr = [1, 2, 3, 4, 5];
const x = arr[0];
const y = arr[1];
const z = arr[4];
const arr = [1, 2, 3, 4, 5];
const [x, y, , , z] = arr;
2.7 클래스
-
프로토타입 문법을 깔끔하게 작성할 수 있는 Class 문법 도입
- Constructor(생성자), Extends(상속) 등을 깔끔하게 처리할 수 있음
- 코드가 그룹화되어 가독성이 향상됨
-
과거의 프로토타입 객체지향 코드 작성법
// 과거 문법은 아래와 같이 생성자 함수/static 함수/인스턴스(prototype) 메소드를 각각 따로따로 작성했습니다. // 과거의 생성자 함수, 보통 대문자로 작성 var Human = function (type) { this.type = type || "human"; } // 과거의 static 메소드 Human.isHuman = function (human) { return human instanceof Human; } // 과거의 인스턴스(prototype) 메소드 Human.prototype.breathe = function () { alert('h-a-a-a-m'); } // 부모 클래스를 상속받기 위해 빌트인 함수 Object의 apply 메소드 활용 var Zero = function (type, firstName, lastName) { Human.apply(this, arguments); this.firstName = firstName; this.lastName = lastName; } Zero.prototype = Object.create(Human.prototype); // 상속하는 부분, 부모의 prototype 메소드를 그대로 Zero(자식) 클래스에 넣어주고 // 또는 Zero.prototype = new Human(); // <- 이렇게.. 이게 위의 코드와 똑같은 역할을하는 코드이다. Zero.prototype.constructor = Zero; // 상속하는 부분, 부모의 constructor(생성자) 함수를 Zero(자식) 클래스에 넣어주고 Zero.prototype.sayName = function () { alert(this.firstName + ' ' + this.lastName); } var oldZero = new Zero("human", "Zero", "Cho"); Human.isHuman(oldZero); // true
-
부모 클래스의 프로퍼티 모두가 연결되기 때문에 중간에 Bridge 생성
// 과거 문법은 아래와 같이 생성자 함수/static 함수/인스턴스(prototype) 메소드를 각각 따로따로 작성했습니다. // 과거의 생성자 함수, 보통 대문자로 작성 var Human = function (type) { this.type = type || "human"; } // 과거의 static 메소드 Human.isHuman = function (human) { return human instanceof Human; } // 과거의 인스턴스(prototype) 메소드 Human.prototype.breathe = function () { alert('h-a-a-a-m'); } // 부모 클래스를 상속받기 위해 빌트인 함수 Object의 apply 메소드 활용 var Zero = function (type, firstName, lastName) { Human.apply(this, arguments); this.firstName = firstName; this.lastName = lastName; } function Bridge() {} // Bridge 함수 만들고 Bridge.prototype = Human.prototype; // Person 프로토타입을 넣어주고 Zero.prototype = Object.create(Bridge.prototype); // 상속하는 부분, 부모의 prototype 메소드를 그대로 Zero(자식) 클래스에 넣어주고 // 또는 Zero.prototype = new Bridge(); // <- 이렇게.. 이게 위의 코드와 똑같은 역할을하는 코드이다. Zero.prototype.constructor = Zero; // 상속하는 부분, 부모의 constructor(생성자) 함수를 Zero(자식) 클래스에 넣어주고 Zero.prototype.sayName = function () { alert(this.firstName + ' ' + this.lastName); } var oldZero = new Zero("human", "Zero", "Cho"); Human.isHuman(oldZero); // true
-
전반적으로 코드 구성이 깔끔해짐
- Class 내부에 관련된 코드들이 묶임
- Super로 부모 Class 호출
- Static 키워드로 클래스 메소드 생성
class Human {
// 생성자 함수
constructor(type = "human") {
this.type = type;
}
// static 메소드
static isHuman(human) {
return human instanceof Human;
}
// 인스턴스(prototype) 메소드
breathe() {
alert('h-a-a-a-m');
}
}
class Zero extends Human {
constructor(type, firstName, lastName) {
super(type); // 부모의 constructor 함수 실행, 즉 constructor(type = 'human') {this.type = type;} 이 부분이 실행
this.firstName = firstName;
this.lastName = lastName;
}
sayName() {
super.breathe(); // 이건 위의 과거 문법 코드 예시와는 일치 안함, 그냥 이렇게 부모 인스턴스 메소드를 넣을 수 있다는 것을 보여주려고 넣은듯함
alert(`${this.firstName} ${this.lastName}`);
}
}
const newZero = new Zero("human", "Zero", "Cho");
Human.isHuman(newZero); // true
2.8 Promise, async/await
2.8.1 Promise
-
콜백 헬이라고 불리는 지저분한 자바스크립트 코드의 해결책
- 프로미스: 내용이 실행은 되었지만 결과를 아직 반환하지 않은 객체
- then을 붙이면 결과를 반환함
- 실행이 완료되지 않았으면 후에 then 내부 함수가 실행됨
- resolve(성공리턴값) -> then으로 연결
- reject(실패리턴값) -> catch로 연결
- finally 부분은 무조건 실행됨
const condition = true; // true면 resolve, false면 reject
// 아래 Promise 안의 코드까지는 동기적으로 실행
// Promise 안에서 무언가를 하게되는데, 무언가를 하고나서 resolve를 호출하면 "성공"했다는 뜻
// reject를 호출하면 "실패"했다는 뜻, 완료가 안되었거나.
// 여튼 성공했던 실패했던 그 값을 계속 들고있습니다.
// promise 변수에서 들고있다가 나중에 원할 때 꺼낼 수가 있습니다. 아래 then 메소드를 활용해서, 실패하면 catch로.
// 그래서 promise 부분은 두 가지입니다. 성공했거나 실패했거나, 그런데 그 값을 계속 promise 변수가 들고있다가 나중에 promise에 then이나 catch를 붙여서 받을 수 있는겁니다.
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve("성공");
} else {
reject("실패");
}
})
// 다른 코드가 들어갈 수 있음
promise
.then((message) => {
console.log(message); // 성공(resolve)한 경우 실행
})
.catch((error) => {
console.error(error); // 실패(reject)한 경우 실행
})
.finally(() => { // 끝나고 무조건 실행
console.log("무조건");
})
// 성공
// 무조건
이 프로미스가 콜백이랑 결정적으로 다른 점이 콜백 함수는 항상 코드가 어디있죠?
function () {
callback(...)
}
위 코드처럼 함수 안에 들어가있죠.
프로미스와는 다르게.
그렇기 때문에 무언가를 하면 바로 실행이 됩니다.
프로미스의 필요성을 잘 못느끼시는 분들이 많은데, 코드를 분리할 수 있냐 없냐는 엄청난 차이입니다.
setTimeout(() => {
// 3초 후에 실행될 코드..
}, 3000)
만약 위와 같이 코드가 있으면 3초 후에 실행되는 코드는 항상 위 {} 블록 안에 있어야됩니다.
function callback() {
}
setTimeout(callback, 3000);
위와 같은 식으로 바깥으로 콜백함수를 뺄 수는 있지만, 어쨌든 항상 setTimeout 안으로 들어가야됩니다.
하지만 Promise 객체를 사용한다고 한다면
const promise = new Promise();
console.log("딴짓");
console.log("딴짓");
console.log("딴짓");
console.log("딴짓");
console.log("딴짓");
console.log("딴짓");
console.log("딴짓");
promise.then(() => {
// 지금할래
})
위와 같이 잠시 promise 변수에 저장을 해두고 "딴짓"을 엄청 합니다.
그리고 "딴짓"을 다 했으면 위 코드 처럼 그때 실행시키면 됩니다.
코드를 분리할 수 있는겁니다.
-
질문: 어? 저는 코드 작성하면서 위와 같이 분리해야된다는 필요성을 못 느꼈는데요?
- 그럴수도있는데 분리를 할 수 있는 것과 아예 분리조차 못하는 것과는 엄청난 차이입니다.
계속 콜백펑션을 활용하면 코드가 엄청 지저분해지는 경우도 생깁니다.
그런데 Promise를 활용하면 위와 같이 저장해놓고 나중에 필요할 때 꺼내서 사용할 수 있기 때문에 훨씬 편합니다.
- 그럴수도있는데 분리를 할 수 있는 것과 아예 분리조차 못하는 것과는 엄청난 차이입니다.
프로미스는 무조건 알아둬야하는게 지금 노드 생태계가 콜백에서 프로미스로 전환되고 있습니다.
대부분의 함수들이 Promise를 지원하는 방향으로 바뀌고 있거든요.
그래서 Promise는 꼭 익혀두셔야합니다.
-
프로미스의 then 연달아 사용 가능(프로미스 체이닝)
- then 안에서 return한 값이 다음 then으로 넘어감
- return 값이 프로미스면 resolve 후 넘어감
- 에러가 난 경우 바로 catch로 이동
- 에러는 catch에서 한 번에 처리
const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve("성공");
} else {
reject("실패");
}
})
promise
.then((message) => {
return new Promise((resolve, reject) => {
resolve(message);
})
})
.then((message2) => {
console.log(message2);
return new Promise((resolve, reject) => {
resolve(message2);
})
})
.then((message3) => {
console.log(message3);
})
.catch((error) => {
console.log(error);
})
// 성공
// 성공
-
콜백 패턴(3중첩)을 프로미스로 바꾸는 예제
아래 예시 코드는 8장에서 배우는 몽구스 코드입니다.function findAndSaveUser(Users) { Users.findOne({}, (err, user) => { // 첫번째 콜백 if (err) { return console.error(err); } user.name = "zero"; user.save((err) => { // 두번째 콜백 if (err) { return console.error(err); } Users.findOne({gender: "m"}, (err, user) => { // 세번째 콜백 // 생략 }) }) }) }
위와 같은 경우가 심해지면 심해질 수록 콜백 지옥(헬)이 생깁니다.
프로미스를 사용하면 위와 같은 콜벡 핼을 막을 수 있습니다. -
findOne, save 메소드가 프로미스를 지원한다고 가정
- 지원하지 않는 경우 프로미스 사용법은 3장에 나옴
프로미스를 사용하면 아래와 같이 코드를 작성할 수 있습니다.
- 지원하지 않는 경우 프로미스 사용법은 3장에 나옴
function findAndSaveUser(Users) {
Users.findOne({})
.then((user) => {
user.name = "zero";
return user.save();
})
.then((user) => {
return User.findOne({gender: "m"});
})
.then((user) => {
// 생략
})
.catch((user) => {
console.error(err);
})
}
하지만 위의 코드도 then
이 계속 반복돼서 보기가 싫습니다.
위의 코드도 다른 방법으로 작성할 수 있는 방법이 있습니다.
바로 아래와 같이 Promise.all()
을 사용하는 방법입니다.
- Promise.resolve(성공리턴값): 바로 resolve하는 프로미스
-
Promise.reject(실패리턴값): 바로 reject하는 프로미스
const promise1 = Promise.resolve("성공1"); const promise2 = Promise.resolve("성공2"); Promise.all([promise1, promise2]) .then((result) => { console.log(result); // ['성공1', '성공2'] }) .catch((error) => { console.error(error); })
-
Promise.all(배열): 여러 개의 프로미스를 동시에 실행
- 하나라도 실패하면 catch로 감
- allSettled로 실패한 것만 추려낼 수 있음
요즘은all()
보다도allSettled
를 사용하는 추세입니다.
요즘은Promise.all()
보다Promise.allSettled()
를 사용한다는 거!
resolve
, reject
에 대해서 다시 설명드리자면, Promise는 어떤 동작을 하는 거거든요?
예를 들어, "저 파일을 읽어와" 아니면 "naver.com에 무슨 요청을 하고 와"
그런데 그 요청이 항상 성공하는 것이 아니라 실패할 수가 있습니다.
비동기로 작동하는 애들은 항상 실패를 염두해둬야하거든요?
물론 동기인 코드도 실패할 수 있습니다.
여튼 예를 들어 naver 서버가 저를 거부했다면, "너 악성 요청이야" 이러고 잘랐다면 실패한거죠?
즉 성공한 경우는 resolve를 호출하는 거고 실패한 경우는 reject를 호출하는 겁니다.
그래서 둘이 나눠져있는 것이고, resolve를 호출하면 then으로 가고 reject를 호출하면 catch로 갑니다.
항상 어떤 동작을 하면 그 동작이 성공하거나 실패하기 때문에,
const promise = new Promise((resolve, reject) => {
if (true) {
resolve("성공");
} else {
reject("실패");
}
})
그런데 프로미스는 저희의 말을 잘 듣기 때문에 실패해도 아래와같이 resolve를 호출하면 then으로 가겠죠?
const promise = new Promise((resolve, reject) => {
if (true) {
reject("성공");
} else {
resolve("실패");
}
})
resolve와 reject는 개발자가 알아서 잘 구분해줘야됩니다.
2.8.2 async/await
-
이전 챕터의 프로미스 패턴 코드
-
async/await로 한번 더 축약 가능
function findAndSaveUser(Users) { Users.findOne({}) .then((user) => { user.name = "zero"; return user.save(); }) .then((user) => { return Users.findOne({ gender: "m" }); }) .then((user) => { // 생략 }) .catch(err => { console.error(err); }) }
-
-
async function의 도입
- 변수 = await 프로미스; 인 경우 프로미스가 resolve된 값이 변수에 저장
-
변수 await 값;인 경우 그 값이 변수에 저장
// 위의 코드를 더 줄이고 싶을 때 아래와 같이 줄일 수 있다. // await가 then 역할을 한다고 보시면 됩니다. // then의 파라미터 user가 이젠 왼쪽 let 변수에 있음 // 즉, 아래는 실행순서가 오른쪽에서 왼쪽이라고 보시면됨 (await가 나왔을 땐 이를 조심) - then일 땐 왼쪽에서 오른쪽으로 흘러감 async function findAndSaveUser(Users) { let user = await Users.findOne({}); user.name = "zero"; user = await user.save(); user = await Users.findOne({gender: "m"}); // 생략 }
그런데 옛날엔 아래의 코드를
const promise = new Promise(() => {
// ...
});
promise.then((result) => {
// ...
})
아래와 같이 작성하는 것이 안됐었습니다.
const promise = new Promise(() => {
// ...
});
const result = await promise;
요즘은 됩니다.
그런데 옛날엔 왜 안됐냐면 await
을 쓰려면 반드시 async function
이 필요하거든요?
const promise = new Promise(() => {
// ...
});
async function a() {
const result = await promise;
}
a();
위와 같이 말이죠.
await
는 항상 async function
안에만 들어있어야 했습니다.
그런데 이게 탑레벨 await라는 것이 나와서 요즘은
const promise = new Promise(() => {
// ...
});
const result = await promise;
위와 같이 작성하는 것이 됩니다.
자바스크립트가 점점 편한쪽으로 발전을 하고있죠?
불안하시면 async function
으로 감싸주셔도 됩니다.
그리고 지금 질문으로 올라오는게
const promise = new Promise(() => {
// ...
});
async function a() {
const result = await promise;
return "hyungju-lee";
}
a();
위와 같이 async function
에서 return
하는 거 있죠?
얘네들, 이 return
한 것은 무조건
const promise = new Promise(() => {
// ...
});
async function a() {
const result = await promise;
return "hyungju-lee";
}
a().then((res) => {
})
위와 같이 then()
으로 받아야합니다.
async
도 Promise거든요?
아니면 아래와 같이 해도 될겁니다.
const promise = new Promise(() => {
// ...
});
async function a() {
const result = await promise;
return "hyungju-lee";
}
const name = await a();
async
는 결론은 Promise입니다.
Promise의 문법을 간단하게 만든거기 때문에 async도 결국 Promise라서 Promise의 성질을 그대로 가집니다.
const promise = new Promise(() => {
// ...
});
async function a() {
const result = await promise;
return result;
}
const name = await a();
// a().then((name) => ...)
위와 같이 result를 return하셔도 됩니다.
그럼 result로 받은걸 name으로 전달할 수 있겠죠?
마지막에 name 변수와 await
를 활용하시던지 then
을 활용하시던지 선택하시면 됩니다.
다만, async/await
를 사용하실 때 주의하실 점은 catch
가 없죠?
Promise가 실패할 경우
그럴 때는 아래와 같이 try-catch 구문으로 감싸주셔야됩니다.
const promise = new Promise(() => {
// ...
});
async function a() {
try {
const result = await promise;
return result;
} catch (error) {
console.error(error);
}
}
const name = await a();
// a().then((name) => ...)
위와 같이 try-catch
구문으로 감싸주셔야합니다.
별로 감싸주고 싶지는 않은데 async/await
가 Promise
와 똑같은데 resolve
만 처리하는 부분이 있고 reject
를 처리하는 부분이 없어서 reject
를 처리하려면 위와 같이 try-catch
문으로 감싸서 처리를 해주셔야됩니다.
사실 try-catch
문 사용하는 것이 드문 케이스인데 async/await
을 쓰면서 try-catch
문의 존재를 아시게된 분들도 많습니다.
원래부터 자바스크립트에 try-catch
문이 있는데 async function
때문에 많이 쓰게됐죠.
-
에러 처리를 위해
try catch
로 감싸주어야함-
각각의 프로미스 에러 처리를 위해서는 각각을
try catch
로 감싸주어야함async function findAndSaveUser(Users) { try { let user = await Users.findOne({}); user.name = "zero"; user = await user.save(); user = await Users.findOne({gender: "m"}); // 생략 } catch (error) { console.error(error); } }
-
-
화살표 함수도
async/await
가능const findAndSaveUser = async (Users) => { try { let user = await Users.findOne({}); user.name = "zero"; user = await user.save(); user = await Users.findOne({gender: "m"}); // 생략 } catch (error) { console.error(error); } }
-
async
함수는 항상promise
를 반환(return)-
then이나 await를 붙일 수 있음
async function findAndSaveUser(Users) { // 생략 } findAndSaveUser().then(() => { // 생략 }) // 또는 async function other() { const result = await findAndSaveUser(); }
-
2.8.3 for await of
- 노드 10부터 지원
-
for await (변수 of 프로미스배열)
- resolve된 프로미스가 변수에 담겨나옴
- await를 사용하기 때문에 async 함수 안에서 해야함
await
이 then
이잖아요?
then
이란 것을 활용해서 아래와 같이 프로미스 배열을 반복문을 돌릴 수 있습니다.
반복문을 돌리면서 다 then
이 붙어서 나오거든요.
for await of
는 프로미스를 반복할 때 많이 사용하는 문법.
const promise1 = Promise.resolve("성공1");
const promise2 = Promise.resolve("성공2");
(async () => {
for await (promise of [promise1, promise2]) {
console.log(promise);
}
})()
// 성공1
// 성공2
2.8.4 질문
-
질문: 실무에서 prototype에 대한 이해가 중요하나요?
-
저는 사실 잘 안씁니다.
저는 클래스 기반으로 안하고 함수형 프로그래밍을 해서 잘 안쓰지만, 클래스 기반으로 하시는 분들, 자바스크립트를 객체지향으로 많이 사용하시는 분들은 prototype 많이 사용합니다.제가 꼭 함수형 프로그래밍을 하는 것은 아닌데, 함수 위주로 많이 코드를 작성하거든요?
그래서 클래스를 잘 안씁니다.
클래스로 만들 것도 함수로 만들어버리기 때문에.그런데 prototype을 알아야되는 이유가 라이브러리 같은 거 많이 쓰잖아요.
라이브러리나 프레임워크, 그런 남의 코드를 분석할 때, 객체지향형 프로그래밍을 하는 사람들이 조금 더 많거든요?
그런 분들의 코드를 분석할 때는 prototype에 대한 이해가 중요하죠.결론은 함수형 프로그래밍이나 객체지향형 프로그래밍 둘 다 아셔야됩니다.
편식해서 하시면 안되고 다 하실 줄 알아야 원활한 프로그래밍이 가능합니다.
-
2.9 프론트엔드 자바스크립트
2.9.1 AJAX
-
서버로 요청을 보내는 코드
- 라이브러리 없이는 브라우저가 지원하는
XMLHttpRequest
객체 이용 - AJAX 요청시 Axios 라이브러리를 사용하는게 편함
- HTML에 아래 스크립트를 추가하면 사용할 수 있음
초판에선
XMLHttpRequest
이 객체를 이용해 작성했었는데,XMLHttpRequest
이 객체를 사용하면 너무 복잡하고 요즘 사용하시는 분들도 별로 없어서axios
를 넣었습니다.
fetch
를 할까axios
를 할까하다가 그냥axios
를 넣었는데 왜냐면fetch
는 인터넷 익스플로러에서 돌아가는데 문제가 있고 그리고JSON
같은 거 처리할 때 코드를 몇 줄 더 적어줘야하기 때문입니다.
그래서 AJAX를 사용할 때axios
를 활용해 코드를 작성했습니다.<script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script > // 여기에 예제 코드를 넣으세요. </script>
브라우저에선 위와 같이 사용가능하고 노드에선 약간 사용 방법이 다릅니다.
- 라이브러리 없이는 브라우저가 지원하는
-
GET 요청 보내기
- axios.get 함수의 인수로 요청을 보낼 주소를 넣으면 됨
-
프로미스 기반 코드라 async/await 사용 가능
axios.get("https://www.zerocho.com/api/get") .then((result) => { console.log(result); console.log(result.data); // {} }) .catch((error) => { console.error(error); })
비동기는 항상 실패를 염두해둬야한다고 했습니다.
위와 같이 남의 서버로 요청을 보내는 것도 비동기인데 비동기는 항상 실패할 가능성을 염두에 두셔야합니다.
만약에 요청을 보낸 서버가 다운되었다면 에러가 발생할 겁니다.
그러면catch
문이 실행이됩니다.
즉, 이럴 경우를 대비해서Promise
는 항상catch
문을 작성해야합니다.그런데 이거는 아셔야되는게
axios.get()
이 Promise를 지원하는지 안하는지 어떻게 알아요?
그냥 콜백을 지원할 수도 있는거잖아요?
이거 솔직히 모르잖아요?
알 수가 없습니다.
이것은 공식문서 보셔야됩니다.
그리고 외우셔야됩니다.
이게 프로미스를 지원하는지 안하는지 외우셔야됩니다.axios.get()
은 프로미스를 지원하기 때문에then
과catch
를 붙일 수 있습니다.
then
,catch
를 붙일 수 있으니까async/await
로 바꿀 수 있겠죠?
await
오른쪽부터 실행되고await
(=then) 된 다음에 result에 결과값 담기고 아래 코드들이 실행된다는거.(async () => { try { const result = await axios.get("https://www.zerocho.com/api/get"); console.log(result); console.log(result.data); // {} } catch (error) { console.log(error); } })()
-
POST 요청을 하는 코드(데이터를 담아 서버로 보내는 경우)
- 전체적인 구조는 비슷하나 두 번째 인수로 데이터를 넣어보냄
브라우저에서 보통
GET
과POST
요청을 많이 보내는데GET
과POST
의 차이는 4강에서 알려드릴겁니다.
REST API할 때 알려드릴건데,POST
는 일단 서버에 데이터를 같이 보낼 수 있는 요청이라고 보시면 됩니다.
예를 들어, 로그인있죠? 회원가입할 때.
제 아이디랑 비밀번호 같이 서버로 보내서 검증받고 그래야되잖아요?
그럴 때POST
많이 씁니다.
데이터를 같이 보낼 수 있어서.(async () => { try { const result = await axios.post("https://www.zerocho.com/api/post/json", { name: "zerocho", birth: 1994, }) console.log(result); console.log(result.data); // {} } catch (error) { console.error(error); } })()
2.9.2 FormData
-
HTML form 태그에 담긴 데이터를 AJAX 요청으로 보내고 싶은 경우
- FormData 객체 이용
-
FormData 메서드
- Append로 데이터를 하나씩 추가
- Has로 데이터 존재 여부 확인
- Get으로 데이터 조회
- getAll로 데이터 모두 조회
- delete로 데이터 삭제
-
set으로 데이터 수정
const formData = new FormData(); formData.append("name", "zerocho"); formData.append("item", "orange"); formData.append("item", "melon"); formData.has("item"); // true formData.has("money"); // false formData.get("item"); // orange formData.getAll("item"); // ["orange", "melon"] formData.append("test", ["hi", "zero"]); formData.get("test"); // hi, zero formData.delete("test"); formData.get("test"); // null formData.set("item", "apple"); formData.getAll("item"); // ["apple"]
FormData
객체는 보통 이미지 업로드, 파일 업로드, 동영상 업로드할 때 많이 사용합니다.
HTML에form
이란 태그가 있는데form
태그도 이미지를 전송할 수 있는데,
저희는 서버로 전송을 할 때axios
를 쓴다고 했죠?
axios
는 이미지나 파일, 동영상을 전송할 때,FormData
에다가 데이터를 넣어서 보내야합니다.
뒤에axios
를 통해서 이미지를 업로드하는 예제가 나옵니다.이에 대해선 6장 또는 9장에서 직접 작성을 해보도록 하겠습니다.
-
FormData POST 요청으로 보내기
-
Axios의 data 자리에 formData를 넣어서 보내면됨
아래 예시 코드의 "hyungju-lee" 부분에 이미지를 넣어서 보낼 수 있습니다. 이미지를 넣어서 .post()할 때 두번째 인자로 보내면 됩니다.
(async () => { try { const formData = new FormData(); formData.append("name", "hyungju-lee"); formData.append("birth", 1988); const result = await axios.post("https://www.zerocho.com/api/post/formdata", formData); console.log(result); console.log(result.data); } catch (error) { console.error(error); } })()
-
2.9.3 encodeURIComponent, decodeURIComponent
-
가끔 주소창에 한글 입력하면 서버가 처리하지 못하는 경우 발생
-
encodeURIComponent로 한글 감싸줘서 처리
(async () => { try { const result = await axios.get(`https://www.zerocho.com/api/search/${encodeURIComponent('노드')}`); console.log(result); console.log(result.data); // {} } catch (error) { console.error(error); } })()
-
주소창에는
ASCII 코드
라는 것이 있거든요?
ASCII 코드
만 입력하는 것이 제일 안전합니다.
ASCII 코드
를 보시면 아시겠지만 한글이 없습니다.
그래서 가끔가다가 한글을 올리면 서버가 고장나는 경우가 있습니다.
또는 에러를 띄우거나 아예 무시를 해버리거나 그런 경우가 있습니다.
노드도 가끔 이런 경우가 발생하거든요?영어권 나라가 아니라면 "인코딩" 문제가 가끔 일어납니다.
그래서 주소에다 한글을 넣을 때는 위 코드처럼encodeURIComponent
라는 함수로 감싸서 넣어주시는 것이 좋습니다.Tipurl은 서버에 있는 파일 위치를 가리키고 uri는 서버에 있는 자원의 위치를 가리킵니다.
파일 위주에서 자원 위주로 넘어갔기 때문에 요즘에는 url이 아니라 uri를 많이 씁니다.
url의 l = locater uri의 i = identifier
-
-
"노드"를
encodeURIComponent
하면%EB%85%B8%EB%93%9C
가 됨
%EB%85%B8%EB%93%9C
이대로 서버로 전송
그럼 서버에서 해독을 해야겠죠?-
decodeURIComponent로 서버에서 한글 해석
decodeURIComponent("%EB%85%B8%EB%93%9C"); // 노드
전송할땐
encodeURIComponent
, 서버에서 받아서 해석할 땐decodeURIComponent
-
2.9.4 data attribute와 dataset
-
HTML 태그에 데이터를 저장하는 방법
- 서버의 데이터를 프론트엔드로 내려줄 때 사용
- 태그 속성으로
data-속성명
-
자바스크립트에서
태그.dataset.속성명
으로 접근 가능- data-user-job -> dataset.userJob
- data-id -> dataset.id
-
반대로 자바스크립트 dataset에 값을 넣으면 data-속성이 생김
-
dataset.monthSalary = 10000 -> data-month-salary="10000"
<ul> <li data-id="1" data-user-job="programmer">Zero</li> <li data-id="2" data-user-job="designer">Nero</li> <li data-id="3" data-user-job="programmer">Hero</li> <li data-id="4" data-user-job="ceo">Kero</li> </ul> <script> console.log(document.querySelector("li").dataset); // { id: "1", userJob: "programmer" } </script>
-
단점 : 공개된 데이터만 저장할 수 있습니다.
왜냐면 누구나data-
속성을 꺼내갈 수 있기 때문에.
위와 같이 HTML 태그에data-pwd
속성으로 비밀번호를 저장하면 해커들이 신나서 비밀번호를 가지고가겠죠?
때문에 공개해도 되는 데이터를 저장할 때 사용.
-
사실 data 속성을 실무에서 자주 쓰는지는 잘 모르겠습니다.
저도 이 책에서 react
같은걸 못 쓰다보니까(강의 주제가 아니기 때문에) 어쩔 수 없이 썼거든요?
그래서 실무에선 data attribute를 잘 쓰는지는 모르겠습니다.
그런데 알아두면 유용할 때가 있겠죠?