[TIL] 22.10.25

nana·2022년 10월 25일
0

TIL

목록 보기
15/50
post-thumbnail

오늘 배운 것 - Javascript


1. Geo loaction

2. API

3. HTTP

4. 동기, 비동기

5. call stack, callback queue

6. Promise 객체

7. then(), catch()

8. 구조분해할당

9. spread 연산자

10. 얕은 복사, 깊은 복사

11. Rest Parameter




1. geo location

getCurrentPosition() 메서드를 호출해서 사용자의 현재 위치 정보를 가져올 수 있다.

지정한 콜백 함수를 호출하고, 과정 중 오류가 발생하면 호출할 오류 콜백을 두 번째 매개변수로 지정할 수도 있다.

위도, 경도 데이터를 가져와 사용하고 싶은 경우,

navigator.geolocation.getCurrentPosition( 지정한 콜백함수, (err) => {
  
});

지정한 콜백함수에 위도, 경도 데이터를 가져오는 함수를 넣어주고, 두 번째 매개변수로 err를 넣어준다.
이때, err는 웹 사이트에서 사용자가 위치 사용에 접근 거부할 경우 실행된다.


2. API

어떠한 프로그램에서 제공하는 기능을 사용자가 활용할 수 있도록 만들어 둔 인터페이스이다.

api 명세서 (요청할 수 있는 api의 목록) -> 요청 (request) -> API -> 응답(response)

API는 전달해주는 역할이다. 서버에게 요청을 전달하고, 서버는 필요한 데이터를 만들어 사용자에게 전달한다.

사용자가 요청하는 것을 request, 사용자가 전달받는 방식을 response라 한다.

날씨정보 api를 가져올 수 있는 사이트
https://openweathermap.org/


3. HTTP (Hypertext Transfer Protocol)

API와 같이 다른 서버와 통신을 할 때는 특정한 규칙에 맞춰 그 통신이 이루어져야 한다.
간단하게, HTTP는 서로 다른 서버 간에 문자 형식으로 데이터를 주고 받을 때 지켜야 정의된 규약(통신 규약)이다.

우리가 HTTP 통신을 할때는 몇가지의 단계를 거치게 된다.

1) Request message

가장 윗줄의 GET /data/2.5/weather? HTTP/1.1에 해당하는 부분은 start-line이라 부른다.
우리가 사용한 HTTP Method, 요청 URI, HTTP의 버전을 담고 있다.

  • 요청 헤더
    그 아래의 Host부터가 요청 헤더에 속한다.
    Request header에는 다양한 정보가 담기게 되는데, 요청을 받는 서버의 이름, 서버의 버전, 전달하는 컨텐츠의 타입, 요청 날짜, 요청을 보낸 컴퓨터의 정보 등 수많은 내용이 담긴다.

그리고 Request header의 내용이 모두 종료되면 하나의 빈 줄로 Request body와 구분을 해준다.

  • 요청 바디
    Request body에는 우리가 서버로 혹은 다른 사용자가 우리의 서버로 전달하고자 하는 컨텐츠를 담는다. 이때, 어떠한 메서드를 통한 요청인지에 따라 Request body를 담을 수 있는지, 없는지가 결정된다.

2) Response message

HTTP/1.1 200 OK에 해당하는 부분은 status-line이라고 부른다.
HTTP 버전과 상태 코드(Status code), 응답 메세지를 담고 있다.

  • 응답 헤더
    두번째 줄부터가 Response header에 속한다.
    Response header도 Request header와 마찬가지로 응답 날짜, 응답을 전달한 서버의 이름, 서버의 버전, 컨텐츠의 타입 등을 담고 있다.
    access-control-allow-origin: * 모든 사이트 접근을 허용한다
    credentials: 쿠키 허용 여부

Response header 이후에 나타나는 빈 줄을 하나 거치고 나면 Response body가 나타난다.

  • 응답 바디
    Response body에는 실제로 응답 리소스 데이터가 담겨져 있다.

HTTP Request 메서드

  • GET : 서버의 데이터를 조회한다. HTTP message에 요청 바디를 담아줄 수 없다.
  • POST : 서버에 데이터를 등록한다. (회원가입, 게시글 등록 등)
  • PUT : 서버 내 데이터를 수정한다.
  • PATCH : 데이터를 일부 수정한다.
  • DELETE : 서버의 데이터를 삭제한다.
  • OPTIONS: 서버가 허용하는 메서드를 확인한다.

4. 동기, 비동기

  • 동기 : 서버에 작업 요청을 보내면, 그 작업의 응답을 받아야 다음 동작을 수행할 수 있다.

  • 비동기 : 서버에 작업 요청을 보냈을 때, 응답 여부에 관계 없이 동시에 다음 동작을 수행할 수 있다.


