0%

BigInt

BigInt 길이의 제약 없이 정수르 사용할 수 있게 한다.
리터럴로 정수 끝에 n 을 붙이거나 BigInt()를 사용하면 된다.

1
2
3
4
5
const bigint = 1234567890123456789012345678901234567890n;

const sameBigint = BigInt("1234567890123456789012345678901234567890");

const bigintFromNumber = BigInt(10); // 10n과 동일합니다.

수학 연산자

일반 숫자와 큰 차이 없이 사용할 수 있다.

BigInt 형 값을 대상으로 한 연산은 BigInt 형 값을 반환한다. BigInt 형 값과 일반 숫자를 섞어서 사용할 순 없다.

1
alert(1n + 2); // Error: Cannot mix BigInt and other types

BigInt()Number()를 명시해 줌으로서 같이 사용할 수 있다.

1
alert(1n + BigInt(2));

bigint를 정수형으로 바꾸기 위해서 +를 사용할 수 없다. 명시적으로 Number()를 사용해 주어야 하고, bigint 값이 너무 큰 경우는 비트는 자동으로 자렬 나간다.

비교 연산자

1
2
alert(2n > 1n); // true
alert(2n > 1); // true

비교 연산은 서로간에 가능한다.

1
2
alert(1 == 1n); // true
alert(1 === 1n); // false

같은 수라도 타입이 다르기 때문에 ===를 사욯아면 false를 반환한다.

논리 연산

if 안에서 0n 은 falsy 이고 다른 값은 truthy로 평가된다.

fetch

AJAX - 서버에서 추가 정보를 비동기적으로 가져올 수 있게 해주는 포괄적인 기술을 의미함. (새로 고침 없이)

AJAX 이외에도 서버에 네트워크 요청을 보내고 정보를 받아올 수 있는 다양한 방법이 존재하는데 그 중 하나인 fetch에 대해서 알아본다.

1
let promise = fetch(url, [options]);
  • url : 접근하고자 하는 url
  • options : 선택 매개변수, method나 header 등을 지정할 수 있다.

options에 아무것도 넘기지 않으면 GET으로 동작한다.

서버에서 응답 헤더를 받자마자 fetch 호출 시 반환받은 promise가 내장 클래스 Response의 인스턴스와 함께 이행 상태가 된다. 아직 본문이 도착하기 전이지만, 개발자는 응답 헤더를 보고 요청이 성공적으로 처리되었는지 아닌지를 확인할 수 있다.

HTTP 응답 상태는 프로퍼티를 사용해 확인할 수 있다.

  • status - HTTP 상태 코드 값 (ex. 200)
  • ok - 불린 값, HTTP 상태 코드가 200 과 299 사이일 경우 ture
1
2
3
4
5
6
7
let response = awiat fetch(url);

if (response.ok) {
let json = await reponse.json();
} else {
alert("HTTP-Error: " + response.status);
}

response에는 프라미스를 기반으로 하는 다양한 메서드가 있다. 이 메서드를 사용하면 다양한 형태의 응답 본문을 처리할 수 있다.

  • response.text() - 응답을 읽고 텍스트로 반환해 줌.
  • response.json() - 응답을 읽고 JSON으로 파싱해 줌
  • response.formData() - 응답을 FormData 객체 형태로 반환함.
  • response.blob() - 응답을 Blob(타입이 있는 바이너리 데이터) 형태로 반환 함.
  • response.arrayBuffer() - 응답을 ArrayBuffer(바이너리 데이터를 로우 레벨 형식으로 표현한 것)
  • 이 외에도 respose.body가 있는데 청크 단위로 읽을 수 있게 해준다.
1
2
3
4
5
6
7
let url =
"https://api.github.com/repos/javascript-tutorial/ko.javascript.info/commits";
let response = await fetch(url);

let commits = await response.json(); // 응답 본문을 읽고 JSON 형태로 파싱함

alert(commits[0].author.login);

본문을 읽을 때 사용되는 메서드는 딱 하나만 사용할 수 있다. response.text() 를 사용해 응답을 얻었다면 본문의 콘텐츠는 모두 처리된 상태이기 때문에 response.json()은 동작하지 않는다.

응답 헤더

응답 헤더는 response.headers에 맵과 유사한 형태로 저장된다.

1
2
3
4
5
6
7
8
9
10
11
let response = await fetch(
"https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits"
);

// 헤더 일부를 추출
alert(response.headers.get("Content-Type")); // application/json; charset=utf-8

// 헤더 전체를 순회
for (let [key, value] of response.headers) {
alert(`${key} = ${value}`);
}

요청 헤더

headers 옵션을 사용하면 fetch에 요청 헤더를 ㅅ걸정할 수 있다.

1
2
3
4
5
let response = fetch(protectedUrl, {
headers: {
Authentication: "secret",
},
});

POST 요청

GET 요청 이외의 요청을 보내려면 추가 옵션을 설정해야 한다.

  • method - ex.POST
  • body - 요청 본문, 다음 중 하나여야 한다.
    • 문자열(ex. JSON 문자열)
    • FormData 객체 - form/multipart
    • Blob이나 BufferSource - 바이너리 데이터 전송을 위해 사용됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let user = {
name: "John",
surname: "Smish",
};

let response = await fetch("/article/fetch/post/user", {
method: "POST",
headers: {
"Content-Type": "application/json;charset-utf-8",
},
body: JSON.stringify(user),
});

let result = await reponse.json();

본문이 문자열일 때는 Content-Type - text/plain;charset-UTF-8 이 기본으로 설정됨. 하지만 위에 예시에서는 JSON 이기 때문에 따로 설정을 해주었다.

FormData

HTML에 formData를 나타내는 생성자는 다음과 같다.

