Node.js & Express에서 JWT(JSON Web Token) 사용하기 -2-

전 포스트에서 JWT에 관한 설명을 조금 했는데 이번엔 내가 어떻게 썻는지에 대해서 중점적으로 남겨보려한다.(나중에 까먹을까봐)

이 방법이 Best Practice인지는 잘 모르겠지만…

혹시 이것보다 더 좋은 방법이 있다면 댓글로 알려주세요!

프로젝트 생성

Express 프로젝트를 생성하고 npm install을 해주자.

1
2
$ express --ejs express-jwt
$ cd express-jwt && npm install

일단 껍데기만 있는 Auth API를 만들어 두자.

app.js에 다음과 같이 추가하자.

1
2
3
4
5
// app.js

const auth = require('./routes/auth');

app.use('/auth', auth);

routes/auth.js 파일을 생성하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// routes/auth.js

const express = require('express');
const router = express.Router();

/* Sign Up API
* - parameter email
* - parameter password
* - parameter username
*/
router.post('/signup', (req, res, next) => {
res.send('ok');
});

/* Sign In API
* - parameter email
* - parameter password
*/
router.post('/signin', (req, res, next) => {
res.send('ok');
});

module.exports = router;

JWT 미들 웨어

그리고 jsonwebtoken이라는 모듈을 설치하자

1
$ npm i --save jsonwebtoken

그리고 Express에서 미들웨어처럼 사용하기 위해 utils/tokenHelper.js라는 파일을 만들었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// utils/tokenHelper.js

const jwt = require('jsonwebtoken');

const TOKEN_SECRET = 'secret';

const tokenGenerator = (data, callback) => {
const token = jwt.sign(data, TOKEN_SECRET, {
algorithm: 'HS256',
expiresIn: 60 * 60 * 24 * 7
})
callback(token)
}

const isValid = (token, callback) => {
jwt.verify(token, TOKEN_SECRET, (err, decode) => {
if (err) {
// console.log("=========Token Helper: Can't decode token")
callback({isValid: false})
} else {
const exp = new Date(decode.exp * 1000)
const now = Date.now()
const day = (60 * 60 * 24 * 1000)
if (exp < now) {
// console.log("=========Token Helper: Expired Token")
callback({isValid: false})
} else if (exp < now + (5 * day)) {
// console.log("=========Token Helper: Generate New Token")
const newToken = module.exports.generateToken(decode.user.id)
callback({isValid: true, token: newToken, userInfo:decode})
} else {
// console.log("=========Token Helper: Token is valid")
callback({isValid: true, token: token, userInfo:decode})

}
}
})
}

const tokenHandler = (req, res, next) => {
const { token } = req.query

if(token) {
module.exports.isValid(token, (result) => {
req.userInfo = result;
next()
})
} else {
req.userInfo = {isValid: false}
next()
}
}

export default {
tokenGenerator,
isValid,
tokenHandler
}

Node.js & Express에서 JWT(JSON Web Token) 사용하기 -1-

두달만을 개발할 때 학습한 것들을 정리해서 올리기로 결심했다.

지금 생각나는 후보로는 다음과 같다.

  • JWT
  • Sequelize
  • Socket.io

더 있지만 일단 이 3가지의 기본 셋팅정도는 포스팅으로 올려놓으면 나중에 다시 보기에 좋을듯 싶다.

가장 처음으로 JWT를 다루기로 했다.

프로젝트 초기에는 Redis를 사용한 세션기반의 인증 방식을 사용하고 있었다.

하지만 뭔가 세련된 방법이 없을까 고민다하가 튜터님의 조언으로 JWT라는 것을 사용해 보기로 했다.

JSON Web Token

들어가기 전에 JSON Web Token의 개념을 알아두자.

JWT??

JSON Web Token은 공개된 업계 표준인 RFC 7519 방식으로 양 측의 클레임(Claims)을 안전하게 한다.
JWT.IO를 사용하여 JWT를 디코드, 검증 및 생성할 수 있다.

Claim이라는 용어가 처음에 낯설었지만 서버와 클라이언트가 주고받는 정보나 메세지 정도로 생각하면 된다.

JSON 객체로된 클레임은 디지털로 서명되었기 때문에 검증되고 신뢰할 수 있다고 한다.

또한 다음과 같은 개념을 가진다.

  • Compact: JWT는 크기가 작기 때문에 URL, POST 파라미터 또는 HTTP Header에 포함될 수 있다. 크기가 작아 전송 속도 또한 빠르다고 할 수 있다.

  • Self-contained: JWT는 Payload에 필요한 데이터를 포함한다. 따라서 토큰을 생성할 때 데이터베이스에서 정보를 가져오면 그 다음부터는 데이터베이스에 질의를 하지 않아도 된다.

언제 JWT를 사용할까?

  • Authentication: JWT를 사용하는 가장 일반적인 시나리오다. 유저가 처음 로그인을 하면, 유저 정보를 포함한 JWT를 생성해 발급해준다. 그 후 유저는 각 요청에 발급받은 토큰을 포함해 보내는데, 서버에서 토큰을 기반으로 유저의 정보, 허용되는 경로 등의 정보를 데이터베이스의 접근없이 알 수 있다. 또한 세션을 유지하지 않아도 되므로 서버의 비용이 줄어들 수 있다.

  • Information Exchange: JWT는 공개 키나 비밀 키로 서명할 수 있기 때문에 안전하게 정보를 교환할 수 있다. 게다가 서명이 헤더와 페이로드를 사용하여 계산되므로 내용이 변경되었는지도 판별할 수 있다.

JWT의 구조

JWT는 점(.)으로 구분되는 세가지 요소를 가진다.

  • Header
  • Payload
  • Signature

그래서 결과적으로 xxxxx.yyyyy.zzzzz 형태를 가지게 된다.

JWT의 첫 번째 부분은 Header 로, 헤더는 2가지 속성 값을 가진다.

  • alg: 해싱 알고리즘. 보통 HMAC SHA256 혹은 RSA가 사용된다.
  • typ: 토큰의 타입 = JWT