자바스크립트의 동작방식

자바스크립트는 동기적으로 동작하지만, 비동기 처리도 가능하다.

console.log("1")

setTimeout(() => {
    console.log("2")
}, 2000)

console.log("3")

위의 코드를 실행해 보면, 1과 3이 먼저 출력되고 2초 후에 2가 출력된다.

분명 자바스크립트는 싱글 스레드이기 때문에 한번에 하나의 작업만 가능한데, 어떻게 setTimeout 함수의 실행을 건너뛰어서 3이 먼저 출력될 수 있을까?

이에 대해 알기 위해서는 JavaScript의 실행 환경을 살펴봐야 한다.


5. call stack, callback queue

📌 stack, queue는 자료 구조이다.
stack : LIFO (Last In First Out). 나중에 들어온 데이터가 먼저 실행된다.
queue : FIFO (First In First Out). 먼저 들어온 데이터가 먼저 실행된다.

1) call stack

함수 실행 순서가 담겨지는 공간이다.

예를 들어 중첩함수가 있다면, call stack 쌓여 있다가 나중에 들어온 함수부터 실행된다.

2) callback queue

기본적인 함수들은 모두 call stack에 쌓이게 된다.
그리고 비동기 함수(Web APIs 포함)들은 모두 callback queue에 쌓이게 된다.

call stack에 쌓인 기본 함수들은 먼저 담긴 함수들이 위에 쌓인 함수들의 종료를 기다린다.

callback queue에 쌓인 비동기 함수들은 call stack이 비워져 있다면, 그 때 call stack으로 하나씩 옮겨지게 된다.

위와 같은 예시에서, setTimeout 함수는 비동기 함수이기때문에 지연 시간을 지정해주지 않았음에도 불구하고 callback queue 영역으로 옮겨진다.

call stack 영역에 쌓인 함수는 LIFO 방식으로 수행되기 때문에, func3, func2, func1 순서로 함수가 실행될 것이다.

그리고 해당 함수들이 모두 완료되면, 그 후에 callback queue에 담긴 setTimeout 함수가 call stack으로 옮겨져 실행된다.


6. Promise 객체

자바스크립트는 싱글 스레드이기 때문에 한번에 하나의 동작만 수행할 수 있다. (동기적으로 동작한다.)

이러한 한계점을 해결하고 비동기 작업을 수행하기 위해 callback 함수를 활용하는 방법이 있었으나, callback 함수가 여러번 중첩되는 경우 가독성에서 심각한 손해를 보게 되는 콜백 지옥(callback hell)을 만나게 되는 문제점이 있었다. 각 함수마다 정상적인 동작에 대한 처리, 에러에 대한 처리가 필요했기 때문에 그 코드의 양은 더욱 많아질 수 밖에 없게 된다.

이러한 문제점을 해결하기 위해 Promise 의 개념이 나타났다.

Promise 객체는 비동기 동작의 결과로 받아올 수 있는 객체이다.

Promise 객체는 현재는 얻을 수 없지만, 추후 작업이 완료되면 받아올 수 있는 데이터에 대한 접근 수단의 역할을 해준다. 아직은 해당 동작의 결과를 받지 못했지만 언젠간 돌려줄 것이라는 약속이 담긴 객체인 것이다. 그 응답이 정상적인 응답일지, 에러를 돌려줄지는 알 수 없다.

Promise 객체는 new Promise() 로 직접 생성이 가능하다.
그리고 Promise 객체는 총 세가지의 상태를 가지고 있다.

promise의 3가지 상태

  1. fulfilled : 비동기 처리가 완료되어 결과값을 반환해준 상태
  2. pending : 비동기 처리가 아직 완료되지 않아 요청에 대한 응답을 기다리고 있는 상태
  3. rejected : 비동기 처리가 실패 혹은 오류가 발생한 상태
const promiseTest = function () {
  return new Promise((resolver, reject) => {
    setTimeout(() => {
      resolver("success"); // fulfilled. 요청이 성공했을 때 resolver를 사용한다.
      //   reject("error"); rejected. 요청이 실패했을 때
    }, 5000);
  });
};

promiseTest(); 	// pending

promiseTest().then((res) => { 
  // pending상태 -> then 메서드를 이용하여 매개 변수를 받아오면 요청이 성공한다.
  console.log(res);
});

위 코드는 promiseTest라는 함수를 실행 했을 때, 그 반환 값으로 Promise 객체를 돌려받는 코드이다.