1
let formData = new FormData([form]);

이렇게 생성해서 서버에서 보내는 작업을 하면 서버에서 볼 때 일반적인 양식 제출처럼 보인다.

Sending a simple form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<form id="formElem">
<input type="text" name="name" value="John" />
<input type="text" name="surname" value="Smith" />
<input type="submit" />
</form>

<script>
formElem.onsubmit = async (e) => {
e.preventDefault();

let response = await fetch("/article/formdata/post/user", {
method: "POST",
body: new FormData(formElem),
});

let result = await response.json();

alert(result.message);
};
</script>

서버 코드는 여기에 없다. 서버는 위에 코드에서 보낸 request를 받아서 응답한다.

FormData Methods

  • formData.append(name, value) - form data에 name과 value를 추가
  • formData.append(name, blob, fileName) - <input type="file"> 인 것처럼 추가, 세번째 매개변수인 fileName인 진짜 파일 이름을 넣어줌.
  • formData.delete(name) - name이란 이름으로 추가된 필드를 제거함
  • formData.get(name) - name이란 이름으로 추가된 필드에 value 값을 얻음.
  • formData.had(name) - name이란 이름으로 추가된 필드가 있다면 ,true 반환 아닐 경우 false 반환

formData는 기술적으로 같은 name에 필드를 추가할 수 있다. 따라서 append를 사용한다. 비슷한 역할을 하는 .set 메서드도 있다. 이 경우 기존에 있던것을 삭제 하고 추가한다.

  • formData.set(name, value)
  • formData.set(name, blob, fileName)

또한 for..of 를 사용해서 요소를 반복할 수 있다.

Sending a form with a file

form은 Content-Type: multipart/form-data로 보내지고 이것은 파일도 보낼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<form id="formElem">
<input type="text" name="firstName" value="John" />
Picture: <input type="file" name="picture" accept="image/*" />
<input type="submit" />
</form>

<script>
formElem.onsubmit = async (e) => {
e.preventDefault();

let response = await fetch("/article/formdata/post/user-avatar", {
method: "POST",
body: new FormData(formElem),
});

let result = await response.json();

alert(result.message);
};
</script>

Sending a form with Blob data

동적으로 생성된 바이너리 데이타 이미지 같은 것을 Blob으로 보낼 수 있다. fetchbody 파라미터로 넒길 수 있다.
그러나 실제로는 이미지를 별도로 보내는 것이 아니라 name 을 추가한 form 으로 보내는 것이 편리한 경우가 많다.

서버도 form으로 받는게 더 어울린다.

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
<body style="margin:0">
<canvas
id="canvasElem"
width="100"
height="80"
style="border:1px solid"
></canvas>

<input type="button" value="Submit" onclick="submit()" />

<script>
canvasElem.onmousemove = function (e) {
let ctx = canvasElem.getContext("2d");
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
};

async function submit() {
let imageBlob = await new Promise((resolve) =>
canvasElem.toBlob(resolve, "image/png")
);

let formData = new FormData();
formData.append("firstName", "John");
formData.append("image", imageBlob, "image.png");

let response = await fetch("/article/formdata/post/image-form", {
method: "POST",
body: formData,
});
let result = await response.json();
alert(result.message);
}
</script>
</body>

이미지 blob 데이터를 어떻게 추가했는지 보자.

1
formData.append("image", imageBlob, "image.png");

이는 <input type="file" name="image">와 같다.

Fetch: download progress

fetch 를 사용하면 다운로드 과정을 추적할 수 있다. 현재는 업로드를 추적할 수는 없어서 업로드를 추적하려면 WMLHttpRequest 를 사용해야 함.

response.body 속성을 사용해서 접근할 수 있다. ReadableStream이라는 청크 단위로 제공하는 특수 객체를 이요한다.

reponse.text()response.json() 와는 다르게 reponse.body 는 전체 과정에을 카운트 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// instead of reponse.json() and other methods
const reader = reponse.body.getReader();

// infinite loop while the body is downloading
while (true) {
// done is true for the last chunk
// value is Uint8Array of the chunk bytes
const { done, value } = await reader.read();

if (done) {
break;
}
}

await reader.read()에 결과는 다음 두가지 프로퍼티를 가진 결과이다.

  • done : 읽기가 모두 완료 됬으면 true, 아니면 false
  • value : Uint8Array 타입인 Array

ReadableStream 은 for awiat ..of도 지원한다, 하지만 아직까지 모든 브라우저에서 지원하는 것이 아니기 때문에 여기서는 while를 사용했다.

진행사항을 로깅하는 전체 예제는 다음과 같다.

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
// step 1 : start the fecth and obtain a reader
let response = await fetch(
"https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100"
);

const reader = response.body.getReader();

// step 2 : get total length
const contentLength = +response.headers.get("Content-Length");

// step 3 : read the data
let recivedLength = 0; // received that many bytes at the momnet
let chunks = []; // array of received binary chunks (comprises the body)
while (true) {
const { done, value } = await reader.read();

if (done) {
break;
}

chunks.push(value);
receivedLength += value.length;

console.log(`Received ${receivedLength} of ${contentLength}`);
}

// step 4: concatenate chunks into single Uint8Array
let chunksAll = new Uint8Array(receivedLength);
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}

// step 5: decode into a string
let result = new TextDecoder("utf-8").decode(chunksAll);

// We're done!
let commits = JSON.parse(result);
alert(commits[0].author.login);