다음과 같이 Header를 생성할 수 있다.

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

Payload

두 번째 부분은 처음에 클레임을 포함하는 Payload가 들어가게 된다.

클레임은 3가지로 구분할 수 있는데, 다음과 같다.

  • Reserved claims: 이것은 필수는 아니지만 JWT를 사용함에 있어 권장되는 미리 정의된 클레임 세트이다. iss(발행자), exp(만료 시간), sub(주제), aud(고객) 등을 포함한다.

  • Public claims: 이것은 JWT사용자끼리 충돌을 방지하기 위한 값을 포함한다. 보통 충돌을 방지하기 위한 네임스페이스를 사용한다.

  • Private claims: 정보를 담는 클레임.

다음과 같이 Payload를 생성할 수 있다.

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

Signature

서명을 만들기 위해서는 인코딩된 헤더, 인코딩된 페이로드, 시크릿 키, 헤더에 지정된 알고리즘을 가진다.

예를 들면 다음과 같이 서명을 만들 수 있다.

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

이렇게 만든 서명으로 메세지가 변경되지 않았음(무결성)을 확인하는데 사용할 수 있다.

확인해보자

JWT Debugger에서 JWT를 디코딩해보거나, 검증하거나 생성해볼 수 있다.

JWT를 어떻게 쓰는…?

클라이언트는 서버에 인증 요청한다.

인증이 성공된다면 서버에서 사용할 클레임을 포함한 JWT를 생성해 클라이언트에게 넘겨준다.
일반적으로 로컬 저장소에 저장되지만 쿠키를 사용할 수도 있다.

그리고 클라이언트에서 인증이 필요한 어떠한 요청을 할 경우 요청 헤더에 다음과 같이 추가해준다.(Bearer는 토큰 타입을 결정하는 자리인데 OAuth2의 토큰 인증 방식이다.)

1
Authorization: Bearer <token>

서버는 요청 헤더에서 토큰 값을 디코딩해 필요한 정보를 가져올 수 있으며 그에 따른 요청 응답을 클라이언트에게 보내준다.

그래서 JWT를 사용하면

서버에서 토큰이 유효하다고 판단하면 클레임들을 디코딩해 정보를 가져올 수 있다.
서버의 메모리와 같이 다른 곳에 따로 관리를 하지 않아도 된다는 뜻이다.

따라서 Stateless한 서버를 만들 수 있게 된다.
그러므로 서버의 확장이 용이하고 요청 도메인에 관해 문제가 되지 않아 CORS문제를 생각하지 않아도 된다.

하지만 클레임셋이 증가하면 자연스레 토큰의 길이가 증가하게 되는데 요청 헤더에 토큰을 삽입하는 방식이므로 과한 오버헤드가 발생할 수 있다.

다음 포스팅

원래 프로젝트에서 사용한 예제도 포함하려 했는데

생각보다 JWT에 대한 설명이 길어져 다음 포스팅으로 넘기려한다.

간단한 예제긴 한데 나름 고민해서(?) 만들었고 나중에도 써먹기 위해 기록해둘 생각이다.

Refer

부스트캠프(Boostcamp) 2차 후기

부스트캠프 2차가 끝났다.

약 3주간 프로젝트 기간으로 진행되었는데 진짜 시간이 어마어마하게 빨리 갔다.

어떤 프로젝트를 할지 고민고민 끝에 두달만이라는 자취방 공유 어플리케이션을 만들어보기로 했다.

부스트캠프 2차 == 프로젝트 이기 때문에 이 글은 거의 두달만 개발 후기라고 할 수 있다.

기획

사실 고백하자면 이 두달만이라는 서비스는 작년 가을학기에 학업만 하기에 지루했던 학교생활을 탈피하기 위해(?) 기획한 서비스이다.

같은 랩실의 친구(나에게 부스트캠프를 추천해준 친구)에게 학교생활이 너무 지루하다며 징징댔었는데,
그런 나에게 그럼 우리 뭐 만들어보자라고 이런 아이디어를 생각했다.

안쓰는 자취방의 기간동안 다른사람이 살면서 월세를 줄일 수 있다면 얼마나 좋을까?

물론 에어비앤비가 그것을 잘 해결해 줄 수 있지만 우리나라에서는 불법이란다.

그러면 이 서비스 또한 불법이 아닐까 고민이 많이 되었지만 그때 결론은 일단 만들어보자 였다.

하지만 중간고사가 겹치고 논문에 여러가지 일이 겹치다보니 실 개발은 거의 못하고 방치되어 있었다.

그래서 이번에 부스트캠프에서 다시 한번 해보면 어떨까싶어 이 두달만이라는 서비스를 만들기로 결정했다.

프로젝트 기간은 3주로 산정이 되어있었지만 거의 첫주는 기획과 디자인 그리고 일정을 잡는데에 쓴것 같다.

UI/UX를 많이 신경쓰긴했는데 기존의 부동산 앱(직방, 다방)을 많이 사용해본 상태여서 거의 흡사한 형태의 앱이 디자인 되었다.

앱의 프로토타입을 보려면 이 링크로 가보면 된다.

개발

기획에 1주일을 날리니 개발을 할 수 있는 기간은 2주정도가 남았다.

처음에 이게 가능한 스케줄인가… 내가 너무 일을 벌린것 아닌가 싶었는데 막상 하고나니 되긴 됐다.(건강은 나빠진것 같다.)

대충 개발 스택들을 나열하자면 다음과 같다.

Swift3

이번에 부스트캠프를 통해 처음 접해본 Swift
아직 익숙하지는 않지만, 나름 두달동안 열심히 한 덕분에 프로젝트가 거의 끝나갈 시점에는 생산성이 처음보다 많이 나아져있었다.

  • MapKit
    • 여러 지도 서비스를 사용해보기보다 빠르게 사용할 수 있는 지도를 택했다. API문서화가 잘 되어있고 간편하게 사용할 수 있었다.

