이전 포스트에서 rest 파라미터에 관해서 간단하게 정리한 적이 있었는데 구조분해할당과 rest, spread 문법을 좀 더 자세히 알아봤고 이후 Form 전송에 대한 학습을 진행했다.
구조분해는 구조화된 배열 또는 객체를 비구조화하여 개별적인 변수에 압축해제할 수 있게 해주는 Javascript 표현식이다. 배열 또는 객체 리터럴에서 필요한 값만을 추출하여 변수에 할당하거나 변환할때 유용하다.
let a, b, rest;
[a, b] = [10,20];
console.log(a); // 10
console.log(b); // 20
[a,b, ...rest] = [10,20,30,40,50];
console.log(rest); // [30,40,50]
배열의 각 요소를 배열로부터 추출하여 변수 리스트에 할당하며 추출과 할당 기준은 배열의 인덱스이다.
배열 구조분해를 위해서 할당 연산자 왼쪽에 배열 형태의 변수 리스트가 필요하고 왼쪽의 변수 리스트와 오른쪽의 배열은 배열의 인덱스를 기준으로 할당된다.
const array = [1,2,3];
let [a, b, c] = array;
console.log(a,b,c); // 1 2 3
[x, y] = array;
console.log(x, y); // 1 2
[x, ,y] = array;
console.log(x, y); // 1 3
[x, y, z, k=4] = array; // 기본값
console.log(x, y, z, k); // 1 2 3 4, 이미 값이 할당된 경우 이 방식으로 값을 재할당할수 없다.
[x, ...y] = array; // spread
console.log(x, y); // 1 [2, 3]
배열의 경우 인덱스의 순서에 따라 값이 할당되므로 인덱스만 중요하여 이름을 자유롭게 변경할 수 있다.
배열 구조분해는 배열에서 필요한 요소만 추출하여 변수에 할당하고 싶을때 유용하다.
const today = new Date(); // Tue May 21 2019 22:19:42 GMT+0900 (한국 표준시)
const formattedDate = today.toISOString().substring(0, 10); // "2019-05-21"
const [year, month, day] = formattedDate.split('-');
console.log([year, month, day]); // [ '2019', '05', '21' ]
출처 : poiemaweb - destrucutring
순서 중요하지 않고 순서가 바뀌어도 키값에 따라 할당된다.
객체의 각 프로퍼티를 객체로부터 구조분해 할당하기 위해서 프로퍼티 키를 사용해야 한다.
객체 구조분해 할당의 경우 변수명 바꿔줄때 퀄른 : 을 사용하고 기본값 할당할때 = 를 사용한다.
객체 구조분해 할당을 위해 할당 연산자 왼쪽에 객체 형태의 변수 리스트가 필요하다.
let obj = {name: 'john', age: 10};
const {name, age} = obj;
console.log(name, age); // john 10
const {name: name1, age: age1} = obj; // 이름 바꾸기
console.log(name1, age1); // john 10
const {name: name2, age: age2 = 20} = obj; // 기본값? no!
console.log(name2, age2); // john 10, 이미 값을 할당받을 경우 기본값이 할당되는게 아니다.
let obj2 = {height: 50, width: 50};
const {height, width, padding = 60} = obj2; // 기본값
console.log(height, width, padding); // 50 50 60
spread 는 펼친다는 의미를 갖고 있다.
실제 사용시엔 배열에서 배열이 갖고 있는 내용들을 풀어서 넣는 기능을 할 수 있다.
const nums = [1,2,3];
const spreadNums = [...nums, 100];
console.log(spreadNums); // [1 2 3 1000];
rest 는 펼친다기 보단 객체, 배열, 함수의 파라미터에서 나머지 요소들을 한번에 묶어서 표현할때 사용할 수 있다. 단, rest 는 앞에서 사용할 수 없고 마지막에 사용해야 한다.
const obj = {
name: 'john',
age: 10,
height: 180
};
const {name, ...rest} = obj;
console.log(rest); // {age: 10, height: 180}
const numbers = [0, 1, 2, 3, 4, 5];
const [one, ...rest] = numbers;
console.log(one); // 0
console.log(rest); // [1,2,3,4,5]
function sum(...rest) {
return rest;
}
const result = sum(1,2,3);
console.log(result); // [1,2,3]
자바스크립트의 클래스는 함수의 일종이다.
??
의도인지 어떤지는 모르겠지만 typeof 를 해봤을때 Class 는 function 이라고 나온다.
class User {
cosntructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
console.log(typeof User);
// 사용법
let user = new User('John');
user.sayHi();
new 키워드를 사용하여 클래스를 호출할때 다음과 같은 일이 발생한다.
빵을 예로 들어보자. 빵틀이 클래스이고 빵이 인스턴스다. 일정한 규격을 갖춘 클래스에 따라 new 키워드를 통해 클래스를 호출하면 그에 따른 인스턴스들을 생성할 수 있다.
또한 클래스의 argument 로 'John' 을 전달하여 new 키워드로 인스턴스를 생성하는 순간 constructor 생성자 함수가 실행된다. 이때 전달한 인자인 'John' 이 name 을 통해 전달되고 this.name 을 통해 user 라는 인스턴스가 name 이라는 프로퍼티를 갖고 그 값이 'John' 이 된다.
[출처: 모던 자바스크립트 Deep Dive]
이전에 참고했던 이미지를 다시 보자.
constructor 생성자 함수 내에서 this.name 이라고 코딩한 부분은 인스턴스 프로퍼티가 된다.
class 내부이나 constructor 바깥에서 생성했던 sayHi(); 라는 메서드는 생성자 함수와 달리 클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 된다.
new 키워드로 생성한 user 라는 인스턴스는 프로토타입 체이닝에 의해 프로토타입 메서드인 sayHi() 라는 메서드를 상속받아 사용할 수 있다.
생성자 함수의 경우 정적 메서드는 명시적으로 생성자 함수에 메서드를 추가해야 정적 메서드를 생성할 수 있다.
// 생성자 함수
function Person(name) {
this.name = name;
}
// 정적 메서드
Person.sayHi = function () {
console.log('hi!')
}
Person.sayHi(); // hi!
클래스의 경우 정적 메서드는 메서드에 static 만 붙이면 정적 메서드가 된다. 정적 메서드는 인스턴스 없이 호출할 수 있다. 이는 정적 메서드가 프로토타입 체이닝에 속하지 않기 때문에 인스턴스로는 정적 메서드를 상속받지 않기 때문이다.
class Person {
constructor(name) {
this.name = name;
}
static sayHi() {
console.log('Hi');
}
static publisher = 'Tom Tailer';
}
Person.sayHi(); // Hi,
혹시 잘못 이해한 부분이 보인다면 언제든지 댓글 부탁드립니다.
javascript.info - class 에 흥미로운 예시가 있어서 가져와봤다.
class Button {
constructor(value) {
this.value = value;
}
click() {
console.log(this.value);
};
}
let button = new Button("안녕하세요.");
setTimeout(button.click, 1000); // undefined
위의 코드를 실행하면 undefined 가 출력되고
class Button {
constructor(value) {
this.value = value;
}
click = () => {
console.log(this.value);
};
}
let button = new Button("안녕하세요.");
setTimeout(button.click, 1000); // 안녕하세요.
위의 코드를 실행하면 안녕하세요
가 출력된다.
두 코드의 프로토타입 메서드를 일반 함수로 선언하는지 혹은 화살표 함수로 선언하는지의 차이가 있다. 그런데 화살표 함수의 경우 자신만의 this 를 갖지 않고 상위 스코프의 this 를 그대로 사용하기에 button.click
을 전달해도 this.value 가 정상적으로 안녕하세요
가 출력된다.
일반 함수의 경우 button.click 을 setTimeout 에 전달하면 button.click 이 실행되지 않고 함수의 레퍼런스가 전달되면서 this 가 올바르게 동작하지 않는다. 따라서 undefined
가 출력된다.
javascript 도 접근 범위를 제한할 수 있다.
public 은 어디서든지 접근할 수 있고 기본적으로 public 이다.
private 는 클래스 내부에서만 접근할 수 있고 protected 는 클래스 내부에서와 상속받은 클래스에서 사용할 수 있다.
protected 프로퍼티명 앞엔 밑줄 _ 이 붙고 private 프로퍼티와 메서드는 #으로 시작한다.
protected 는 문법적으로 지원하는 바가 아니나 프로그래머들끼리 암묵적인 약속이다. protected 로 만들고 싶은 프로퍼티를 외부에서 접근시 get/set 함수를 만들어서 접근한다.
class CoffeeMachine {
_waterAmount = 0;
#waterLimit = 200;
#checkWater() {
...
}
setWaterAmount(value) {
if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
this._waterAmount = value;
}
getWaterAmount() {
return this._waterAmount;
}
}
new CoffeeMachine().setWaterAmount(100);
let coffeeMachine = new CoffeeMachine();
coffeeMachine.#checkWater(); // Error, private 는 외부에서 접근 불가
[출처 : javascript.info - private, protected]
forEach 는 반환값이 없다.
find 는 조건에 맞는 객체, 요소를 하나 반환한다면(있을때) filter 는 조건에 맞는 요소들을 담은 배열을 반환한다.
map 은 순회하며 요소마다 콜백함수 적용후 새로운 배열로 리턴한다.
fiter 와 map 비교
- 둘다 기존 배열은 변화가 없고 요소들을 순회한 뒤 새로운 배열을 리턴한다.
- map 은 콜백함수가 적용된 새 요소, filter 는 조건문을 만족한 요소들을 반환한다.
미들웨어 함수는 요청과 응답, 그리고 애플리케이션의 요청-응답 주기 중 그 다음의 미들웨어 함수에 대한 액세스 권한을 갖는 함수이며 그 다음의 미들웨어 함수는 next 라는 이름의 변수로 표시된다.
express 에서 미들웨어란 말 그대로 요청과 응답 사이 중간 middle 에서 핸들링해주는 기능을 말한다.
기능
현재의 미들웨어 함수가 요청-응답 주기를 종료하지 않을 경우 next() 를 호출하여 그 다음 미들웨어 함수에 제어를 전달해야 하며 그렇지 않으면 해당 요청은 정지된 채로 방치된다.
수업중 express 를 사용한 사례를 정리해봤다.
const express = require('express');
const app = express();
//// app.use
// body-parser
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// 정적 파일 업로드
// __dirname : 현재 실행중인 파일의 디렉토리 경로
app.use("/uploads", express.static(__dirname + "/uploads"));
//// app.set
// 뷰엔진
app.set("view engine", "ejs");
app.set("views", "./views");
//// app.METHOD()
// METHOD 는 미들웨어 함수가 처리하는 요청 (get, put, post) 등의 HTTP 메소드
// router
app.get("/", (req, res) => {
res.render("index");
});
app.post("/upload", upload.single("userfile"), function (req, res) {
console.log(req.file);
res.send("Upload!");
});
// 지정된 경로에서 연결을 수신 대기
app.listen(PORT, () => {
console.log(`http://localhost:${PORT}`);
});
미들웨어 종류
써드파트 미들웨어를 클릭해보자
써드 파티 미들웨어로 들어가보면 익숙한 body-parser
를 볼 수 있다.
여기에서 의문하나가 든다.. 지금까지 사용했던 이 코드
// body-parser
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
body-parser 라고 배웠는데 express docs
에서 보면 body-parser 가 따로 있는 것 같다.
express docs
에서 소개하는 body-parser example 코드
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
res.end(JSON.stringify(req.body, null, 2))
})
마지막 코드를 보면 body-parser 미들웨어에 의해 request 파라미터 변수에 body 가 생성되는 것을 알 수 있다.
우리는 express 에서 urlencoded 와 json() 을 사용하고 있었는데 이것과 bodyParser 에서 직접 사용하는데에 차이가 있을까?
애초에 클라이언트로부터 받은 http 요청 메시지 형식에서 body 데이터를 해석하기 위해 express.json() 과 express.urlencoded() 로 처리가 필요했었다.
express.urlencoded
에서
extended: false
=> Nodejs 에 기본적으로 내장된 querystring 모듈을 사용한다.extended: true
=> 추가로 설치가 필요한 qs 모듈을 사용하며 qs 모듈은 express 에 포함되어 자동으로 설치된다.node_modules/express/package.json dependencies
좀 방향이 샌 것 같은데 .urlencoded 는 content-type 이 x-www-form-urlencoded 형태의 데이터를 해석하고 .json() 은 JSON 형태의 데이터를 해석한다.
우리가 사용하던 방식은 express 내장 방식이며 express docs 에서 소개하는 외부 body-parser 패키지를 직접 가져오는 방식도 있으나 기능적인 측면에서는 완전히 동일하다.
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
var bodyParser = require('body-parser')
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
body-parser
- body : 웹 브라우저 측에서 요청한 정보의 본체 (이 본체를 설명하는 데이터가 헤더이다.)
- parser : 이 본체 데이터를 분석해서 우리가 필요한 형태로 가공해준다.
express docs
에서 writing middleware 의 예시코드를 보자 링크
const express = require('express')
const app = express()
const myLogger = function (req, res, next) {
console.log('LOGGED')
next()
}
app.use(myLogger)
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(3000)
myLogger 를 보면 알 수 있듯이 req, res, next 라는 파라미터를 갖는 함수를 express 에서 미들웨어 함수라고 부른다. 이 함수 내부를 어떻게 구현하는지에 따라 앞에서 살펴본 써드파티 미들웨어같은 동작을 하게 되는 것이다.
- next 에는 다음에 호출되어야 할 미들웨어가 담겨있다.
app.use()
를 통해 미들웨어를 사용할 수 있다. 지금까지 router 라고 생각했던 app.get, app.post 들은 사실 첫 번째 파라미터로 경로 ('*' 을 사용시 모든 경로를 의미), 두 번째 파라미터로 미들웨어에 해당하는 콜백함수 (미들웨어함수) 였다...
알아볼 만큼 알아본 것 같다 ...
정리
하자면 우리가 지금까지 사용한 express 미들웨어는 Application-level middleware 였다.
const express = require('express')
const app = express()
app.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
app.use('/user/:id', (req, res, next) => {
console.log('Request Type:', req.method)
next()
})
app.use('/user/:id', (req, res, next) => {
console.log('Request URL:', req.originalUrl)
next() // 이 next 는 바로 뒤에 붙인 미들웨어를 호출한 것과 같다.
}, (req, res, next) => {
console.log('Request Type:', req.method)
next()
})
app.use 및 app.METHOD 를 통해 미들웨어 함수를 등록하여 사용할 수 있고 requset, response 객체를 받아서 변형할 수 있다. 그리고 next 라는 것을 호출해서 그 다음에 실행되어야 할 미들웨어를 그 이전의 미들웨어가 실행 여부를 결정할 수 있게 한다. 또한 특정 경로를 주어서 특정 경로에 대해서만, 특정 METHOD 방식의 미들웨어가 동작하도록 할 수도 있다.
미들웨어 함수는 여러개를 붙여서 사용할수도 있다.
app.get('/user/:id', (req, res, next) => {
// if the user ID is 0, skip to the next route
if (req.params.id === '0') next('route')
// otherwise pass the control to the next middleware function in this stack
else next()
}, (req, res, next) => {
// send a regular response
res.send('regular')
})
// handler for the /user/:id path, which sends a special response
app.get('/user/:id', (req, res, next) => {
res.send('special')
})
이 예시코드는 경로와 METHOD 종류가 같은 미들웨어를 두번이상 실행시 미들웨어 실행 순서를 보여주는데 req.params.id 가 0 일때 next('route') 는 아래의 router special
미들웨어가 실행되고 그렇지 않다면 next() 가 연달아 붙인 regular
미들웨어를 실행하게 한다.
생활코딩 강의 정말 만족스럽다
HTTP API 를 만들때 중요한 것은 리소스와 행위를 분리하는 것이며 그중 리소스 식별이 가장 중요하다. URI 는 리소스만 식별
하며 행위는 HTTP 메서드로 구분
할 수 있다.
GET, POST 어떤 차이인지 답을 해야 하는 상황일때 뭐라고 답할까?
나의 경우 멱등성의 차이가 있다고 답을 하고 싶다.
멱등성 : 여러번 연산을 적용하더라도 결과가 달라지지 않는 성질
GET 은 멱등성을 갖고 POST 는 멱등성을 갖지 않는다. 즉, 리스트 조회, 유저 조회같은 경우 몇번을 실행하더라도 결과가 달라지지 않으나 GET 사용
/ 포스트 생성, 임시 저장, 토큰 생성과 같은 경우 실행할때마다 결과가 달라진다. POST 사용
(id, jwt 토큰값 등이 매번 달라진다) 즉, 무작정 get, post 를 사용할 것이 아니라 이런 특성을 비교하면서 HTTP 메서드를 사용해야 한다는 의미이다.
바디
를 통해 서버로 요청 데이터를 전달한다.즉, 새 리소스를 생성하거나 / 요청 데이터를 처리하거나 / 다른 메서드로 처리하기 애매한 경우 POST 를 사용한다.
앞에서 PUT 과 PATCH 의 특성을 다음과 같이 정리했다.
이 말로는 이해가 잘 되지 않을 수 있는데 다음 예시를 보자
userID: 100 인 유저의 정보를 age: 50 으로 바꾸려고 한다. 아래는 기존 100번 유저의 정보다.
{
"username": "young",
"age": "20"
}
PUT 메서드는 리소스를 완전히 대체한다. 즉, request 에 age: 50 만 있었다면 username 은 사라지고 age: 50 만 남는다.
PATCH 메서드는 리소스를 부분 변경한다. 즉, request 에서 변동사항이 생겼던 age 에 대해서만 값을 부분 교체한다.
DELETE 메서드는 리소스를 제거한다.
위의 특성들을 볼때
GET 메서드는 멱등성을 갖기에 호출해도 리소스를 변경하지 않아 안전하다. POST 메서드는 멱등성을 갖지 않는다.
그놈의 Content-Type 이번에 정리하자
Content-Type
: 표현 데이터의 형식
Content-Encoding
: 표현 데이터의 압축 방식
Content-Language
: 표현 데이터의 자연 언어
Content-Length
: 표현 데이터의 길이
REST
: REpresentational State Transfer 는 웹의 장점을 최대한 활용할 수 있는 아키텍쳐로서 REST 가 소개되었고 HTTP 프로토콜을 의도에 맞게 디자인하도록 유도하고 있다. REST 의 기본 원칙을 성실히 지킨 서비스 디자인을 RESTful 하다고 표현한다.
Representational : 대표적인
State : 상태
Transfer : 전달
즉, REST
는 대표적인 자원의 상태를 전달하는 것
을 말한다.
카카오 플랫폼 서비스에서는 REST API 에 대해서 우리가 어떠한 자원에 직접 접근하는 것이 아닌 이러한 자원의 대표라고 부를 수 있는 JSON, XML, HTML 과 같은 데이터 자원의 위치를 URI(Uniform Resource Identifier) 등으로 명시해두면 HTTP 상에서의 요청을 통해 접근할 수 있도록 하는 것을 REST 라고 한다.
라고 설명하고 있다.
API
: Application Programming Interface
REST 의 중요한 규칙은
# bad
GET /getTodos/1
GET /todos/show/1
# good
GET /todos/1
# bad
GET /todos/delete/1
# good
DELETE /todos/1
Method | Action | 역할 | 페이로드 |
---|---|---|---|
GET | index/retrieve | 모든/특정 리소스를 조회 | x |
POST | create | 리소스를 생성 | ○ |
PUT | replace | 리소스의 전체를 교체 | ○ |
PATCH | modify | 리소스의 일부를 수정 | ○ |
DELETE | delete | 모든/특정 리소스를 삭제 | x |
REST API 는 리소스, 행위, 내용으로 구성되며 리소스는 HTTP URI 로, 행위는 HTTP Method 로, 내용은 HTTP payload 로 표현한다.
docs
mdn - destructuring
Express.js - middleware
blog
javascript.info - destructuring
벨로퍼트 모던 자바스크립트 - destructuring
poiemaweb - destructuring
KirKim - body-parser
superbono - HTTP 메서드 GET, POST 비교
poiemaweb - REST
course
생활코딩 express
인프런 모든 개발자를 위한 HTTP 웹 기본 지식
private 는 앞에 #, protected 는 앞에 _을 붙인다.
너무 길어지는 것 같아 중간에 끊었다. 이 내용을 조사하다 보면 저것도 알아야 하고 이것도 알아야 할 것 같아서 점점 내용이 추가되고 있었다. 이 포스트에 정리한 내용만큼은 나의 것으로 완전히 이해한다는 마음가짐으로 블로그를 작성해보자.
글 잘 봤습니다.