“Received 79656 of 0”
“Received 437910 of 0”

  • response.json()을 통해 데이터를 읽지 않았다.
  • Content-Length 헤더를 통해서 response에 길이를 알 수 있다.
  • chunks 배열에 chunk를 모은다. response는 한번 읽으면 다시 읽을 수 없기 때문이다.
  • chunksAll에 하나의 결과로 모은다. 아쉽게도 다른 메서드가 없기 때문에 반복문을 사용햇 가각의 chunk를 .set을 통해 복사한다. 하지만 아직도 바이트 어레이이다.
  • byte를 string으로 해석할 필요가 있다. TextDecoder은 원하는 딱 이런 작업을 해줌.

Fetch: Abort

fetch 는 promise를 반환하고 자바스크립트에서는 fetch를 중단하는 개념이 없다. 이런 목적을 위해 내장된 객체인 AbortController가 있다.
이것은 fetch 뿐만 아니라 비동기 작업에서도 사용될 수 있다.

The AbortController object

1
let controller = new AbortController();

반환하는 객체는 간단하다.

  • abort() 메서드를 갖는다.
  • 이벤트 등록을 위한 signal 프로퍼티를 갖는다.

abort()가 호출되었을 때

  • controller.signalabort 이벤트를 내보낸다.
  • controller.signal.aborted 프로퍼티가 true가 된다.

취소 가능한 작업을 controller.signal에 리스너로 등록한다. controller.abort()를 호출할때 리스너가 트리거 된다.

fetch를 포함하지 않은 예제는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
let controller = new AbortController();
let signal = controller.signal;

// teh party that performs a cancelable operation
// gets "signal" object
// and sets the listener to trigger when controller.abort() is called
signal.addEventListener("abort", () => alert("abort!!"));

// the other party, that cancels (at any point later)
controller.abort(); // abort!!

//the event triggers and signal.aborted becomes true
alert(signal.aborted); // true

코드에서 AbortController 없이 비슷한 작업을 할 수 있지만 fecth 가 AbortController 객체와 함께 작동하고 통합된다는것이 중요하다.

Using with fetch

AbortControllersignal프로퍼티를 fetch에 옵션으로 전달해 주면 된다.

1
2
3
4
let controller = new AbortController();
fetch(url, {
signal: controller.signal,
});

fetch가 중단되면 AbortError를 던진다. try..catch를 통해서 에러를 잡을 수 있다.

전체 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// abort in 1 second
let controller = new AbofrtController();
setTimeout(() => controller.abort(), 1000);

try {
let response = await fetch("/article/fetch-abort/demo/hang", {
signal: controller.signal,
});
} catch (err) {
if (err.name == "AbortError") {
alert("Aborted!");
} else {
throw err;
}
}

AbortController is scalable

AbortController은 확장가능햇 한번에 여러 fetch를 중단시킬 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
let urls = [...];

let controller = new AbortController();

// an array of fetch promises
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));

let results = await Promise.all(fetchJobs);

// if controller.abort() is called from elsewhere, it aborts all fetches

다른 비동기 작업도 fetch와 함께 하나의 AbortController로 멈출 수 있다.

우린 단지 abort 이벤트를 들으면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let urls = [...];
let controller = new AbortController();

let ourJob = new Promise((resolve, reject) => {
// our task
controller.signal.addEventListener('abort', reject);
})

let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));

// Wait for feches and our tast in parallel
let results = await Promise.all([...fetchJobs, ourJob]);

// if controller.abort() is called from elsewhere, it aborts all fetches and our Job

fetch와 Cross-Origin 요청

도메인이나 서브도메인, 프로토콜, 포트가 다른 곳에 요청을 보내는 것을 Croww-Origin Request(크로스 오리진 요청) 이라고 한다. 크로스 오리진 요청을 보내려면 리모트 오리진에서 전송받은 특별한 헤더가 필요하다. 이러한 정책을 CORS라고 부른다.

왜 CORS 가 필요한가에 대한 짧은 역사

과거 수 년 동안, 한 사이트의 스크립트에서 다른 사이트에 있는 콘텐츠에 접근할 수 없다는 제약이 있었다. 더 강력한 작업을 위해서 다른 웹사이트로에 요청이 필요했다.

여러가지 트릭이 있었지만, 오랜 논의 끝에 크로스 오리진 요청을 허용 하기로 결정했다. 대신 크로스 오리진 요청은 서버에서 명시적으로 크로스 오리진 요청을 ‘허가’ 했다는 것을 알려주는 특별한 헤더를 전송받았을 때만 가능하도록 제약을 걸었다.

안전한 요청

안전한 요청은 다음과 같은 두 가지 조건 모두를 충족한다.

  1. 안전한 메서드 - GET이나 POST, HEAD를 사용한 요청
  2. 안전한 헤더 - 다음 목록에 속하는 헤더
    • Accept
    • Accept-Language
    • Content-Language
    • 값이 application/x-www-form-urlencoded나 multipart/form-data, text/plain 인 Content-Type

표준이 아닌 헤더가 들어있거나 안전하지 않은 메서드를 사용한 요청은 안전한 요청이 될 수 없다. 그런데 시간이 지나고 개발자가 스크립트를 사용해 안전하지 않은 요청을 보낼 수 있게되자, 브라우저는 안전하지 않은 요청을 서버에 전송하기 전에 preflight 요청을 먼저 전송해 서버가 크로스 오리진 요청을 받을 준비가 되어있는지 확인한다.

이때 서버에서 크로스 오리진 요청은 허용하지 않는다는 정보를 담은 헤더를 브라우저에 응답하면 안전하지 않은 요청은 서버에 전송되지 않는다.

CORS와 안전한 요청

크로스 오리진 요청을 보낼 경우 브라우저는 항상 Origin이라는 헤더를 요청에 추가해서 자신에 도메인을 적는다.

1
2
3
4
GET /request
Host: anywhere.com
Origin: https://javascript.info
...