Promise 객체를 생성할 때는 내부에 함수를 인자로 넣어줄 수 있는데, 이때 그 내부 함수는 resolver, reject를 매개변수로 받아올 수 있다.

비동기 처리가 완료되면(Fulfilled), resolver가 호출되고,
비동기 처리에 실패하면(Rejected), reject가 호출된다.

그리고 위 함수를 보면 resolver의 실행을 5초간 지연시키고 있는데, 때문에 해당 Promise 객체를 돌려 받은 뒤 5초가 지나기 이전에 참조하려 하면 Pending 상태를 돌려받게 된다.
아직 resolver가 호출되지 않았기 때문이다.

이를 해결하기 위해 then 메서드를 사용할 수 있다.


7. then(), catch()

통신을 통해 응답을 받아오는 과정은 코드가 실행되는 과정보다 느린 경우가 대다수이다.
때문에 통신 또한 비동기 처리가 필요하다.

const communicationResult = fetch(HTTP Request)
console.log(communicationResult)

위 코드처럼 어떠한 서버로 통신을 요청한 경우 그 결과를 바로 받아와 콘솔로그로 출력해보면, Promise 객체가 담겨져 있다.

Promise 객체가 반환되는 이유는 코드의 실행이 통신의 결과를 받는 것보다 빠르기 때문이다. 그렇기 때문에 우리는 통신의 결과를 특정 로직에 적용시키고 싶다면, 그 결과를 받아올 때까지 기다려주어야 한다.

비동기 통신을 처리해주는 방법은 여러가지가 있는데, then(), catch() 메서드를 사용해서 처리할 수 있다.


1) then()

fetch() 를 사용해주면 JavaScript에서 바로 통신을 수행할 수 있다.

const communicationResult = fetch(HTTP Request)
console.log(communicationResult)

하지만 위와 같이 작성하면 정상적인 응답을 받지 못한다는 것을 확인할 수 있다.
그 응답이 돌아올 때까지 기다려주어야 하는데, 이때 then()메서드를 사용할 수 있다.

fetch(HTTP Request).then()

fetch()를 사용한 후에 그 응답을 받아올 때까지 기다리도록 fetch()에 붙여서 then()을 사용해주면 된다.
then() 메서드는 fetch()뿐만 아니라 Promise 객체를 돌려주는 함수라면 언제든 사용할 수 있다.
해당 요청에 의한 통신이 완료 되었다면, then() 메서드 내부로 코드의 실행이 옮겨진다.

fetch(HTTP Request)
	.then((res) => {
		console.log(res)
	})

then() 메서드 내부에 익명 함수를 하나 만들고 그 안에서 매개변수로 데이터를 받을 수 있는데,
이때 받아오는 데이터가 바로 통신의 결과로 우리에게 돌려준 데이터이다.
그 응답을 받아올 때까지 기다려서 정상적인 값을 받아올 수 있는 것이다.


2) catch()

만약 통신을 수행하다가 통신에서 문제가 생긴다면, 혹은 then() 메서드 내에서 로직을 수행하다가 에러를 만난다면 catch() 메서드로 그 분기를 나눠줄 수 있다.

fetch(HTTP Request)
	.then((res) => {
		console.log(res)
	})
	.catch((err) => {
		console.error(err)
	})

이런식으로 then() 메서드 뒤에 붙여서 작성해주면 된다.
그리고 catch() 메서드 내부에도 익명 함수를 넣어줄 수 있는데, 해당 함수의 매개변수로 error의 내용을 받아올 수도 있다.


8. 구조분해할당

배열과 객체의 값을 각각의 변수에 담아 관리하고 싶다면, 즉, 값을 복사해주고 싶다면 어떻게 할 수 있을까?

각각의 값을 따로 담아서 관리해줄 수 있지만, 관리할 데이터가 여러개인 경우 각각의 데이터를 모두 변수로 선언해 주어야 하기 때문에 굉장히 비효율적이다.

이런 경우 구조분해할당을 사용할 수 있다.

구조분해할당이란, 구조화 되어 있는 배열 / 객체와 같은 데이터를 분해하여 각각의 변수에 다시 할당하는 것이다.

1) 배열의 구조분해할당

const arr = [ 1, 2, 3, 4, 5 ];

let one = arr[0];
let two = arr[1];

위의 코드를 구조분해할당 하면,

const arr = [ 1, 2, 3, 4, 5 ];

let [ one, two ] = arr; 	// 배열의 구조분해할당
console.log(one, two) 	// 1, 2

선언 키워드를 적은 후, 대괄호를 적어준다. 대괄호 안의 문자는 변수명으로 사용된다.