그 외에 여러 CocoaPod을 사용했다.

  • Alamofire, AlamofireObjectMapper
    • 서버와 네트워킹을 할때 사용했다.
    • NSURLSession을 사용하는 것보다 코드량도 줄일 수 있고, 더욱 직관적인 코딩을 할 수 있게 해준다.
  • Nuke
    • 이미지 로더로 방 사진을 로딩할 때 사용했다.
  • GooglePlaces
    • 지도상에서 지역을 검색해서 이동하거나, 방을 등록할때 주소를 검색할 수 있게 사용했다.
    • 등록시 주소검색은 다음 주소 검색을 사용하는 것으로 바꿀 예정.
  • FontAwesome.swift
    • 폰트어썸!
  • SwiftRangeSlider
    • 필터에서 가격을 필터링하기 위해 범위 슬라이더로 사용했다.
  • Socket.IO-Client-Swift
    • 채팅을 위해 Socket.IO 클라이언트로 사용했다.

Node.js + Express + Socket.IO

사용해본 경험이 있는 언어와 프레임워크로 백엔드의 부담을 최소화했다.
안그래도 시간이 부족한데 백엔드에서 시간을 빼았기기 싫었기 때문이다.

Auth를 위해 JWT를 사용했고 처음 로그인이나 회원가입이후에 모든 요청에는 토큰을 포함해 인증을 했다.

Node.js에서 ORM을 사용하기 위해 Sequelizer를 사용했는데 생각보다 학습하는데 시간이 필요했다.
어찌어찌 구현은 했는데 나중에 리팩터링을 해야겠다.

Azure

별다른 이유없이 크레딧이 남아서 썻다.

MariaDB

처음에는 클라이언트에도 Realm을 써보고 백엔드에도 Realm을 발라보자! 했지만 Geo 거리 계산같은 쿼리는 지원하지 않는듯 해서 RDBMS를 사용하게 되었다.

Nginx

사진처럼 정적인 파일은 어플리케이션 서버에서 처리하기보단 그 앞에 웹 서버를 둬서 처리하면 어플리케이션 서버의 효율을 높일 수 있다.

데모데이

그렇게 순식간에 개발기간이 끝났다.

데모데이는 토요일에 있었는데 그 전날엔 거의 밤을 새서 앱을 마무리하고 소개페이지와 데모영상을 만들수밖에 없었다.(평소에 좀 더 할껄…)

데모영상을 나름 신경써서 만들긴했는데 데모데이날 다른사람들의 영상을 보니 내 영상은 정말 초라했다.

다른사람들이 한 프로젝트를 그날 처음봤는데 다들 재미있는 프로젝트들을 하고 있었고 완성도도 어마어마했다.(부스트캠프 iOS반 짱짱)
역시 세상은 넓고 고수는 많다

네트워킹데이

드디어 부스트캠프의 마지막행사 네트워킹데이가 다가왔다.


드디어 끝이 보인다

두달동안 다들 고생해서 그런가 피곤해보이는 사람들이 많았다.(물론 나포함)

행사는 다음과 같이 진행되었다.

1부

  • 오프닝 및 기업소개
  • Android 발표와 부스 세션
  • Break
  • iOS 발표와 부스 세션
  • 마무리

2부

  • 안내 및 다과
  • 수료식

이름만 들어도 알만한 기업들과 조금은 생소하지만 재밌는 것을 하고 있는 기업들도 많이 참여해서 부스트캠프에서 인재를 발굴해가고 싶다고 했다.(부디)

그리고 Android 발표와 부스 세션시간이 지나고 iOS 발표와 부스 세션시간이 되었다.

저번 데모데이때 좋은 평가를 받은 6개의 프로젝트의 발표가 이루어졌다.(내 플젝은 안타깝지만 ㅠㅠ)

그리고 각 프로젝트별로 부스를 설치해 자기 앱을 발표하는 시간이었는데, 내 부스에는 아무도 오지 않으면 어떡하나 싶었지만 매우 감사하게도 몇몇분이 방문해 앱 설명을 듣고 응원도 해주시고 궁금한 것들을 물어보셨다.
또 다녀가신 분들 중 한분께서 창업엔 관심이 없냐며 자기 회사의 서비스와 어떻게 협력하면 좋을 것 같다고 까지 말씀해주셔서 되게 감사하고 기억에 남았다.

사실 데모데이가 끝나고 앱을 더 수정할 것이 남아있었지만 조금 의욕이 한풀 꺾여있었는데, 다시금 동기부여를 해주신것 같다.(런칭까지 빠샤!)

그렇게 iOS 발표까지 끝나고 1부가 끝났다.

2부가 시작하기 전에 저녁으로 피자를 줬다.(🍺까지 있었다면 참 좋았을텐데)

저녁을 해결하고 원티드의 황리건 님의 특강이 있었다.
개발자 커리어의 시작점에서 라는 주제로 여러가지 좋은 말씀을 해주셨는데 그 중 시장가치가 중요하다는 말이 제일 기억에 남는다.

지금은 핫한 기술일지라도 언제 사라질지 모른다며 플래쉬를 예로 들어 아주 기억에 남게 설명해주셨다.

그리고 드디어 마지막 수료식이 남았다.

수료증을 받고 사진을 찍으니 이제 진짜 끝이구나 싶었다.

수료식 중간에 갑자기 베스트 부스터였나? 열심히 한 사람 시상식이 있었는데 영문을 알수없게도 나도 수상하게 되었다.(감사합니다)

그렇게 수료식도 끝나고 팀별로 사진도 찍고나니 행사가 끝났다.


라떼 팀 수고하셨슴다~

진짜 후기

진짜 두달이란 시간동안 말그대로 부스트했던 것 같다.

앱 개발은 1도 모르던 나였지만 제법 그럴듯해보이는 앱을 하나 제작했다.