서버는 Origin을 검사하고 요청을 받아들이기로 동의한 상태라면 특별한 헤더 Access-Control-Allow-Origin을 응답에 추가한다. 여기에 오리진 정보나 * 를 붙히면 응답에 성공하고 그렇지 않으면 실패하게 된다.

이때 브라우저는 중재인의 역할을 수행한다.
서버에서 크로스 오리진 요청을 수행한 경우, prefilght 요청에 응답은 다음과 같은 형태를 뛴다.

1
2
3
200 OK
Content-Type:text/html; charset=UTF-8
Access-Control-Allow-Origin: https://javascript.info

응답 헤더

크로스 오리진 요청이 이루어진 경우, 자바스크립트는 기본적으로 안전한 응답 헤더로 분류되는 헤더에만 접속할 수 있다.

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

이 외의 응답 헤더에 접근하면 에러가 발생한다.

위 리스트엔 Contnet-Length헤더는 없다.

안전하지 않는 응답 헤더에 접근하려면 서버에서 Access-Contorol-Expose-Headers라는 헤더를 보내줘야 한다. 여기에 자바스크립트 접근을 허용하는 안전하지 않은 헤더 목록이 담겨 있다.

1
2
3
4
5
6
200 OK
Content-Type:text/html; charset=UTF-8
Content-Length: 12345
API-Key: 2c9de507f2c54aa1
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Expose-Headers: Content-Length,API-Key

안전하지 않은 요청

요츰엔 GET, POST 뿐만 아니라 PATCH, DELETE 등 어떤 메서드도 사용할 수 있다.

과거에는 get 과 post 만 가능한 웹서버만 있었고 요즘도 종종 있다. 혼란스런 상황을 방지하기 위해서 안전하지 않은 요청( 위에서 안전한 요청에 조건을 벗어나는 요청) 을 보낼 때는 바로 요청을 보내지 않고 preflight 요청을 사전에 보낸다.

preflight 요청은 OPTIONS 메서드를 사용하고 두 헤더가 함께 들어가며, 본문은 비어있다.

  • Access-Control-Request-Method - 안전하지 않은 요청에 사용하는 메서드 정보
  • Access-Contorl-Request-Header - 안전하지 않은 요청에 사용하는 헤더 목록

안전하지 않은 요청을 허용하기로 협의했다면 서버는 본문이 비어있고 상태 코드가 200인 응답을 다음과 같은 헤더와 함께 브라우저로 보낸다.

  • Access-Contorl-Allow-Origin - *나 요청을 보낸 오리진
  • Access-Control-Allow-Method - 허용된 메서드 정보
  • Access-Control-Allow-Headers - 허용된 헤더 목록
  • Access-Control-Max-Age - 퍼미션 체크 여부를 몇 초간 캐싱해 놓을지를 명시. 이렇게 캐싱해 놓으면 브라우저는 일정 기간 동안 preflight 요청을 생략하고 안전하지 않은 요청을 보낼수 있다.

실제 PATCH 메서드를 사욭하는 예시를 통해서 확인해 본다.

1
2
3
4
5
6
7
let response = await fetch("https://site.com/service.json", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"API-Key": "secret",
},
});

위 요청은 안전한 요청에 조건을 벗어나므로 안전하지 않은 요청이다.

1 단계 priflight 요청

1
2
3
4
5
OPTIONS /service.json
Host: site.com
Origin: https://javascript.info
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type,API-Key

2 단계 priflight 응답

1
2
3
4
5
200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Methods: PUT,PATCH,DELETE
Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
Access-Control-Max-Age: 86400

3 단계 실제 요청

이제 부터는 안전한 요청에 대한 프로세스와 동일하다. 본 요청은 크로스 오리진 요청이기 때문에 Origin 헤더가 붙는다.

1
2
3
4
5
PATCH /service.json
Host: site.com
Content-Type: application/json
API-Key: secret
Origin: https://javascript.info

4단계 실제 응답

서버에서 본 응답에 Access-Contorl-Allow-Origin 헤더를 반드시 붙여야 한다.

1
Access-Control-Allow-Origin: https://javascript.info

preflight 요청은 자바스크립트를 사용해 관찰 할 수 없다. preflight 요청이 거부된 경우 에러만 확인할 수 있다.

자격 증명

크로스 오리진 요청은 기본적으로 자격 증명이 함께 전송되지 않는다. 이렇게 한 이유는 크로스 오리진 요청 시 자격 증명을 함께 전송할 수 있으면 사용자 동의 없이 자바스크립트로 민감한 정보에 접근할 수 있기 때문이다.

그럼에도 불구하고 자걱 증명이 담긴 헤더를 명시적으로 허용하겠다는 세팅을 서버에 해줄 수 있다.

fetch 메서드에 자격 정보를 함께 전송하려면 다음과 같이 credentials: "include" 옵션을 추가하면 된다.

1
2
3
fetch("http://another.com", {
credentials: "include",
});

서버에서 자격 증명 정보가 담긴 요청을 받아들이기로 했다면 응답에 Access-Control-Allow-Origin 헤더와 함께 Access-Conrol-Allow-Credentials: true 헤더를 추가해서 보낸다.

1
2
3
200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Credentials: true

이때, Access-Contorl-Allow-Origin에 *를 쓸 수 없다. 정확한 오리진 정보만 명시되어야 서버에서 신뢰할 수 있기 때문이다.

URL objects

URL 객체를 사용할 수 있다. 물론 string을 사용해도 충분 하지만 유요한 기능을 제공 하기도 한다.

Creating a URL

1
new URL(url, [base]);
  • url - full url 이나 베이스가 설정되어 있다면 패스 만
  • base - base가 설정 되어 있으면 base와 관련지어서 생성된다.