이때, 변수명으로 사용하고자 하는 문자들은 쉼표(,)로 구분 해주고, 그 개수에 따라 주어진 배열의 앞에서부터 차례대로 각각의 변수에 담기게 되기 때문에 순서가 중요하다.

그 뒤에는 할당 연산자, 구조분해할당 해주고 싶은 배열을 입력해 준다.

2) 객체의 구조분해할당

구조분해할당은 iterable 데이터(순회가 가능한 데이터)만 가능하다.
객체는 iterable하지 않지만, key값 을 이용해서 구조분해할당을 할 수 있다.

const obj = {
	name: "otter",
	gender: "male"
}

let { name, gender } = obj;
console.log(name, gender) // otter, male

객체는 중괄호를 사용한다.
위의 예시와 같이 객체의 구조분해할당은 key값이 변수로써 동작하고, 변수에 value값들이 담기게 된다.
따라서 존재하지 않는 key를 입력하면 구조분해할당이 제대로 되지 않는다.

let { name: a, gender: b } = obj

만약 해당 데이터를 실제로 존재하는 key가 아닌 별도의 변수명으로 관리하고 싶다면,
위와 같이 작성하여 각 key에 대응하는 value들이 a와 b라는 변수에 담기게 하면 된다.


9. spread 연산자

하나로 뭉쳐있는 값들의 집합을 전개해주는 연산자이다.
전개해주고 싶은 배열의 앞에 마침표를 세개 적어주면 된다.

const arr = [ 1, 2, 3, 4, 5 ];
console.log(...arr) 	// 1, 2, 3, 4, 5

이처럼 배열의 대괄호를 벗어나 숫자 요소가 각각 출력된다.

let str = "Hello"
console.log(...str) // "H" "e" "l" "l" "o"

문자열 역시 가능하다.


참조 타입 데이터의 복사

const arr = [ 1, 2, 3, 4, 5 ];
const newArr = [ ...arr ];

arr이라는 배열을 spread 연산자를 사용해서 펼쳐준 뒤에 다시 새로운 대괄호 안에 담아주었다.
이렇게 해주면 기존에 arr 배열이 가지고 있던 주소값과 전혀 별개의 새로운 배열이 newArr이라는 변수에 담기게 된다. 참조 타입의 값의 복사를 실행한 것이다.

const obj =  { name: "otter", gender: "male" }
const copyObj = {...obj}	// 새로운 객체 생성

copyObj.gender = 'female'

console.log(copyObj) // { name: "otter", gender: "female" }
console.log(obj) // { name: "otter", gender: "male" }

객체 역시 spread 연산자를 그냥 사용하게 되면 에러가 발생한다.
spread연산자를 사용하여 펼쳐준 뒤에 다시 새로운 중괄호 안에 담아 새로운 객체를 만들어준다.


10. 얕은 복사, 깊은 복사

얕은 복사는 주소값까지만 복사하고, 깊은 복사는 실제 데이터까지 복사한다.

위에서 설명한 바와 같이, spread 연산자를 사용하여 배열이나 객체 값의 복사를 수행할 수 있었다.

그러나, 이 방법 역시 참조 타입의 완전한 복사를 수행한것은 아니다.

const obj = {
	name: "otter",
	gender: "male",
  favoriteFood: {
		first: "sushi",
		second: "hamburger"
  }
}

const newObj = { ...obj }

위 객체는 객체 안에 favoriteFood라고 하는 또 다른 객체가 존재하는 중첩 객체이다.

obj 객체의 name, gender와 내부 객체 favoriteFood의 property를 변경한 후
복사본인 newObj를 살펴보면,

obj.name = "rabbit"
obj.gender = "female"
obj.favoriteFood.first = "cold noodle"

console.log(newObj)

/*
	{
		name: "otter",
		gender: "male",
	  favoriteFood: {
			first: "cold noodle",
			second: "hamburger"
	  }
	}
*/

name과 gender는 영향을 받지 않았는데, 내부 객체 favoriteFood는 여전히 영향을 받고 있는 것을 확인할 수 있다. 중첩 객체는 제대로 복사가 되지 않았기 때문이다.

이는 우리가 spread 연산자를 사용해서 해준 복사가 얕은 복사이기 때문이다.

1) 얕은 복사

spread 연산자를 사용하여 간단하게 얕은 복사를 할 수 있다.

let arr = [1,2,3,4,5]
let arr2 = [6,7,8]

let copy = [...arr, ...arr2]
console.log(copy) 	// 1,2,3,4,5,6,7,8