같이 공부하고 매일매일 회의하면서 서로 피드백도 주면서 도와줬던 팀원들, 엄청 질문을 많이 해도 하나하나 다 알려주셨던 이재훈 튜터님, 주말마다 특강을 준비해주셨던 다른 튜터님들 그리고 잘 진행되도록 프로그램을 짜준 부스트캠프 운영진들 덕분인 것 같다.

이제 시작인 2017년 시작이 좋은 것 같다.

두달만은 일단 앱스토어 등록 시도는 해봐야겠다.

부스트캠프(Boostcamp) 1차 후기

Connect 재단에서 운영하는 부스트캠프라는 것을 하게 됐다.

학기가 거의 끝나갈즈음, 취업에 실패한 나는 백수가 될 예정이었다.
그러던 중 동병상련의 처지인 친구가 부스트캠프를 소개를 해줬다.

부스트캠프에서 운영하는 모바일 앱 개발 교육 프로그램이었다.
맨날 웹 개발만 하다보니 앱 개발에도 자연스레 흥미가 생겼었지만 Java나 Objective-C는 하질 못해서 시도조차 못해봤었다.

그래서 이걸 하면 조금이나마 배워서 어디에 써먹을 수 있겠다~ 라는 마음에 친구와 같이 신청해버렸다.(놀아서 뭐하나…)

시험

신청만 하면 끝인줄 알았지만 인터넷으로 코딩 테스트까지 진행했다.

하필 코딩테스트 날이 가족들과 오키나와에 여행가 있는 기간이랑 겹쳐서 가족들이 국제거리를 구경하는 동안 혼자 스타벅스에 가서 시험을 봐야만 했다.

하지만 와이파이 상태가 좋지 못해 테스트를 하다보면 한번씩 연결이 끊어지고 다시 접속해야 했다.

게다가 프로그래밍 언어는 내 영역 밖인 C, C++, Java로 제한해둬서 더욱 난감했다.

여차저차 그나마 조금 할 줄 아는 Java로 4문제 중 3문제를 풀어서 제출했다.

문제들이 다 쉬워서 한문제를 못풀었기 때문에 떨어졌다 생각하고 있었는데 합격했다는 메일이 왔다.

부스트캠프

그리하여 합격자를 대상으로 한 오리엔테이션을 시작으로 부스트캠프가 시작됐다.

총 4주간 유다시티 강의를 베이스로 진행이 된다.(사실 현장에서 직접 알려주는 줄 알았는데 좀 충격이었다.)

Intro to iOS App Development with Swift, UIKit Fundamental 이 2개의 강의를 각각 2주간 듣고 강의 끝에 프로젝트를 제출하는 방식이었다.

그리고 토요일마다 튜터와 함께 조별 모임을 가져 그 주에 한것들을 나누고, 튜터분이 준비한 특강을 들으며 스터디를 또 가졌다.

4주동안 시간이 정말 빨리 지나갔다.

우리 조같은 경우는 유다시티 강의뿐 아니라 스위프트 책을 하나 선정하여 스위프트 스터디도 따로 진행했는데 일주일을 정말 공부만 하면서 보내야했다.

그리고 마지막 주는 프로젝트를 제출해야했는데 그 주에 설이 껴있어서 더 시간이 부족했다.
결국 설날에도 컴퓨터를 붙잡고 프로젝트를 완성해야 했다.

그리고

그렇게 4주가 끝나고 부스트 캠프 1차가 종료되었다.
2차로 가기위해선 또 면접을 보고 그 중 일부만 참여할 수 있었다.

부스트캠프 2차에서는 각자 만들어 보고 싶은 앱을 기획해 직접 개발하는 프로젝트 기간이다.
아이디어 팩토리라는 코워킹 스페이스에 등록을 해줘서 거기서 개발을 할 수 있게 했는데 생각보다 시설이 괜찮았다.(자리가 널럴하지 못한점만 빼면…)

2차가 시작한지 몇일 안됐는데 또 시간이 어마어마하게 빨리 갈것 같다.

그리고(2)

사실 부스트캠프 1차에서 유다시티 강의가 거의 위주였다.
그것도 각자 알아서 듣고 알아서 공부해오고 알아서 프로젝트를 하는 식이었는데 그런 프로세스가 과연 효과적일까 의문이 들긴했다.
그래도 시키니까 하긴했지만…

조금 더 부스트캠프만의 컨텐츠(?) 그런것들을 만들어서 배풀었다면 어땠을까 싶다.
물론 주말마다 튜터분들의 특강은 되게 알찼다.
하지만 그것만으로는 뭔가 채워지지 않는 그런 느낌적인 느낌…

그래도 정말 공부는 많이 하게됐다.
부스트캠프가 아니었다면 어마어마하게 방탕한 삶을 살았을 것 같은데, 다행히 그것을 방지할 수 있었다.
어마어마한 양의 진도와 조 모임때 조원들에게 피해를 주지 않으려고 공부를 많이 했다.

사실 웹도 아직 쪼렙인데 괜히 영역만 넓히고 얕은 수준에 머무르는 것 아닌가 싶기도 한데
한편으로는 아직 얕은 수준이기 때문에 이것도 해보고 저것도 해봐서 나에게 맞는 무언가를 찾는 것도 유의미한 일이라 생각한다.(하지만 못찾는다면 어쩌지)

무튼 앞으로 부스트캠프 2차는 3주도 안남았지만 그 기간동안 열심히 공부하고 열심히 개발해서 무사히 마쳤으면 한다.

[번역] Overview of JavaScript ES6 features (a.k.a ECMAScript 6 and ES2015+)

이 글은 Adrian MejiaOverview of JavaScript ES6 features (a.k.a ECMAScript 6 and ES2015+)을 번역한 글입니다. 오타나 오역 제보는 언제나 환영입니다!


자바스크립트는 지난 몇 년간 꽤 많은 변화가 있었습니다. 지금 당장 쓸 수 있는 새로운 12가지 기능은 다음과 같습니다.

1. JavaScript History

이번에 자바스크립트에 추가된 기능을 ECMAScript 6 이라고 부릅니다. ES6 나 ES2015+ 라고도 불리죠.