다음에 두가지 url은 같다.

1
2
let url1 = new URL("https://javascript.info/profile/admin");
let url2 = new URL("/profile/admin", "https://javascript.info");

url 객체를 사용하면 객체에 구성소에 바로 접근할 수 있다.

1
2
3
4
5
let url = new URL("https://javascript.info/url");

alert(url.protocol); // https:
alert(url.host); // javascript.info
alert(url.pathname); // /url

url에 구조는 다음과 같다.

네트워킹 메서드에 문자열 대신에 URL Object를 전달할 수 있다.
예를 들면 fetchXMLHttpRequest에서

SearchParams “?..”

파라미터는 만약 공백이 포함되어 있거나 라틴 글자가 아니면 인코딩이 필요하다.

글새 url.searchParams는 유용한 메서드를 제공한다.

  • append(name, value) - name에 해당하는 파라미터를 추가한다.
  • delete(name) - name 에 해당하는 파라미터를 제거한다.
  • get(name) - name 에 해당하는 파라미터를 얻는다.
  • getAll(name) - name 에 해당하는 파라미터를 모두 얻는다.(여러개일 경우)
  • has(name) - name에 해당하는 파리머터가 있는지 체크한다.
  • set(name, value) - set/relace 파리미터
  • sort() - 이름으로 정렬한다, 드물게 필요하다.

사용 예시는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let url = new URL("https://google.com/search");

url.searchParams.set("q", "test me!"); // added parameter with a space and !

alert(url); // https://google.com/search?q=test+me%21

url.searchParams.set("tbs", "qdr:y"); // added parameter with a colon :

// parameters are automatically encoded
alert(url); // https://google.com/search?q=test+me%21&tbs=qdr%3Ay

// iterate over search parameters (decoded)
for (let [name, value] of url.searchParams) {
alert(`${name}=${value}`); // q=test me!, then tbs=qdr:y
}

Encoding

url에 가능한 글자가 RFC3986에 정의 되어 있다. 제공되지 않는 문자열에 경우 인코딩이 필요하다.

다행인건, URL Object를 사용하면 자동으로 이 작업을 해준다.

1
2
3
4
let url = new URL("https://ru.wikipedia.org/wiki/Тест");

url.searchParams.set("key", "ъ");
alert(url); //https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D1%82?key=%D1%8A

Encoding String

URL Object가 아니라 문자열을 사용하는 경우 특수 문자를 수동으로 인코딩/ 디코딩 해야 한다.

몇가지 내장된 메소드는 다음과 같다.

  • encodeURI
  • decodeURI
  • encodeURIComponent - 파라미터나 헤쉬 값 같은 것
  • decodeURIComponent

encodeURIencodeURIComponent에 차이는 무엇일까?

예시 url

1
https://site.com:8080/path/page?p1=v1&p2=v2#hash

:, ?, =, &, #와 같은 문자가 url에서 허용된다. 파라미터로 사용되는 문자열에는 이런 문자열이 포함되어야 하는데 url에 허용되는 문자열이기 때문에 인코딩 되지 않는 문제가 있다.

따라서 이러한 문자는 형식을 깨드리지 않고 인코딩되어야 한다.

전체 URL은 encodeURI를 사용한다.

1
2
3
4
// using cyrillic characters in url path
let url = encodeURI("http://site.com/привет");

alert(url); // http://site.com/%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82

매개변수를 사용하는 경우 encodeURIComponent를 대신해서 사용해야 한다.

1
2
3
4
let music = encodeURIComponent("Rock&Roll");

let url = `https://google.com/search?q=${music}`;
alert(url); // https://google.com/search?q=Rock%26Roll

encodeURI와 비교하면 다음과 같다.

1
2
3
4
let music = encodeURI("Rock&Roll");

let url = `https://google.com/search?q=${music}`;
alert(url); // https://google.com/search?q=Rock&Roll

& 은 Rock 그리고 Roll 이라는 파라미터를 의미하고 이는 적절히 인코딩 되어야 한다. 그런데 encdoeURI를 사용하면 &를 허용하기 때문에 인코딩 되지 않는 문제가 있다.

git basic

  • 리눅스에 대해서 간략히 공부했다.
  • git의 대한 기본적인 설정방법과 사용법을 알아보고 리뷰어와 조를 이루어 풀리퀘스트를 요청화는 과정을 연습해 보았다.

Linux

리눅스는 unix 기반에 오픈소스 운영체제로서 수많은 배포 버전이 있다. 헬싱키 대학생이던 리누스 토발즈가 앤디 타넨 비우의 MINX를 개조한 Linux를 배포했다.

Kernel

커널은 하드웨어와 응용 프로그램을 이어주는 운영체제의 핵심 소프트 웨어이다.

Shell

쉘은 운영체제의 커널과 사용자를 이어주는 소프트웨어로서 사용자가 Kernel를 제어하기 위해 새용한다.

git object

git에는 3가지의 객체가 있다.

  1. Blob: 파일 하나의 대한 정보로서 사진으로 비유하자면 사진 파일 자체인 .jpg 파일이 될 수 있다.
  2. Tree: Blob 이나 subtree 의 메타데이터로서 사진으로 비유하자면 찍히 날짜에 대한 정보라던지, 저장된 위치 같은 정보가 될 수 있다.
  3. Commit: 커밋 순간의 스냅샷으로서 버전으로 저장하려고 압축한 파일들과 같다.

git 저장소

git repo 에는 크게 Local 저장소와 remote 저장소가 있다. 흔히 사용하는 github과 같은 서비스가 remote 저장소라고 할 수 있다.