그러나 spread 연산자를 사용하여 복사를 하게되면 얕은 복사이기 때문에, 객체 안에 존재하는 객체의 값을 변경할 경우 원본 배열에도 영향을 끼치게 된다.

const obj = {
	name: "otter"
}
obj.name = "rabbit"

console.log(obj.name) 	// rabbit

const 키워드로 선언된 obj(안에 담긴 주소값)은 변환되지 않았다.
heap메모리에 존재하는 객체의 값을 변환시킨 것이다.


2) 깊은 복사

자바스크립트의 실행 환경은 call stack, callback queue 외에도 heap이라는 영역이 존재한다.

참조 타입의 데이터는 heap 이라는 임시 저장 메모리에 담긴다.

그리고 이 heap 영역은 참조 타입 데이터와 같이 그 데이터의 크기가 유동적으로 변할 수 있다는 특징을 가지고 있다.

그리고 중첩 객체를 스프레드 연산자로 복사했을 때의 문제점도 여기에서 찾아볼 수 있다.

스프레드 연산자를 사용해서 객체의 중괄호를 한번 벗겨냈을 때, 다른 property들은 모두 주소값의 연결이 끊어진채로 새로운 주소를 가진 데이터로 완전히 복사가 되었지만, 이때도 그 내부에 존재하는 favoriteFood 객체는 펼쳐지지 못한다.

그래서 원본 객체의 favoriteFood와 복사된 객체의 favoriteFood는 여전히 같은 주소값을 공유하고 있다.

이러한 문제를 해결하기위해서 JSON.stringify()와 JSON.parse()를 사용할 수 있다.

JSON.stringify() 는 소괄호 안에 들어가는 값을 JSON 데이터 포맷, 즉 문자열로 변환해준다.

const obj = {
	name: "otter",
	gender: "male",
  favoriteFood: {
		first: "sushi",
		second: "hamburger"
  }
}

const copy = JSON.stringify(obj)	// 문자열 변환

console.log(copy)
// {"name":"otter","gender":"male","favoriteFood":{"first":"sushi","second":"hamburger"}}

위의 예시처럼 문자열로 변환이 된다면, copy에 담긴 값은 더이상 객체가 아니며 완전히 새로운 문자열이기 때문에 전혀 새로운 주소값을 가지게 된다.

const deepCopy = JSON.parse(copy)	// 원본 데이터로 변환

console.log(deepCopy)

/*
	{
		name: "otter",
		gender: "male",
	  favoriteFood: {
			first: "sushi",
			second: "hamburger"
	  }
	}
*/

이후, JSON.parse() 를 사용해서 JSON 데이터 포맷을 다시 객체 형태로 바꿔주면, 이때 새로운 객체가 생성되며 deepCopy에 담기는 것이기 때문에 원본 객체와는 전혀 다른 주소값을 가진 객체가 생성된다.

이러한 복사를 깊은 복사라고 한다.


11. Rest Parameter

let origin = {
	name: "otter",
    age: 25,
    petName: "cherry",
    hobby: "playing game"
}

const { petName, hobby, ...rest} = origin

console.log(rest)	// {name: "otter", age: 25}

객체에서 필요한 데이터 / 필요없는 데이터를 각각의 변수로 담아서 사용하고 싶을때 rest parameter를 이용할 수 있다.



오늘의 회고

드디어 자바스크립트 강의 마지막 날이다. 오늘 배운 내용이 너무 많은데 복습하려면 정리를 잘 해놔야겠다.
그동안 강의를 들으면서 이게 뭔데 했던 부분들에 대해 좀 더 자세히 알 수 있어서 좋았다.
특히 얕은 복사와 깊은 복사의 원리를 이해하고 나니, 객체와 배열에 대해서도 좀 더 이해할 수 있었다.

API와 같이 통신을 통해 응답을 받아오는 과정은 코드가 실행되는 과정보다 느려서, 통신 또한 비동기 처리가 필요하다는 사실을 알 수 있었다. fetch시 then과 catch를 사용하여 처리해주는 것을 기억해야겠다.

그리고 자바스크립트는 동기적으로 작동하지만, 어떻게 해서 비동기적인 처리가 가능한지도 상세하게 알 수 있어서 도움이 많이 되었다. 그 과정에서 stack과 queue와 같은 자료구조에 대해서도 간단하게 짚고 넘어갔는데, 앞으로 알고리즘 수업을 들을때 도움이 될 것 같다.

실습으로 진행한 Todo 리스트 만들기 코드는 너무 길어져서 그냥 github에만 올려야겠다.
주석을 참고해서 다시 복습하자!

profile
프론트엔드 개발자 도전기

0개의 댓글