1025 - 1031
1025
서버사이드 렌더링으로 다시 이동하는 이유
-> 속도
고차함수가 콜백함수를 호출하면 동기 함수
고차함수가 콜백함수를 호출하지 않으면 비동기 함수
서버 통신은 비동기
아래 3개의 이유때문에 서버 통신은 비동기로 동작해야한다.
- 랜선을 타고 통신 해야한다.
- 서버에서 로직
- DB접근. DB는 파일로 저장되기 때문에 느리다. 비동기의 핵심 -> 블로킹
빌트인 객체는 힙 영역에 있고, Web API는 브라우저에 객체로 존재하지 JS 엔진 내부에 있지 않다.
비동기 함수란?
함수 내부에 비동기 처리를 하면 비동기 함수
옵저버 패턴
상태가 변했을 때 -> render를 호출해야 할 타이밍
문제는 상태가 변했을 때 render함수에 의존한다는 것
상태가 변경했을 때 컨트롤러에서 render를 호출해주면 문제를 해결할 수 있다.
옵저버 패턴을 사용하면 store에서 직접적으로 render에 의존 하지는 않게 되지만 컨트롤러에서 render를 호출해줘야 한다는 코딩 약속을 지켜야하기에 가독성이 떨어진다.
프로그래머스 Level2 카카오 - 압축
function solution(msg) {
const answer = [];
const dic = " ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
let lt = 0;
for (let rt = 1; rt <= msg.length; rt++) {
if (dic.indexOf(msg.slice(lt, rt)) === -1) {
dic.push(msg.slice(lt, rt));
answer.push(dic.indexOf(msg.slice(lt, rt - 1)));
lt = rt - 1;
}
}
return [...answer, dic.indexOf(msg.slice(lt))];
}
- A부터 Z까지 문자열을 생성하고 앞에 빈 칸을 두어 split으로 사전 배열을 생성했다. 앞에 빈 칸을 둔 이유는 인덱스를 하나씩 늘려서 문제 조건에 맞게 조회하기 위해서이다.
- 투포인터 개념을 사용했는데, lt부터 rt까지 잘라낸 문자열이 사전에 없을 때까지 rt를 증가시켜 사전에 push하고 answer에는 rt-1까지의 값을 푸시했다.
- 그리고 lt에 rt - 1을 할당하고 계속 진행하였다.
프로그래머스 Level2 카카오 - 수식 최대화
function solution(expression) {
let answer = 0;
const prior = [];
const arr = expression.match(/[0-9]+|./g);
const sh = [...new Set(expression.match(/[^0-9]/g))];
const ch = Array.from(Array(sh.length), () => 0);
const tmp = [];
function DFS(L) {
if (L === sh.length) {
prior.push(tmp.join(""));
} else {
for (let i = 0; i < sh.length; i++) {
if (ch[i] === 0) {
ch[i] = 1;
tmp.push(sh[i]);
DFS(L + 1);
tmp.pop();
ch[i] = 0;
}
}
}
}
DFS(0);
for (const p of prior) {
let express = [...arr];
for (const op of p) {
let tmp = [];
for (let i = 0; i < express.length; i++) {
if (express[i] === op) {
tmp.push(eval(tmp.pop() + op + express[i + 1]));
i++;
} else {
tmp.push(express[i]);
}
}
express = tmp;
}
answer = Math.max(answer, Math.abs(express[0]));
}
return answer;
}
- 생각보다 어려운 문제라고 느낀 구현 문제였다.
- 푸는 것 자체는 어렵지 않았는데, 구현이 생각보다 어려웠다.
- DFS로 연산자 조합을 모두 구하고, 해당 연산자 조합으로 반복문을 돌려서 절대값이 가장 큰 수를 구했다.
1026
45장 프로미스
자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다.
전통적인 콜백 패턴은 콜백 헬로 인해 가독성이 나쁘고 비동기 처리 중 발생한 에러의 처리가 곤란하며 여러 개의 비동기 처리를 한 번에 처리하는 데도 한계가 있다.
-> 프로미스는 비동기 처리를 위한 또 다른 패턴이다. 콜백 패턴이 가진 단점을 보완하며 비동기 처리 시점을 명확하게 표현할 수 있다는 장점이 있다.
비동기 처리를 위한 콜백 패턴의 단점
콜백 헬
- 비동기 함수를 호출하면 함수 내부의 비동기로 동작하는 코드가 완료되지 않았다 해도 기다리지 않고 즉시 종료된다.
- 즉, 비동기 함수 내부의 비동기로 동작하는 코드는 비동기 함수가 종료된 이후에 완료된다.
-
따라서 비동기 함수 내부의 비동기로 동작하는 코드에서 처리 결과를 외부로 반환하거나 상위 스코프의 변수에 할당하면 기대한 대로 동작하지 않는다.
- 서버로부터 응답이 도착하면 xhr 객체에서 load 이벤트가 발생한다.
- 이때 xhr.onload 핸들러 프로퍼티에 바인딩한 이벤트 핸들러가 즉시 실행하는 것이 아니라 load 이벤트가 발생하면 일단 태스크 큐에 저장되어 대기하다가, 콜 스택이 비면 이벤트 루프에 의해 콜 스택으로 푸시되어 실행된다.
- 이처럼 비동기 함수는 비동기 처리 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수도 없다.
- 따라서 바동기 함수의 처리 결과에 대한 후속 처리는 비동기 함수 내부에서 수행해야 한다.
- 이때 비동기 함수를 범용적으로 사용하기 위해 처리 결과에 대한 후속 처리를 수행하는 콜백 함수를 전달하는 것이 일반적이다.
- 콜백 함수를 통해 비동기 처리 결과에 대한 후속 처리를 수행하는 비동기 함수가 비동기 처리 결과를 가지고 또다시 비동기 함수를 호출해야 한다면 콜백 함수 호출이 중첩되어 복잡도가 높아지는 현상이 발생한다. -> 콜백 헬
에러 처리의 한계
비동기 처리를 위한 콜백 패턴의 문제점 중에서 가장 심각한 것은 에러 처리가 곤란하다는 것이다.
try {
setTimeout(() => {
throw new Error("Error!");
}, 1000);
} catch (e) {
console.error("캐치한 에러", e);
}
- 위의 예제에서 콜백함수는 에러를 발생시키지만 catch 코드 블록에서 캐치되지 않는다.
- setTimeout의 콜백 함수가 실행될 때 setTimeout 함수는 이미 콜 스택에서 제거된 상태다.
- 이것은 setTimeout의 콜백 함수의 호출자(caller)가 setTimeout 함수가 아니라는 것을 의미한다.
- 에러는 호출자 방향으로 전파된다. 즉, 콜 스택의 아래 방향으로 전파된다.
- 따라서 setTimeout 함수의 콜백 함수가 발생시킨 에러는 catch 블록에서 캐치되지 않는다. -> 이러한 문제점들을 해결하기 위해 ES6에서 프로미스가 도입되었다.
프로미스의 생성
- Promise 생성자 함수를 new 연산자와 함께 호출하면 프로미스 객체를 생성한다.
- 프로미스는 호스트 객체가 아닌 ECMAScript 사양에 정의된 표준 빌트인 객체다.
- Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수를 인수로 전달받는데 이 콜백 함수는 resolve와 reject 함수를 인수로 전달받는다.
- Promise 생성자 함수가 인수로 전달받은 콜백 함수 내부에서 비동기 처리를 수행한다. 이때 비동기 처리가 성공하면 콜백 함수의 인수로 전달받은 resolve 함수를 호출하고, 비동기 처리가 실패하면 reject 함수를 호출한다.
const promiseGet = (url) => {
return Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else {
reject(new Error(xhr.status));
}
};
});
};
promiseGet("url");
- 비동기 함수인 promiseGet은 함수 내부에서 프로미스를 생성하고 반환한다. 비동기 처리는 Promise 생성자 함수가 인수로 전달받은 콜백 함수 내부에서 수행한다.
- 비동기 처리가 성공하면 비동기 처리 결과를 resolve 함수에 인수로 전달하면서 호출하고, 실패하면 에러를 reject 함수에 인수로 전달하면서 호출한다.
프로미스 상태 정보
프로미스는 현재 비동기 처리가 어떻게 진행되고 있는지 나타내는 상태 정보를 갖는다.
- 생성된 직후의 프로미스는 기본적으로 pending 상태이다. 비동기 처리가 수행되면 비동기 처리 결과에 따라 프로미스 상태가 변경된다.
- 프로미스의 상태는 resolve 또는 reject 함수를 호출하는 것으로 결정된다.
- fulfilled 또는 rejected 상태를 settled 상태라고 한다. settled 상태는 pending이 아닌 비동기 처리가 수행된 상태를 말한다.
- 프로미스는 pending 상태에서 settled 상태로 변화할 수 있지만, settled 상태가 되면 다른 상태로 변화할 수 없다.
- 프로미스는 비동기 처리 상태와 더불어 비동기 처리 결과도 상태로 갖는다. 즉, 프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체다.
프로미스의 후속 처리 메서드
- 프로미스의 비동기 처리 상태가 변화하면 이에 따른 후속 처리를 해야한다.
- 예를 들어, 프로미스가 fulfilled 상태가 되면 프로미스의 처리 결과를 가지고 무언가를 해야 하고, 프로미스가 rejected 상태가 되면 프로미스의 처리 결과(에러)를 가지고 에러 처리를 해야한다.
- 프로미스의 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출된다.
- 후속 처리 메서드의 콜백 함수에 프로미스의 처리 결과가 인수로 전달된다.
- 모든 후속 처리 메서드는 프로미스를 반환하며, 비동기로 동작한다.
- 만약 후속 처리 메서드의 콜백 함수가 프로미스가 아닌 값을 반환하면 그 값을 암묵적으로 resolve 또는 reject하여 프로미스를 생성해 반환한다.
Promise.prototype.then
then 메서드는 두 개의 콜백 함수를 인수로 전달받는다.
첫 번째 콜백 함수는 비동기 처리가 성공했을 때 호출되는 성공 처리 콜백 함수이며, 두 번째 콜백 함수는 비동기 처리가 실패했을 때 호출되는 실패 처리 콜백 함수다.
// fulfilled
new Promise((resolve) => resolve("fulfilled")).then(
(v) => console.log(v),
(e) => console.error(e)
); // fulfilled
// rejected
new Promise((_, reject) => reject(new Error("rejected"))).then(
(v) => console.log(v),
(e) => console.error(e)
); // Error: rejected
Promise.prototype.catch
catch 메서드는 한 개의 콜백 함수를 인수로 전달받는다. catch 메서드의 콜백 함수는 프로미스가 rejected 상태인 경우만 호출된다.
// rejected
new Promise((_, reject) => reject(new Error("rejected"))).catch((e) =>
console.log(e)
); // Error: reject
Promise.prototype.finally
finally 메서드는 한 개의 콜백 함수를 인수로 전달받는다. finally 메서드의 콜백 함수는 프로미스의 성공 실패와 상관없이 무조건 한 번 호출된다. 프로미스의 상태와 상관없이 공통적으로 수행해야 할 처리 내용이 있을 때 유용하다.
new Promise(() => {}).finally(() => console.log("finally")); // finally
프로미스의 에러 처리
- 비동기 처리를 위한 콜백 패턴은 에러 처리가 곤란하다는 문제가 있다. 프로미스는 에러를 문제없이 처리할 수 있다.
- 비동기 처리에서 발생한 에러는 then 메서드의 두 번째 콜백 함수나 catch를 사용해 처리할 수 있다.
- catch 메서드를 호출하면 내부적으로 then(undefined, catch 콜백함수)을 호출한다.
- then의 두 번째 콜백 함수는 첫 번째 콜백 함수에서 발생한 에러를 캐치하지 못하고 코드가 복잡해져서 가독성이 좋지 않다.
- 모든 then 메서드를 호출한 이후에 catch 메서드를 사용하면 then 메서드 내부에서 발생한 에러까지 모두 캐치할 수 있고 가독성이 좋고 명확하다.
프로미스 체이닝
const url = "https://jsonplaceholder.typicode.com";
// id가 1인 post의 userId를 취득
promiseGet(`${url}/posts/1`)
// 취득한 post의 userId로 user 정보를 취득
.then(({ userId }) => promiseGet(`${url}/users/${userId}`))
.then((userInfo) => console.log(userInfo))
.catch((err) => console.error(err));
- then, catch, finally 후속 처리 메서드는 언제나 프로미스를 반환하므로 연속적으로 호출할 수 있다. 이를 프로미스 체이닝이라 한다.
- 프로미스는 프로미스 체이닝을 통해 비동기 처리 결과를 전달받아 후속 처리를 하므로 비동기 처리를 위한 콜백 패턴에서 발생하던 콜백 헬이 발생하지 않는다. 다만 프로미스도 콜백 패턴을 사용하므로 콜백 함수를 사용하지 않는 것은 아니다.
프로미스의 정적 메서드
Promise는 주로 생성자 함수로 사용되지만 함수도 객체이므로 메서드를 가질 수 있다. Promise는 5가지 정적 메서드를 제공한다.
Promise.resolve / Promise.reject
두 메서드는 이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용한다.
Promise.resolve 메서드는 인수로 전달받은 값을 resolve하는 프로미스를 생성한다.
const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then((e) => console.log(e));
// 다음과 같이 동작한다.
const resolvedPromise = new Promise((resolve) => resolve([1, 2, 3]));
resolvedPromise.then(console.log); // [1, 2, 3]
Promise.reject 메서드는 인수로 전달받은 값을 reject하는 프로미스를 생성한다.
Promise.all
Promise.all 메서드는 여러 개의 비동기 처리를 모두 병렬 처리할 때 사용한다.
- 여러 개의 비동기 처리가 서로 의존하지 않고 개별적으로 수행되는 경우. 즉, 앞선 비동기 처리 결과를 다음 비동기 처리가 사용하지 않는 경우 세 개의 비동기 처리를 순차적으로 처리할 필요가 없다.
const requestData1 = () =>
new Promise((resolve) => setTimeout(() => resolve(1), 3000));
const requestData2 = () =>
new Promise((resolve) => setTimeout(() => resolve(2), 2000));
const requestData3 = () =>
new Promise((resolve) => setTimeout(() => resolve(3), 1000));
Promise.all([requestData1(), requestData2(), requestData3()])
.then(console.log)
.catch(console.error);
- Promise.all 메서드는 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받는다. 그리고 전달받은 모든 프로미스가 fulfilled 상태가 되면 모든 처리 결과를 배열에 저장해 새로운 프로미스를 반환한다.
- Promise.all 메서드는 인수로 전달받은 배열의 모든 프로미스가 모두 fulfilled 상태가 되면 종료한다. 따라서 Promise.all 메서드가 종료하는 데 걸리는 시간은 가장 늦게 fulfilled 상태가 되는 프로미스의 처리 시간보다 조금 더 길다.
- 첫 번째 프로미스가 resolve한 처리 결과부터 차례대로 배열에 저장해 그 배열을 resolve하는 새로운 프로미스를 반환한다. 즉, 처리 순서가 보장된다.
- 인수로 전달받은 배열의 프로미스가 하나라도 rejected 상태가 되면 나머지 프로미스가 fulfilled 상태가 되는 것을 기다리지 않고 즉시 종료한다.
- 인수로 전달받은 이터러블의 요소가 프로미스가 아닌 경우 Promise.resolve 메서드를 통해 프로미스로 래핑한다.
Promise.race
- Promise.race 메서드는 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받는다.
- Promise.all 처럼 모든 프로미스가 fulfilled 상태가 되는 것을 기다리는 것이 아니라 가장 먼저 fulfilled 상태가 된 프로미스의 처리 결과를 resolve하는 새로운 프로미스를 반환한다.
- 프로미스가 rejected 상태가 되면 에러를 reject하는 새로운 프로미스를 즉시 반환한다.
Promise.allSettled
- Promise.allSettled 메서드는 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받는다.
- 전달받은 프로미스가 모두 settled 상태(비동기 처리가 수행된 상태)가 되면 처리 결과를 배열로 반환한다.
- Promise.allSettled 메서드가 반환한 배열에는 상태와는 상관없이 인수로 전달받은 모든 프로미스들의 처리 결과가 모두 담겨있다.
- fulfilled 상태인 경우 비동기 처리 상태를 나타내는 status 프로퍼티와 처리 결과를 나타내는 value 프로퍼티를 갖는다.
- rejected 상태인 경우 비동기 처리 상태를 나타내는 status 프로퍼티와 에러를 나타내는 reason 프로퍼티를 갖는다.
[
// 프로미스가 fulfilled 상태인 경우
{status: "fulfilled", value: 1},
// 프로미스가 rejected 상태인 경우
{status: "rejected", reason: Error: Error! at <anonymous>:3:60}
]
마이크로태스크 큐
setTimeout(() => console.log(1), 0);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
- 프로미스의 후속 처리 메서드도 비동기로 동작하므로 1 -> 2 -> 3 순으로 출력될 것처럼 보이지만 2 -> 3 -> 1 순으로 출력된다.
- 그 이유는 프로미스의 후속 처리 메서드의 콜백 함수는 태스크 큐가 아니라 마이크로태스크 큐에 저장되기 때문이다.
- 마이크로태스크 큐에는 프로미스의 후속 처리 메서드의 콜백 함수가 일시 저장된다.
- 그 외의 비동기 함수의 콜백 함수나 이벤트 핸들러는 태스크 큐에 일시 저장된다.
- 마이크로태스크 큐는 태스크 큐보다 우선순위가 높다.
- 즉, 이벤트 루프는 콜 스택이 비면 먼저 마이크로태스크 큐에서 대기하고 있는 함수를 가져와 실행한다. 이후 마이크로태스크 큐가 비면 태스크 큐에서 대기하고 있는 함수를 가져와 실행한다.
fetch
- fetch 함수는 XMLHttpRequest 객체와 마찬가지로 HTTP 요청 전송 기능을 제공하는 클라이언트 사이드 Web API이다.
- XMLHttpRequest 객체보다 사용법이 간단하고 프로미스를 지원하기 때문에 비동기 처리를 위한 콜백 패턴의 단점에서 자유롭다.
- fetch 함수에는 HTTP 요청을 전송할 URL과 HTTP 요청 메서드, HTTP 요청 헤더, 페이로드 등을 설정한 객체를 전달한다.
- fetch 함수는 HTTP 응답을 나타내는 Response 객체를 래핑한 Promise 객체를 반환한다.
- 그렇기 때문에 후속 처리 메서드 then을 통해 프로미스가 resolve한 Response 객체를 전달받을 수 있다.
- Response.prototype에는 Response 객체에 포함되어 있는 HTTP 응답 몸체를 위한 다양한 메서드를 제공한다.
- 예를 들어, 반환한 프로미스가 래핑하고 있는 MIME 타입이 application/json인 HTTP 응답 몸체를 취득하려면 Response.prototype.json 메서드를 사용한다.
- 이 메서드는 response body를 취득하여 역직렬화한다.
- fetch 함수에 첫 번째 인수로 HTTP 요청을 전송할 URL과 두 번째 인수로 HTTP 요청 메서드, HTTP 요청 헤더, 페이로드 등을 설정한 객체를 전달한다.
const request = {
get(url) {
return fetch(url);
},
post(url, payload) {
return fetch(url, {
method: "POST",
headers: { "content-Type": "application/json" },
body: JSON.stringify(payload),
});
},
patch(url, payload) {
return fetch(url, {
method: "PATCH",
headers: { "content-Type": "application/json" },
body: JSON.stringify(payload),
});
},
delete(url) {
return fetch(url, { method: "DELETE" });
},
};
출처
모던 자바스크립트 Deep Dive
1027
78일 차
알고리즘 시험을 대비해서 리트코드 문제를 풀어봤다.
처음 겪는 듯한 스타일에 적지 않게 당황했고, 쉽지 않다는 생각이 들었다.
프로그래머스는 어느정도 문제를 풀어봤으니 이제 리트코드를 열심히 풀어야겠다고 생각했다.
Two Sum
var twoSum = function (nums, target) {
for (let i = 0; i < nums.length; i++) {
for (let j = 0; j < nums.length; j++) {
if (i === j) continue;
if (nums[i] + nums[j] === target) return [i, j];
}
}
};
- 리트코드 문제에는 투포인터나 DP 스타일의 문제들이 다양했다.
- 처음에는 영어 문제가 해석이 잘 안돼서 고생 좀 했다.
- 이 문제는 시간복잡도를 크게 중요시 하는 문제는 아니라서 쉽게 해결했다.
Three Sum
var threeSum = function (nums) {
let answer = [];
nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length; i++) {
if (nums[i] === nums[i - 1]) continue;
let lt = i + 1;
let rt = nums.length - 1;
while (lt < rt) {
if (lt === i) lt++;
if (rt === i) rt--;
let sum = nums[i] + nums[lt] + nums[rt];
if (sum === 0) {
while (nums[lt] === nums[lt + 1]) lt++;
while (nums[rt] === nums[rt - 1]) rt--;
answer.push([nums[i], nums[lt], nums[rt]]);
lt++;
rt--;
} else if (sum < 0) {
lt++;
} else {
rt--;
}
}
}
return answer;
};
- 이 문제에서 살짝 좌절감을 느꼈었는데, 처음에는 해시로 접근했다가 포함하는 요소가 같은 경우를 어떻게 처리해 줄지 고민했었다.
- 찾아보니 투포인터로 문제를 해결하는 좋은 방법이 있었다.
- 우선 이 방법을 사용하기 위해서는 오름차순으로 정렬을 했다.
- for 문을 한 번만 돌리면서 그 내부에서 lt와 rt를 정하고 세 인덱스에 위치한 값을 더해 값이 0보다 큰지와 0보다 작은지로 나누어서 lt와 rt를 조정해주었다.
- lt와 rt 중 계속해서 동일한 값이 반복되는 경우를 방지하기 위해 같은 경우 인덱스를 조절해줬다.
1028
코딩테스트 리뷰
이번 코딩테스트를 보며 생각하게 된 바가 많다.
소수를 구하는 발상을 몇 번 마주친 적 있었는데, 크게 중요하지 않다고 생각하고 대수롭지 않게 넘겼는데 그게 화근이 됐다.
초반에는 빠른 속도로 잘 풀다가 이 하나때문에 너무 크게 말리게 되었다. 게으름을 반성하고 후회한다…
또한 코딩테스트에서는 맨탈 관리가 상당히 중요하다고 느꼈다. 단순히 한 문제씩 푸는 것이 아니라 장시간에 여러 문제를 푸는 것 또한 훈련해야겠다.
isPrime
const isPrime = (n) => {
if (n % 2 === 0) return false;
for (let i = 3; i <= Math.sqrt(n); i += 2;) {
if (n % i === 0) return false;
}
return true;
}
- 이번 코딩테스트를 나락으로 빠트린 장본인이다.
- 우선 짝수라면 소수가 될 수 없으므로 false를 반환한다.
- 3부터 n의 제곱근보다 작거나 같을 때까지 2씩 증가시키며 n이 i로 나누어 떨어지면 false를 반환한다.
- 굳이 n까지 모든 수로 나누어 볼 필요는 없다.
- 모든 수는 제곱근의 제곱으로 나타낼 수 있으므로 굳이 제곱근 이상의 수로 나누어 볼 필요가 없기 때문이다.
countPrimes
var countPrimes = function (n) {
let ch = Array(n).fill(0);
ch[0] = 1;
ch[1] = 1;
for (let i = 2; i * i < n; i++) {
if (ch[i] === 1) continue;
for (let j = i * i; j < n; j += i) {
ch[j] = 1;
}
}
return ch.filter((v) => !v).length;
};
- 저번에 무심코 넘어갔던 에라토스테네스의 체 알고리즘을 활용한 문제를 해결했다.
- 에라토스테네스의 체는 어떤 숫자까지의 소수를 찾을 때 유용한 알고리즘이다.
- 이는 2부터 시작해서 배수로 소수 명단을 제거해나가는 식이다.
- 이를 발전시켜 i가 2부터 i 제곱이 n보다 작을 때까지만 이를 제거해나가도 결과가 같다.
- 이렇게 모든 소수 명단을 제거하고 마지막에 소수의 개수를 세어서 문제를 해결했다.
Maximum Subarray
var maxSubArray = function (nums) {
let answer = -10000;
let lt = 0;
let sum = 0;
for (let rt = 0; rt < nums.length; rt++) {
sum += nums[rt];
while (sum < nums[rt]) {
sum -= nums[lt++];
}
answer = Math.max(answer, sum);
}
return answer;
};
- 가장 최대가 되는 연속 부분수열을 구하는 문제
- rt를 증가하면서 모든 숫자 합을 구하면서, 숫자의 합보다 rt위치에 있는 수가 크다면 lt를 증가시키며 최댓값을 구해줬다.
Valid Palindrome
const isPalindrome = (s) => {
const answer = s.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
return answer === answer.split("").reverse().join("");
};
- 정규표현식을 활용해서 문제를 해결하였다.
- 처음에는
\W를 사용해서 문제를 풀려고 했는데, 이는 알파벳과 숫자 그리고_ 가 아닌 것들을 선택하기 때문에 이 방법을 사용하면 안됐다.
Maximum Product Subarray
var maxProduct = function (nums) {
const arr = [];
let max = Number.MIN_SAFE_INTEGER;
let target = 1;
for (let i = 0; i < nums.length; i++) {
target *= nums[i];
max = Math.max(max, target);
if (nums[i] === 0) {
target = 1;
}
}
target = 1;
for (let i = nums.length - 1; i >= 0; i--) {
target *= nums[i];
max = Math.max(max, target);
if (nums[i] === 0) {
target = 1;
}
}
return max;
};
- 왼쪽에서 한 번, 오른쪽에서 한 번 수를 쭉 곱해서 그 중 최댓값을 구했다.
- 도중 0인 요소를 만나면 값을 1로 초기화 해주었다.
- 이런 연속 수열 문제는 한 번쯤 왼쪽으로 쭉 오른쪽으로 쭉 계산하는 방법을 떠올려봐야겠다.
Product of Array Except Self
var productExceptSelf = function (nums) {
const left = Array(nums.length).fill(1);
let tmp = 1;
for (let i = 1; i < nums.length; i++) {
tmp *= nums[i - 1];
left[i] = tmp;
}
tmp = 1;
for (let i = nums.length - 2; i >= 0; i--) {
tmp *= nums[i + 1];
left[i] *= tmp;
}
return left;
};
- 왼쪽에서 한 번, 오른쪽에서 한 번 값을 곱한 뒤 두 수를 곱하면 현재 인덱스에 있는 수를 제외하고 총 곱이 되었다.
- 발상 자체를 떠올리는 것이 리트코드 스타일은 익숙하지 않았다. 이를 꾸준히 풀어야겠다고 생각했다.
1029
80일 차
객체나 배열을 비교할 때 JSON.stringify 메서드를 사용하면 모든 내용을 문자열로 변환하니 간단하게 요소 내용을 비교할 수 있다. ( 코테용 )
export 하는 방법은 두 가지
- named export
- 한 파일 내에서 여러 변수/클래스 등등을 export하는 것이 가능하다.
- 단 import 시 export 된 이름과 동일하게 설정해야 한다.
- 다른 이름으로 import할 수 있으나 as를 사용해야 한다.
- default export
- default로 선언된 모듈은 하나의 파일에서 단 하나의 변수 또는 클래스 등등만 export할 수 있다.
- import 할 때는 아무 이름으로나 가능하다.
- 단 var, let, const를 바로 export default 할 수 없다.
- import 받게 된 식별자는 무조건 const로 선언된 식별자처럼 처리되기 때문에 재할당이 불가능하다. 그래서 위와 같이 규칙이 정해진 것이다.
null을 반환하지 말라 -> 클린 코드에서 나오는 이야기인데 이는 타입 언어에 관련된 이야기이다. 좋은 코드라는 것은 굉장히 주관적인 것
제네레이터
_을 붙여주면 제네레이터 함수가 정의된다.
yield 라는 키워드를 쓰면 이 함수를 호출했을 때 어디까지 실행시켰다가 멈출수 있다.
-> await는 이를 이용해서 구현
-> 비동기처리 시 그 함수의 실행이 진짜 wait가 된다. await 문을 5개 나열하면 하나의 await가 5초 걸린다면 25초가 걸린다.
-> 그래서 비동기처리가 서로 연관이 없으면 Promise.all을 사용하여 병렬처리를 해야한다.
_ Promise.all 앞에 await를 붙인다.
- await는 뒤에 오는 프로미스가 resolve 될 때까지 기다린다.
- 제네레이터 함수와 이 함수를 호출한 caller가 있는데, caller에게 중간중간 값을 줄 수 있다.
- 제네레이터가 하는 역할은 함수의 실행을 중단했다가 재개하는 것이다. 일시 중단한 시점에서 값을 주고받을 수 있다.
async와 await의 장점
- 비동기 처리를 동기처럼 쓸 수 있다.
- try catch 문으로 에러를 처리할 수 있다.
async 키워드는 함수의 가장 선두에 사용한다.
코딩을 할 때 내부부터 쓰지말고, try catch 부터 작성한다.
-> 까먹지 않기 위해서
개발 기본 팁
- 개발용 코드는 public에 위치해서는 안된다. -> 나중에 빌드된 js파일이 위치해야함
- src폴더에 js폴더를 위치하게 하고 웹팩이 빌드한 파일을 public에 위치하도록 한다.
source-map은 디버깅용
웹팩은 서버랑 상관없이 클라이언트 소스코드를 번들링 해주는 것이다.
babel을 사용해도 프로미스, 제네레이터 등을 대체할 문법이 없기 때문에 폴리필을 깔아줘야 한다.
ESM은 기본적으로 defer이다.
html 파일에서 script 태그를 동적으로 추가할수있다.
템플릿을 만들어 놓고 script 태그를 추가하도록 한다.
-> 실수를 방지하기 위함
webpack 설정파일까지는 watch를 못해준다.
-> 즉 설정파일 변경 시 서버를 다시 켜줘야함
webpack-dev-server
- 라이브 리로드 기능을 제공하는 개발용 서버
- 빌드를 실행하면 번들링된 파일을 생성하기 때문에 규모가 큰 프로젝트라면 빌드에 시간이 걸린다.
- webpack-dev-server는 실제 번들링된 파일을 생성하지 않고 번들링된 결과를 메모리에 저장하기 때문에 빌드 속도가 빠르다.
1030
네이버 파이낸셜 리뷰
프로그래머스 홈페이지에서 네이버 파이낸셜 과제 테스트가 올라왔길래 참여해봤다.
아직 리액트를 배우지는 않아서 2차까지 도전하지는 못할거 같았지만 경험삼아 1차만 시험을 맛봤다.
시험에는 사용해본 경험이 있는 Pagination이 나왔지만, 웹 vscode로 시험을 치는게 처음이라 서버를 어떻게 동작시켜야하는지 엄청 헤맸다.
결과적으로 마지막에 추가 기능을 구현하다가 실수를 저질러 망한 거 같기는 하지만 좋은 경험이었다고 생각한다.
이번 경험을 삼아 다음에는 초반에 헤매는 일이 적어질 것이라고 생각한다.
함수 매개변수의 기본값 설정하기
이번 페이지네이션을 구현하며 생성자 함수를 사용했는데,
const pagination = new Pagination({
currentPage: 1,
totalItemCount: 300,
pagePerItemCount: 20,
});
위와 같이 호출을 해서 화면에 렌더링을 해줘야 했다.
위의 세 개의 프로퍼티를 포함한 객체를 인수로 전달해줘야 했는데, 이를 객체 디스트럭처링 할당에서 기본값을 이용해 해결했다.
문제는 객체 자체를 전달하지 않을 때도 기본값을 설정해줘야 했는데, 이 때는 객체 자체가 undefined로 평가되면서 에러가 발생했다.
function pagination({
currentPage = 1,
totalItemCount = 0,
pagePerItemCount = 20,
} = {}) {
this.currentPage = currentPage;
this.totalItemCount = totalItemCount;
pagePerItemCount = pagePerItemCount;
}
- 이를 위와 같이 해결했다.
- 빈 오브젝트를 할당하지 않고 작성했을 때는 함수 호출 시 적어도 하나의 인자가 제공되어야 한다.
- 무조건 객체를 넘기길 원하는 경우에는 빈 객체를 할당하지 않아도 되지만, 어떠한 매개변수 없이도 호출할 수 있기를 원한다면 위와 같이 작성할 경우 아무런 객체를 넘기지 않아도 세 객체에 기본값까지 할당된다.