Local 저장소는 크게 3가지 상태로 나눌 수 있다.

  1. Working Directory : 지금 작업하고 있는 폴더이다. git init을 통해 git repo를 만들거나 clone 받은 폴더가 될 수 있다.
  2. staging area : git add 를 통해 추가한 파일들에 상태를 말한다. 아직 커밋을 하기 전 상태이며 add를 해야지 git 에서 버전을 추적할 수 있다.
  3. localrepo : git commit을 수행해서 버전이 등록한 파일들에 상태이다. git push를 통해 remote 저장소로 파일을 올리거나 git pull을 통해 받아올 수 있다.

configure

깃을 사용하기전 환경 설정으로 해주면 좋을 것들을 알아본다.

1
2
3
4
5
$ git config --global user.name "당신의유저네임"
$ git config --global user.email "당신의메일주소"
$ git config --global core.editor "vim"
$ git config --global core.pager "cat"
$ git config --global init.defaultBranch "main"

commit 을 입력할 editor을 vim으로 설정하는 설정과 default branch를 main으로 바꾸는 설정이 포함되어 있다. 필수적인 사항은 아니다.

만약 환경변수 설정을 내리고 싶다면 --unset 플래그를 추가하면된다.

git config --global --unset core.editor

Commit Convention

commit을 한번에 알기쉽게 하기위해서 보편적으로 사용하는 규칙들이 있다.

  • feat: features (기능 개발에 대한 커밋)
  • docs: documentations (문서 작업에 대한 커밋)
  • conf: configurations (환경 변수 설정에 대한 커밋 ex)node pakage)
  • test: test (test 코드에 대한 수정 생성에 대한 커밋)
  • fix: bug-fix (버그를 고치거나 에러를 고친 커밋)
  • refactor: refactoring (같은 기능을 하나 보기 좋고 효율적인 코드로 대체했을때)
  • ci: Continuous Integration
  • build: Build
  • perf: Performance

이 외에도 제목은 50자 이내로 간결하게 작성하고 내용은 되도록 상세히 작성하도록 한다.

1
2
3
4
5
6
7
feat: add back button

add back button using <buuton> tag

TODO
- create page

작업의 단위

commit 작업의 단위는 어떻게 될까?

commit 을 하는 단위는 어떤 동작을 기준으로 한다고 보면된다. JS에서 예를 들자면 어떤 함수를 만들고 동작까지 확인했다면 그때를 commit 기점으로 생각하면된다.

작업 최소 단위로 생각하고 세세하게 작성하도록 한다.

staging area가 있는 이유?

만약 staging area 가 없다면 어떻게 될까?

우리는 모든 파일에 변경에 커밋을 한번에 날려야할 것이다. 만약 문서화 작업에대한 변경과 기능 추가에 대한 변경이 존재한다면 이를 한꺼번에 커밋해야 되는 것이다.

stging area 가 있기 때문에 하나의 파일을 올려두고 staging area 에 있는 파일을 commit 대상으로 삼을 수 있다.

pull request 실습

github repo > Settings > Mnage access > Invate a collaborator 를 통해 코드 리뷰어를 초대한다.

새로운 브렌치를 만들어 기능을 만든다.

1
2
3
4
5
$ git branch [new branch name]
$ git checkout [new branch name]
$ git add [수정한 파일]
$ git commit
$ git push -u origin [new branch name]

새로운 브랜치를 로컬에서 생성했다면 remote repo 에는 새로운 브랜치가 없을 것이다. -u 플래그를 통해 새로운 브랜치를 remote repo 에 생성하면서 push 할 수 있다.

github 저장소로 이동하여 pull request를 요청한다.

이제 코드 리뷰어가 코드를 보고 수정할 것을 요청할 것이다. 그럼 다시 로컬 저장소로 돌아와서 파일을 수정하고 push 하는 과정까지 진행하면된다.

코드리뷰어가 확인하고 문제가 없다 판단하면 승인을 해줄 것이다. 그럼 merge 를 클릭하여 remote repo 에 브랜치를 머지하면된다.

로컬 저장소에는 아직 머지 되지 않은 상태 이기 때문에 머지명령어를 입력한다.

1
2
3
$ git checkout main
$ git fecth origin main
$ git merge FETCH_HEAD

사실 git pull origin main 을 통해서도 동일한 과정을 진행할 수 있다. 내부적으로 보자면 git pull을 통한 머지가 위에 코드블럭에서 설명한 것을 똑같이 수행한다.

차이점은 fetch를 통해 이시적으로 받아올 수 있기 때문에 변경사항을 확인하면서 머지할 수 있다는 것이다. 복잡한 코드를 머지해야 할때나 다시한번 확인이 필요할 경우가 있을 수 있으니 fetch를 사용하도록 하자.

다음에 배울 것

branch 를 더 배울 예정이다.

풀 리퀘스트하는 과정을 더 연습해서 익숙해 지도록 해야 겠다.

branch

분기점을 생성하여 독립적으로 코드를 변경할 수 있도록 도와주는 모델

모든 작업을 시작할때에는 브랜치를 만들어서 작업 하기를 권장.

기능 구현이 완료 되었으면 해당 브랜치를 main 브렌치로 머지하고 해당 브랜치를 삭제 하는 것으로 작업을 하면 된다.


git flow

대표적인 branchg models 중에 하나이다.

git flow 는 branch 를

  • master
  • develop
  • hotfix
  • release
  • feature

로 나누어서 관리한다.

메인 스트림 브렌치는 master(main) 과 devlop 으로 나뉜다.

프로젝트를 생성하면 main 브렌치와 devlop 브렌치로 나뉘고 각각 팀원들은 devlop 브렌치에서 작업을 수행한다.

새로운 기능을 구현해야 할때에는 feature 브렌치를 만든다.