1995년 자바스크립트 개념이 도입된 이래로, 자바스크립트는 서서히 진화해 오고 있었습니다. 기능을 추가하는 작업은 몇 년에 한번씩 일어나곤 했습니다. ECMAScript는 자바스크립트의 이러한 방향을 바로 잡기 위해 1997년에 등장했습니다. ECMAScript는 ES3, ES5, ES6 등과 같은 버젼을 발표해 왔습니다.

보시다시피, ES3, ES5 와 ES6은 각각 10년과 6년의 간격이 있습니다. 새 모델은 매년 작고 점진적인 변화를 만드는 것입니다. ES6 처럼 한 번에 엄청난 변화를 하는것 대신에요.

2. Browsers Support

모든 현대 브라우저와 환경은 ES6를 이미 지원하고 있습니다!


source: https://kangax.github.io/compat-table/es6/

Chrome, MS Edge, Firefox, Safari, Node와 많은 다른 환경에서 이미 자바스크립트 ES6의 기본적인 기능을 지원합니다. 그래서 당신이 이제 배울 모든 튜토리얼은 당장 사용해 볼 수 있습니다.

ECMAScript 6를 시작해봅시다!

3. Core ES6 Features

브라우저 콘솔에서 모든 코드들을 테스트 할 수 있습니다.

그러니 글만 읽지 말고 ES5와 ES6 예제를 테스트 해보길 바랍니다. 어서 해보죠 💪

3.1 Block scope variables

ES6에서 변수를 선언하기 위해 var 대신 let/const를 사용합니다.

var에 무슨 문제가 있었을까요?

var의 문제는 for문이나 if문과 같은 코드 블록에서 변수가 누출되는 것입니다.

ES5

1
2
3
4
5
6
7
8
9
10
var x = 'outer';
function test(inner) {
if (inner) {
var x = 'inner'; // scope whole function
return x;
}
return x; // gets redefined because line 4 declaration is hoisted
}
test(false); // undefined 😱
test(true); // inner

test(false)에서 outer가 반환되길 기대했지만, undefined를 얻었습니다.

왜 일까요?

왜냐하면 if 블록이 실행되지 않더라도 네째 줄의 var x가 호이스트(hoisted)되기 때문입니다.

var 호이스팅:

  • var는 함수 범위(scope)입니다. 이것은 선언되기 전에도 전체 함수에서 사용할 수 있습니다.
  • 선언(Declarations)은 호이스트(Hoisted)됩니다. 그래서 선언되기 전에도 변수를 사용할 수 있습니다.
  • 초기화(Initializations)는 호이스트되지 않습니다. 만약 var를 사용한다면 항상 맨 위에 변수를 선언하세요.
  • 호이스팅 규칙을 적용해 본 후에 무슨 일이 일어났는지 더 잘 이해할 수 있습니다.
    ES5
1
2
3
4
5
6
7
8
9
10
> var x = 'outer';
> function test(inner) {
> var x; // HOISTED DECLARATION
> if (inner) {
> x = 'inner'; // INITIALIZATION NOT HOISTED
> return x;
> }
> return x;
> }
>

ECMAScript 2015가 도와주러 왔습니다:

ES6

1
2
3
4
5
6
7
8
9
10
let x = 'outer';
function test(inner) {
if (inner) {
let x = 'inner';
return x;
}
return x; // gets result from line 1 as expected
}
test(false); // outer
test(true); // inner

varlet으로 변경하면 예상대로 작동합니다. if 블록이 호출되지 않는다면, 변수 x는 블록 밖으로 호이스트되지 않습니다.

호이스팅(hoisting) 과 “temporal dead zone”

  • ES6에서 let은 블록의 맨 위로 호이스트 됩니다.(ES5 처럼 함수의 맨위가 아닌)
  • 그러나, 변수가 선언되기 전에 블록에서 변수를 참조하는 것은 ReferenceError를 초래합니다.
  • let은 차단된 스코프를 가집니다. 선언되기 전에 사용할 수 없습니다.
  • “Temporal dead zone”은 블락의 처음부터 변수가 선언되기 전까지의 영역입니다.

IIFE

IIFE를 설명하기 전에 예제를 봅시다. 여길 봐주세요:

ES5

1
2
3
4
{
var private = 1;
}
console.log(private); // 1

보시다시피, private은 유출됩니다. 그것을 감싸기 위해 IIFE (immediately-invoked function expression)을 사용할 필요가 있습니다:

ES5

1
2
3
4
(function(){
var private2 = 1;
})();
console.log(private2); // Uncaught ReferenceError

jQuery/lodash 나 다른 오픈소스 프로젝트를 살펴보면 그것들은 전역 환경의 오염을 피하기위해 IIFE를 사용하고, 단지 _, $jQuery만 전역으로 정의합니다.

ES6에서 더 깔끔합니다. 블록과 let을 사용하면 더이상 IIFE를 사용할 필요가 없습니다.

ES6

1
2
3
4
{
let private3 = 1;
}
console.log(private3); // Uncaught ReferenceError

Const

변수를 전혀 변경하지 않는다면 const를 사용할 수 있습니다.

밑줄: var를 버리고 letconst로.

  • 모든 참조에 var의 사용을 피하고 const를 사용하세요.
  • 만약 참조를 재 정의해야 한다면 const 대신 let을 사용하세요

3.2 Template Literals

템플릿 리터럴을 사용하면 더이상 중첩된 연결을 하지 않아도 됩니다. 여길 보세요:

ES5

1
2
3
var first = 'Adrian';
var last = 'Mejia';
console.log('Your name is ' + first + ' ' + last + '.');