feature 브렌치에서 기능이 완료 된 후 develop 브렌치로 머지하고 feature 브렌치는 삭제한다.

기능이 어느정도 개발이 완료되서 새로운 버전을 출시해도 되겠다 싶으면 release 브렌치를 만든다.

release 브렌치에서는 어떤 기능을 개발해서는 안되고 버전을 올리기 전에 문서를 정리한더 던지 사용자에게 보이지 말하야 할 부분을 가린다던지에 기능 개발 외적인 작업을 수행한다.

작업이 완료되면 relesae를 끝내면 되는데 이때 작업한 내용은 각각 main 브렌치와 develop 브렌치로 병합된다.

hotfix 는 버그가 생겼다던지 빠른 시일내에 고쳐야 하는 긴급한 이슈가 생겼을때 만든다.

긴급히 수정한 이후에는 release 와 같이 main 과 develop 브렌치로 병합된다.

git flow command

홈 브루를 이용하여 git flow를 설치한다.

brew install git-flow-avh

초기화

깃 레포에서 실행.

git flow init

feature

새기능 브렌치 만들고 브렌치로 switch

git flow feature start MYFEATURE

기능 개발 완료

git flow feature finish MYFEATRUE

기능을 공동으로 개발하고 있을 때 (게시)

git flow feature publish MYFEATURE

게시된 기능 가져오기

git flow feature pull origin MYFEATURE

릴리즈

릴리즈 시작

git flow release start version

릴리즈 완료

git flow release finish version

릴리즈 완료후 테그에 대한 정보도 확인해야 한다.

테그가 있는지 확인하려면

git tag

tag 를 원격 저장소로 push 하려면

git push --tags

핫 픽스

핫 픽스 시작

git flow hitfix start version

핫 픽스 완료

git flow hotfix finish version


충돌, 되돌리기

충돌이 발생하면 충돌이 발생한 파일에 구분선을 통해서 어느 부분이 다른지 표시해 준다.

해당 파일을 열어서 몇가지 선택 하면 된다.

  1. 현재 로컬 코드로 만든다.
  2. 리모트 코드로 만든다.
  3. 로컬과 리모트 코드를 섞는다.

만약 파일에 이름을 변경 하고 싶다거나 이동하고 싶다면?

그냥 하게 되면 파일이 삭제되고 다시 생성하는것과 같게 된다.

따라서 그동안의 변경 이력이 다 날라가게 됨으로 git 명령어를 이용한다.

git mv ~

되돌리기

  1. reset
  2. revert

어느 한 시점만 삭제하는것은 불가능하다.

그 시점으로 돌아가서 다시 시작하는 것은 가능하다.

reset을 통해 되돌아 가면 되돌아간 내역들이 모두 삭제됨으로 권장하는 방법은 아니다.

1
git reset --hard HEAD~3

revert 를 통해서 돌아가기를 권장함.

1
2
3
git revert --no-commit HEAD~3.행

git commit

HEAD 에서 3개의 commit을 순서대로 올라가 해당 내역에 대한 commit, push 수행

git 기초

모르고 있었던 내용을 추가해서 정리한다.

1
2
3
4
5
6
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt

아직 추적하고 있지 않는 파일은 ?? 로 뜬다. README 파일은 아직 staged 상태가 안된 파일이여서 오른쪽에만 M이 붙는다. Rakefile 같은 경우는 수정 후 한번 add 했다가 다시 수정해서 왼쪽에 staged 된 변경사항이라는 정보와 오늘쪽에 staged 안된 변경 사항이라는 정보가 같이 적힌다(MM). staged 상태로 추가한 파일 중 새로 생성한 파일 앞에는 A 표시가 붙난다.

Staged와 Unstaged 상태의 변경 내용을 보기

git diff 명령을 수행하면 수정했지만 아직 staged 상태가 아닌 파일을 비교해 볼 수 있다.

만약 커밋하려고 Staging Area에 넣은 파일의 변경 부분을 보고 싶으면 git diff --staged 옵션을 사용한다.

commit 하기

git commit -v 옵션을 추가하면 편집기에 diff 메세지도 추가된다. 기본적으로넌 git status 명령의 결과가 채워진다.

파일 삭제하기

git rm 명령으로 Tracked 상태의 파일을 삭제한 후 (Staging Area에서 삭제 하는 것) 커밋을 실시. 그냥 제거 하게 되면 Unstaged 상태로 파일이 지워 졌다고 표시함.
반면에, git rm 명령어를 사용하면 파일이 지워졌다는 메세지와 함께 staged 된다(파일도 함께 지워짐). 만약 변경하고 Staging Area 에 추가했다면 -f 옵션을 주어 강제로 삭제 해야 한다.

Staging Area에서만 제거하고 워킹 디렉토리에 있는 파일은 지우지 않고 남겨두고 싶다면 --cached 옵션을 사용한다.

git rm -- cached README

파일 이름 변경하기

이름을 바꿀 때에는 git mv명령을 사용한다.
git mv file_from file_to

이것은 다음 세가지에 명령을 단축한 명령어이다.

1
2
3
$ mv README.md README
$ git rm README.md
$ git add README

커밋 히스토리 조회하기

git log

옵션

-p, --patch : 각 커밋의 diff 결과를 보여준다. 다른 유용한 옵션으로는 -2가 있는데 최근 두 개의 결과만 보여주는 옵션이다.

--stat : 각 커밋의 통계 정보를 조회할 수 있다.

--pretty : 히스토리 내용을 보여주는 형식, online, short, full, fuller, format --prety=format: "%h -%an, %ar: %s" 와 같은 방법으로 사용함.

--oneline : --pretty=oneline --abbrev-commit 두 옵션을 함께 사용한 것과 같다.