이제 우린 역 따옴표( ` )와 문자열 보간 ${}을 사용할 수 있습니다:

ES6

1
2
3
const first = 'Adrian';
const last = 'Mejia';
console.log(`Your name is ${first} ${last}.`);

3.3 Multi-line strings

아래처럼 더이상 문자열과 \n을 연결하지 않아도 됩니다.

ES5

1
2
3
4
5
6
7
8
9
var template = '<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >\n' +
' <div class="view">\n' +
' <input class="toggle" type="checkbox" [checked]="todo.isDone">\n' +
' <label></label>\n' +
' <button class="destroy"></button>\n' +
' </div>\n' +
' <input class="edit" value="">\n' +
'</li>';
console.log(template);

ES6에서 역 따옴표를 사용하여 이를 해결할 수 있습니다.

ES6

1
2
3
4
5
6
7
8
9
const template = `<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >
<div class="view">
<input class="toggle" type="checkbox" [checked]="todo.isDone">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit" value="">
</li>`;
console.log(template);

두 코드는 정확히 똑같은 결과를 가지게 됩니다.

3.4 Destructuring Assignment

ES6 비구조화(Destructuring)은 매우 유용하고 중요합니다. 아래 예제를 보세요:

배열에서 요소 가져오기

ES5

1
2
3
4
var array = [1, 2, 3, 4];
var first = array[0];
var third = array[2];
console.log(first, third); // 1 3

다음과 같습니다:

ES6

1
2
3
const array = [1, 2, 3, 4];
const [first, ,third] = array;
console.log(first, third); // 1 3

값 치환

ES5

1
2
3
4
5
6
var a = 1;
var b = 2;
var tmp = a;
a = b;
b = tmp;
console.log(a, b); // 2 1

다음과 같습니다:

ES6

1
2
3
4
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

여러 반환 값에 대한 비구조화

ES5

1
2
3
4
5
6
7
8
function margin() {
var left=1, right=2, top=3, bottom=4;
return { left: left, right: right, top: top, bottom: bottom };
}
var data = margin();
var left = data.left;
var bottom = data.bottom;
console.log(left, bottom); // 1 4

세째 줄에서, 다음과 같은 배열로 리턴할 수도 있습니다.

1
return [left, right, top, bottom];

하지만 호출자(caller)는 반환된 데이터의 순서에 대해 생각해볼 필요가 있습니다.

1
2
var left = data[0];
var bottom = data[3];

ES6에서, 호출자는 필요한 데이터만 설택할 수 있습니다. (여섯째 줄):

ES6

1
2
3
4
5
6
function margin() {
const left=1, right=2, top=3, bottom=4;
return { left, right, top, bottom };
}
const { left, bottom } = margin();
console.log(left, bottom); // 1 4

Notice: 셋째 줄에서, 우리는 ES6의 새로운 기능을 사용하고 있습니다. { left: left }{ left }로 줄일 수 있습니다. ES5 버젼과 비교하면 얼마나 간결한지 보이시죠. 멋지지 않나요?

일치하는 매개변수에 대한 비구조화

ES5

1
2
3
4
5
6
7
var user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName(user) {
var firstName = user.firstName;
var lastName = user.lastName;
return firstName + ' ' + lastName;
}
console.log(getFullName(user)); // Adrian Mejia

다음과 같습니다.(하지만 더 간결한)

ES6

1
2
3
4
5
const user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
console.log(getFullName(user)); // Adrian Mejia

Deep Matching

ES5

1
2
3
4
5
6
7
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
var tmp = settings();
var displayColor = tmp.display.color;
var keyboardLayout = tmp.keyboard.layout;
console.log(displayColor, keyboardLayout); // red querty

다음과 같습니다.(하지만 더 간결한)

ES6

1
2
3
4
5
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();
console.log(displayColor, keyboardLayout); // red querty

이것은 객체 비구조화라고도 불립니다.

보시다시피, 비구조화는 매우 유용하고 좋은 코딩 스타일을 가지게 합니다.

모범 사례:

  • 배열 비구조화를 사용하여 요소를 가져오거나 값을 바꾸길 바랍니다. 임시 참조를 만들지 않아도 되게 도와줍니다.
  • 여러 값을 반환할 때 배열 비구조화를 사용하는 대신 객체 비구조화를 사용하세요.

3.5 Classes and Objects

ECMAScript 6에서, “constructor functions” 🔨 대신 “classes” 🍸 를 사용합니다.

자바스크립트에서 모든 단일 객체는 또 다른 객체인 프로토타입을 가지고 있습니다. 모든 자바스크립트 객체는 그들의 프로토타입의 메서드와 프로퍼티를 상속받습니다.

ES5에서, 우리는 다음과 같이 객체를 생성하기 위해 생성자 함수를 사용하여 객체 지향 프로그래밍(OOP)를 했습니다.

ES5

1
2
3
4
5
6
7
8
9
10
11
var Animal = (function () {
function MyConstructor(name) {
this.name = name;
}
MyConstructor.prototype.speak = function speak() {
console.log(this.name + ' makes a noise.');
};
return MyConstructor;
})();
var animal = new Animal('animal');
animal.speak(); // animal makes a noise.

ES6에는 몇가지 달콤한 문법이 존재합니다. 우리는 적은 보일러 플레이트와 새로운 키워드인 classconstructor 를 사용해 같은 작업을 수행할 수 있습니다. 또한 우리가 constructor.prototype.speak = function ()speak() 메서드를 얼마나 명확하게 정의했는지 확인해보세요:

ES6

1
2
3
4
5
6
7
8
9
10
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
const animal = new Animal('animal');
animal.speak(); // animal makes a noise.

보시다시피, 두 스타일 (ES5/6)은 동일한 결과를 만들어 내고 같은 방식으로 사용됩니다.

모범 사례:

  • 항상 클래스 문법을 사용하고 프로토 타입을 직접 조작하는 것을 피하세요. 왜냐구요? 왜냐하면 코드가 더 간결해지고 이해하기도 더 쉬워지기 때문입니다.
  • 빈 생성자를 가지는 것을 피하세요. 클래스는 생성자가 지정되지 않았다면 기본 생성자를 가집니다.

3.6 Inheritance

이전 Animal클래스를 기반으로 진행하겠습니다. 우리는 그것을 확장하여 Lion 클래스를 정의하기를 원합니다.

ES5에서, 이것은 프로토 타입 상속과 조금 더 관련이 있습니다.

ES5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Lion = (function () {
function MyConstructor(name){
Animal.call(this, name);
}
// prototypal inheritance
MyConstructor.prototype = Object.create(Animal.prototype);
MyConstructor.prototype.constructor = Animal;
MyConstructor.prototype.speak = function speak() {
Animal.prototype.speak.call(this);
console.log(this.name + ' roars 🦁');
};
return MyConstructor;
})();
var lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

모든 디테일한 사항들을 검토하지 않을겁니다:

  • 셋째 줄에서 우리는 Animal 생성자를 매개변수와 함께 명시적으로 호출합니다.
  • 7-8 줄에서 Lion 프로토 타입을 Animal의 프로토 타입으로 지정했습니다.
  • 11 줄에서 부모 클래스인 Animal에서 speak 메서드를 호출합니다.

ES6에서 새 키워드인 extendssuper가 있습니다.

ES6

1
2
3
4
5
6
7
8
9
class Lion extends Animal {
speak() {
super.speak();
console.log(this.name + ' roars 🦁');
}
}
const lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

이 ES6 코드가 같은 동작을 하는 ES5 코드와 비해 얼마나 읽기 쉬운지 보세요. 승리!

모범 사례:

  • 상속을 위해 내장되어 있는 extends를 사용하세요

3.7 Native Promises

콜백 헬(Callback Hell) 👹 대신 프러미스(Promise) 🙏 를 사용합니다.

ES5

1
2
3
4
5
6
7
8
9
10
11
12
function printAfterTimeout(string, timeout, done){
setTimeout(function(){
done(string);
}, timeout);
}
printAfterTimeout('Hello ', 2e3, function(result){
console.log(result);
// nested callback
printAfterTimeout(result + 'Reader', 2e3, function(result){
console.log(result);
});
});

우리는 done일 때 실행하기 위해 콜백을 받는 함수를 가지고 있습니다. 우리는 두번씩 차례대로 실행해야 합니다. 그것이 콜백에서 printAfterTimeout를 두 번째로 호출한 이유입니다.

이것은 세 번째나 네 번째 콜백이 필요할 경우 매우 빨리 동작하게 됩니다. promise가 어떻게 동작하는지 봅시다:

ES6

1
2
3
4
5
6
7
8
9
10
11
12
13
function printAfterTimeout(string, timeout){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(string);
}, timeout);
});
}
printAfterTimeout('Hello ', 2e3).then((result) => {
console.log(result);
return printAfterTimeout(result + 'Reader', 2e3);
}).then((result) => {
console.log(result);
});

보시다시피, promise를 사용하면 다른 함수가 완료된 후에 무언가를 하기 위해 then을 사용할 수 있습니다. 더이상 중첩 함수를 사용할 필요가 없어집니다.

3.8 Arrow functions

ES6는 함수 표현식을 제거하지 않았지만, 화살표 함수(arrow functions)라고 불리는 것을 새로 추가했습니다.

ES5에서 this의 문제가 몇가지 있었습니다:

ES5

1
2
3
4
5
6
7
var _this = this; // need to hold a reference
$('.btn').click(function(event){
_this.sendData(); // reference outer this
});
$('.input').on('change',function(event){
this.sendData(); // reference outer this
}.bind(this)); // bind to outer this

함수를 내부에서 참조하거나 bind를 사용하기 위해 임시로 this를 사용해야만 합니다. 하지만 ES6에서 화살표 함수를 사용할 수 있습니다.

ES6

1
2
3
4
5
// this will reference the outer one
$('.btn').click((event) => this.sendData());
// implicit returns
const ids = [291, 288, 984];
const messages = ids.map(value => `ID is ${value}`);

3.9 For…of

forforEach 대신 for...of를 사용합니다.

ES5

1
2
3
4
5
6
7
8
9
10
// for
var array = ['a', 'b', 'c', 'd'];
for (var i = 0; i < array.length; i++) {
var element = array[i];
console.log(element);
}
// forEach
array.forEach(function (element) {
console.log(element);
});

ES6 for...of 또한 반복 작업을 수행할 수 있습니다.

ES6

1
2
3
4
5
// for ...of
const array = ['a', 'b', 'c', 'd'];
for (const element of array) {
console.log(element);
}

3.10 Default parameters

변수가 정의되었는지 확인하는 것 대신 default parameters로 값을 지정할 수 있습니다. 전에 이렇게 한 적이 있나요?

ES5

1
2
3
4
5
6
7
8
9
10
function point(x, y, isFlag){
x = x || 0;
y = y || -1;
isFlag = isFlag || true;
console.log(x,y, isFlag);
}
point(0, 0) // 0 -1 true 😱
point(0, 0, false) // 0 -1 true 😱😱
point(1) // 1 -1 true
point() // 0 -1 true

아마도 변수가 값을 가지고 있는지 확인하거나 기본 값을 할당하는 일반적인 패턴입니다. 그러나 몇 가지 문제가 있는 것을 확인하세요:

  • 여덟째 줄, 0, 0을 넘겨주고 0, -1을 받게됩니다.
  • 아홉째 줄, false를 넘겨주고 true를 받게됩니다.

기본 매개변수로 부울 값을 갖거나 값을 0으로 설정한다면 작동하지 않습니다. 왜 그런지 아시나요? ES6 예를 들어 설명해 드리겠습니다. ;)

ES6를 사용하면 더 적은 코드로 더 좋게 할 수 있습니다!

ES6

1
2
3
4
5
6
7
function point(x = 0, y = -1, isFlag = true){
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

다섯째 줄과 여섯째 줄은 예상대로 결과를 얻었습니다. ES5 예제는 동작하지 않았었죠. 우리는 false, null, undefined0 은 거짓 값이므로 undefined를 먼저 확인해야 합니다. 숫자는 잊을 수 있습니다.

ES5

1
2
3
4
5
6
7
8
9
10
function point(x, y, isFlag){
x = x || 0;
y = typeof(y) === 'undefined' ? -1 : y;
isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

undefined를 확인할 때 비로소 예상대로 동작하게 됩니다.

3.11 Rest parameters

매개변수에서 나머지 매개변수와 전개 연산자로 왔습니다.

ES5에서 매개변수에서 임의의 변수를 얻는 것은 어렵습니다.

ES5

1
2
3
4
5
6
function printf(format) {
var params = [].slice.call(arguments, 1);
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);

우리는 나머지 매개변수 ...를 사용하여 동일한 작업을 수행할 수 있습니다.

ES6

1
2
3
4
5
function printf(format, ...params) {
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);

3.12 Spread operator

우리는 apply()대신 전개 연산자를 사용합니다. 우리는 다시 ...의 도움을 받습니다:

주의사항: 우리는 배열을 전달인자의 목록으로 바꾸기 위해 apply()를 사용합니다. 예를 들어, Math.max()는 전달인자의 목록을 취하지만 배열인 경우 apply를 사용하여 동작하게 합니다.

먼저 보았듯이, apply를 사용해 전달인자의 목록을 배열로 넘겼습니다.

ES5

1
Math.max.apply(Math, [2,100,1,6,43]) // 100

ES6에서는 전개 연산자를 사용할 수 있습니다.

ES6

1
Math.max(...[2,100,1,6,43]) // 100

또한 우리는 concat 대신 전개 연산자를 사용할 수 있습니다.

ES5

1
2
3
4
var array1 = [2,100,1,6,43];
var array2 = ['a', 'b', 'c', 'd'];
var array3 = [false, true, null, undefined];
console.log(array1.concat(array2, array3));

ES6에서 전개 연산자를 사용해 중첩된 배열을 병합 할 수 있습니다:

ES6

1
2
3
4
const array1 = [2,100,1,6,43];
const array2 = ['a', 'b', 'c', 'd'];
const array3 = [false, true, null, undefined];
console.log([...array1, ...array2, ...array3]);

4. Conclusion

자바스크립트는 많은 변화를 겪었습니다. 이 글은 모든 자바스크립트 개발자가 알아야 할 중심 기능을 다뤘습니다. 또한 코드를 보다 간결하고 쉽게 이해할 수 있는 모범 사례를 제공했습니다.

만약 꼭 알아야할 다른 기능이 있다면 아래 댓글로 알려주세요. 저는 이 글을 업데이트할 것 입니다.

5. 역자 후기

매번 ES6를 공부 해야지 생각만 하다가 좋은 글을 발견하여 읽게되었다.
읽는 도중에 영어가 막혀서 한글로 번역해서 보고 싶다고 생각했다.

그것이 나의 첫 번역이었다.

역시 영어가 서툰 나에게는 글 내용 자체를 이해하기도 힘들었다.
그래서 번역을 할 때 단어가 의미하는 것이 정확이 무엇인지 조사하고 그에 맞는 적절한 단어를 선택할 수 밖에 없었는데, 그 점이 오히려 나의 이해를 도운것 같다.

영어 공부를 더 해야겠다…

[원문보기]

[책] 자바스크립트 & 제이쿼리 - 존 두켓, 제이펍 출판

이 책을 산지 한 1년 만에 읽은 것 같다.

서점에 가서 책 구경을 하고 있는데 문구가 굉장히 눈에 띄는 책이 있었다.


지금 여러분은 세상에서 가장 아름다운 프로그래밍 서적을 보고 있습니다!

문구에 낚여서 펼쳐보니 여태 내가 알던 프로그래밍 서적과는 역시 매우 달랐다.
개발 서적보다는 약간 잡지의 느낌이 더 강했다.

그렇게 사버렸다.

하지만 600페이지가 넘는 책의 분량에 매번 초중반까지 읽고 끝까지 읽지 못했다.

1
2
3
4
5
6
7
8
9
 var totalPage = 600;

for(var page = 0; page == totalPage; page++) {
if (page < 150) {
reading(page)
} else {
page = 0
}
}

약간 이런느낌

무한 루프에서 빠져나오지 못하다가 요즘 시간이 많아져서 다행히 책을 다 읽을 수 있었다.

예쁘다

책이 진짜 예쁘다.
아까도 말했듯이 책보다는 잡지의 느낌이다.

사실 나는 책을 더럽게 보는 편인데 이 책만큼은 그러지 못했다.
책에 펜질도 하고 중요한 페이지는 접어두고 봐야하는데 이 책은 왠지 그러면 범죄를 저지르는 느낌이랄까

그래서 잘 안보게 되었나…

설명만으로 잘 이해하기 어려운 개념들을 그림과 인포그래픽 등으로 시각화해서 이해를 많이 도왔다.

또한 책의 전체적인 구성이 설명 한페이지 예제 한페이지 이런식으로 되어있는데 그 점 또한 매력적이었다.

하지만 이런 장점이 약간 가독성을 떨어뜨리는 것 같다.

기존에 보던 개발서적과는 UI(?)가 달라서 그런가 종종 처음부터 다시 보는 페이지가 몇몇 있었다.

그래서 책 내용은…?

JavaScript와 JQuery를 처음 접하는 사람에게는 매우 좋은 입문서가 될 것 같다.
기초적인 문법과 사용 예제 더 나아가 AJAX, API 활용 등 여러방면으로 설명이 잘 되어있다.

개발을 막 배우다보면 놓치고 가는 개념들도 많은데 그런것들도 섬세하게 설명해주었다.(나한텐 그랬다…)

아직 쪼렙 개발자인 나는 이 책에서 새로 알게된 개념들도 많았고, 헷갈리던 개념들을 정리한 것도 많았다.

  • this 키워드
  • event를 바인딩하는 방법
  • XSS 공격

언젠가 위 내용들도 따로 포스팅해야겠다.

책 내용이 그렇게 무겁지가 않아서 시간이 조금 있다면 슥슥 넘기면서 보기 좋은 책이다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×