--graph : 브랜치와 머지 히스토리를 보여주는 마스키 그래프를 출력한다.

조회 제한 조건

--since--until 같은 시간을 기준으로 조회하는 옵션도 있다.

--author 옵션이나 --grep 옵션도 있는데 각각 저자 , 커밋메세지 키워드 기준으로 검색할 수 있다. 두 가지 모두를 만족하는 커밋을 찾으려면 --all-match옵션도 반드시 함깨 사용해야 한다.

-S 코드에서 제거되거나 추가된 내용 중에 특정 텍스트가 포함되어 있는지를 검색한다.

되돌리기

git을 사용하면 우리가 저지른 실수는 대부분 복구할 수 있지만 되돌린 것은 복구할 수 없다.
만약 커밋 메세지를 되돌리고 싶다면 파일을 수정하고 staging area에 추가한 다음 --amend 명령어를 추가해서 수정하면된다.

1
$ git commit --amend

만약 파일 수정 내용 없이 해당 내용을 입력하면 마지막 커밋 메세지만 수정된다. 명령어를 입력하면 편집기가 열릴때 전에 커밋한 내용이 포함된체로 열린다.

1
2
3
$ git commit -m 'initail commit'
$ git add forgotten_file
$ git commit --amend

첫번째 커밋은 무시 된다.

파일 상태를 Unstage로 변경하기

실수로 git add . 같은 명령어를 수행했는데 내려야 한다면 어떻게 해야 할까?

git reset HEAD <file> 명령을 수행하면 된다. 만약 잘못 Staging area 에 올린 파일이 README.md 파일이였다면 git reset HEAD README.me 이렇게 입력하면 된다.

사실 git reset 명령어는 매우 위험하다. 특히 --hard 옵션과 같이 사용할때는 더더욱 그렇다. 하지만 위에서 처럼 옵션 없이 사용하면 워킹 디렉토리의 파일은 건드리지 않는다.

Modified 파일 되돌리기

Staging area 에 올리지는 않았는데, modifed 된 상태라면 어떻게 전 상태로 돌릴 수 있을까?

git checkout -- <file>을 입력하면 된다. 정상적으로 복원된다.

git checkout - [file]은 꽤 위험한 명령이다. 원래 파일을 덮어 쓰우기 때문에 수정한 내용이 모두 사라진다. 정말 수정한 내용이 필요 없을때만 사용해야 한다.

리모트 관련 명령어

git remote -v 리모트 저장소에 대한 정보를 표시해 줌.

git remote add <저장소 별칭> <url> 저장소 별칭으로 url에 해당하는 저장소를 remote 저장소로 추가한다.

git remote remove <저장소 별칭> remote 저장소를 제거한다.

git remote rename <이전> <이후> 이전 저장소에 해당하는 remote 저장소에 이름을 이후 저장소로 바꿈

git remote show <저장소 별칭> 저장소 별칭에 해당하는 다양한 정보를 출력해 준다.

tag

git tag tag 목록을 확인할 수 있다.

git tag -l 와일드 카드로 검색하려 한다면 필수적인 옵션으로 -l을 사용해야 한다.

git tag -a <tagname> annotated tag를 추가한다. 명령어를 입력하면 편집기가 생성되서 메세지를 적을 수 있다.

git show <tagname> tag에 대한 정보를 확인할 수 있다.

lightweight tag, annotated tag

git 에 태그에는 두 종류가 있다. lightweigth tag는 단순히 커밋에 포인터와 같고, annotated tag는 git 저장소에 지정된 메세지 서명정보 등이 포함된다. 일반적으로 annotated tag를 사용한다.

lightweight tag

git tag <버전> 옵션을 사용하지 않고 추가하면 lightweight tag 로 추가 할 수 있다.

나중에 테그하기

git tag -a <체크섬> 특정 체크섬으로 tag 를 생성할 수 있다.

테그 공유하기

git push 명령으로 remote 저장소에 tag 정보까지 올라가지는 않는다.

git push orgin <tagname>으로 remote 저장소에 올린다.

git push origin --tags 를 이용하여 저장소에 내에서 태그 내용 중에서 remote 저장소에 없는 태그를 저장소로 올린다.

lightweight tag 만 저장소로 올리는 방법은 없다. 무조건 annotated tag 와 같이 저장소로 올라간다. 다만 annotated tag 만 저장소로 올리고 싶다면 git push <remote> --folww-tags 를 사용하면 된다.

태그 삭제하기

git tag -d <tagname>을 통해서 특정 태그를 삭제할 수 있다. 이는 작업하고 있는 저장소에 tag 만 삭제하는 것이여서 remote 서버에 push 해야 한다.

두가지 방법 중 하나를 선택한다.

  1. git push <remote> :refs/tags/<tagname> 올리기 전에 tag를 null 로 설정해서 비워 주는것과 같다.
  2. git push origin --delete <tagname>

특정 버전으로 checkout

git checktou <tagname>으로 특정 tag로 checkout 할 수 있다. 이렇게 checktout 하면 detached HEAD 상태가 된다. 이상태에서 작업한 내용을 커밋해도 도달할수 없는 상태가 된다.

따라서 버그를 고친다 같은 작업을 수행할 때는 특정 브랜치를 만들어서 작업해야 한다. 이렇게 작업해도 tag 에서 작업한 내용과 커밋한 내용간의 차이가 존재함에 주의해야 한다.

alias 사용하기

git config --global alias.ci commit 이렇게 사용하면 commit 에 대한 별칭을 사요할 수 있다.

git ci 로 사용한다.

외부 명령어를 사용할 때는 !를 붙힌다.

git config --global alias.visual '!gitk'

git visual를 입력하면 gitk가 실행된다.