unary plus

무엇이든 숫자로 캐스팅하는 연산자

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
console.log(+1);
// expected output: 1

console.log(+-1);
// expected output: -1

console.log(+'');
// expected output: 0

console.log(+true);
// expected output: 1

console.log(+false);
// expected output: 0

console.log(+null);
// expected output: 0

console.log(+'1');
// expected output: 1

console.log(+'12');
// expected output: 12

console.log(+'hello');
// expected output: NaN

console.log(+0xff);
// expected output: 255
  • 이 연산의 반환 값은 숫자다
  • 보통 10진 숫자로 된 문자열을 숫자로 바꿀 때 사용하지만
  • true, false, null, 16진수 등 다양한 인풋을 지원한다

참고

eslint prefer-destructuring

Use array destructuring

  • ???
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let time = 0;
const arrarr = [
[0, 1],
[1, 11],
[2, 21],
];
// ...

// error
time = arrarr[0][0];
// ok
[[time]] = arrarr; // 0

// error
time = arrarr[1][1];
// ok
[, [, time]] = arrarr; // 11
  • 홀리…
  • 배열과 오브젝트는 구조 분해 할당할 수 있다
  • 오브젝트의 경우 자주 사용하지만, 배열의 경우는 별로 사용해본적이 없다
  • 리액트를 쓰면서 useState에서 state와 setState함수를 받을 때 주로 사용했지,
  • 이렇게 이미 선언해서 쓰던 변수에 값을 덮어쓸 때 사용해서 약간 충격이다
  • 배열에 변수가 아닌 정적인 상수 인덱스를 통해서 꺼내는 경우 구조 분해 할당을 사용하라고 추천해줘서 알게 되었다
  • 근데 상수 인덱스가 너무 큰 경우에는 오히려 가독성이 떨어질 것 같다

참고

script태그 defer, async

  • 처음 웹 프로그래밍을 배울 때 스크립트 태그는 body 최하단에 위치시키라고 배웠다
  • 스크립트 로드 타이밍을 뒤로 미뤄서 돔을 조작하는 스크립트가 잘 동작한다
  • 근데 요즘에는 스크립트 태그를 head 안에 선언하는 경우가 많다
  • defer를 사용하면 위에서 말한 효과를 볼 수 있다
  • 근데 defer 말고도 async라는 속성을 줄 수 있다
  • 둘이 차이는 뭘까?

default (blocking further parsing)

  • 스크립트 태그를 만나면 파싱을 멈추고 스크립트를 로드하고 실행한다
  • 이 때 HTML 파싱을 멈춘다
  • 스크립트가 로드 다 끝나면 이어서 남은 파싱을 진행한다
  • 스크립트가 헤비 하면 화면이 안 그려지고 사용자가 떠날 확률도 높아지겠다

defer, async 공통

  • 스크립트는 스크립트대로 로드하고 HTML 파싱은 계속된다
  • 비동기적이다

defer

  • 스크립트 로드가 완료되었다고 바로 실행되지 않는다
  • HTML 파싱이 모두 끝나면 실행된다
  • 주로 돔을 조작하는 스크립트를 로드할 때 사용하면 된다

async

  • 스크립트가 로드되고 바로 실행된다
  • 돔을 조작하는 스크립트가 없을 때 사용하는 것이 좋다

참고

promise async await 예제

example.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
// promise는 선언과 동시에 실행된다
function asyncFunction(delay, memo) {
console.log(`in`, memo);
const thisIsPromise = new Promise((resolve) => {
setTimeout(() => {
console.log(`done`, memo);
resolve(delay);
}, delay);
});

return thisIsPromise;
}

const fetchData = asyncFunction.bind(null, 1000, 'fetchData');
const fetchHeavyData = asyncFunction.bind(null, 3000, 'fetchHeavyData');

function add(a, b) {
return a + b;
}

async function main() {
console.log(`start main`);
console.time(`main`);

const a = await fetchHeavyData();
const b = await fetchData();

const result = add(a, b);
console.log(`main result`, result);
console.timeEnd(`main`);
}

async function main2() {
console.log(`start main2`);
console.time(`main2`);

const a = fetchHeavyData();
const b = fetchData();

const promiseResultArr = await Promise.all([a, b]);

const result = add(...promiseResultArr);
console.log(`main2 result`, result);
console.timeEnd(`main2`);
}

main();
// main2();
  • 친구한테 비동기에 대해 설명하면서 만든 예제 코드다

설명하기

  • 비동기 초보 친구한테 설명하면서 어떤 부분을 설명했나?

await은 async함수 내부에서 사용 가능

1
2
3
4
5
6
// 1 work
fetchData().then(console.log);

// 2 not work
const data = await fetchData();
console.log(data);
  • 위는 동작하고, 아래는 동작하지 않는다고 해서
  • await 키워드 사용은 async 함수 내부에서만 가능하다고 말했다

express에서 비동기

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1 work...
router.get('/async', function (req, res) {
asyncFunction(1000, 'memo').then((result) => {
res.json(result);
});
});

// 2 work
router.get('/async', async function (req, res) {
const result = await asyncFunction(1000, 'memo');

res.json(result);
});
  • 1로 했을 때 어떻게 동작하냐 해서
  • 2로 하는 게 좋을 것 같다고 했다
  • 실제로 테스트 결과 1, 2 모두 잘 동작했다
  • 1의 경우 안될 줄 알았는데 express가 똑똑한 건가…

then 콜백 함수에서 return

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1 not work
async function f() {
await asyncFunction(1000, 'memo').then((result) => {
return result;
});
}

// 2 work
async function f() {
return await asyncFunction(1000, 'memo').then((result) => {
return result;
});
}
  • 1의 경우에서 함수 f의 반환 값이 없다 하여,
  • then 콜백 함수에서 return이 함수 f의 리턴을 의미하지 않는다고 말했다

체이닝

1
2
3
4
5
6
7
const a = await asyncFunction(1000, 'memo') // 1000
.then((result) => {
return result * 2;
}) // 2000
.then((result) => {
return result + 200;
}); // 2200
  • then을 끝까지 수행한 후에 결괏값을 반환한다고 알려줬다
  • a에는 2200이 들어간다

생략 표현

1
2
3
4
5
6
function add1(a) {
return a + 1;
}

asyncFunction(1000, 'memo').then(add1);
asyncFunction(1000, 'memo').then((res) => add1(res));
  • 인자를 그대로 다른 함수에 넘겨주는 경우 생략이 가능하다

Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1
{
const a = await fetchHeavyData();
const b = await fetchData();
const result = add(a, b);
}

// 2
{
const a = fetchHeavyData();
const b = fetchData();

const promiseResultArr = await Promise.all([a, b]);

const result = add(...promiseResultArr);
}
  • promise는 선언과 동시에 실행된다
  • 1의 경우 서로 무관한 비동기 흐름을 순차적으로 처리한다
  • 2의 경우 Promise.all을 사용해 병렬 처리하여 1보다 효율적이다

blog fix 카피 버튼이 안나오는 문제

  • 블로그에 코드 스니펫 오른쪽 위에 카피 버튼이 나올 때도 있고 안 나올 때도 있었다
  • 무한 새로고침하면 종종 모습을 비춘다… (이스터에그도 아닌 것이..)

환경

  • hexo-theme-icarus v4.0.1

해결하기

  • 일단 어디서 이 버튼을 생성하는지 알아내야 한다
  • 버튼이 정상적으로 출력되었을 때 해당 버튼의 클래스명을 확인했다
  • 그리고 main.js라는 곳에서 동적으로 버튼을 생성하고 있음을 알아냈다
themes\icarus\source\js\main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(clipboard, typeof ClipboardJS);
if (typeof ClipboardJS !== 'undefined' && clipboard) {
$('figure.highlight').each(function () {
const id = 'code-' + Date.now() + ((Math.random() * 1000) | 0);
const button =
'<a href="javascript:;" class="copy" title="Copy" data-clipboard-target="#' +
id +
' .code"><i class="fas fa-copy"></i></a>';
$(this).attr('id', id);
$(this).find('figcaption div.level-right').append(button);
});
new ClipboardJS('.highlight .copy'); // eslint-disable-line no-new
}
  • 조건문에 ClipboardJS가 있으면 버튼을 생성하는데
  • main.js에는 ClipboardJS에 대해 따로 선언이 없었고, 외부 라이브러리라는 것을 알았다
  • 이 ClipboardJS가 어디서 오는지 확인해야 했다
themes\icarus\layout\common\scripts.jsx
1
2
3
4
5
6
7
8
9
{
clipboard && (
<script
src={cdn('clipboard', '2.0.4', 'dist/clipboard.min.js')}
// async
defer
></script>
);
}
  • layout\common\scripts.jsx에서 가져오는 코드를 확인했는데
  • async으로 가져오고 있었다
  • 문제는 clipboardJS가 로드되기 전에 main.js가 먼저 실행되는 문제였다
  • defer로 수정하니까 잘 동작했다!

최신 버전 확인

  • hexo-theme-icarus v4.2.0
  • 최신 버전을 확인하니까 defer로 변경되어있었다… (이슈 검색했을 때는 못 찼았다…)
  • 처음부터 최신 버전을 참고하면 좋았겠다
  • 9개월 정도 나름대로 커스텀하면서 사용해왔는데,
  • 나중에 시간 나면 커스텀한 내용을 유지하면서 최신 버전에 맞게 업데이트해줘야겠다

참고

async, defer

jest 'describe' is not defined

.eslintrc.json
1
2
3
4
5
6
7
{
"env": {
// ...
"jest": true
}
// ...
}
  • env에 추가해준다
  • ⚠️ 추가 해주고 F1 > eslint.restart 명령을 실행해 eslint를 재부팅한다

상황

  • jest를 eslint와 처음 사용해본다

eslint(no-undef)

‘describe’ is not defined.
‘test’ is not defined.
‘expect’ is not defined.

  • 위 에러가 나왔다

참고

커밋 메시지 작성을 도와줄 확장도구 vscode-conventional-commits

상황

  • 기존에 Visual Studio Code Commitizen Support 확장 도구를 사용하고 있었다
  • 그런데 커밋 body에 적는 내용이 어째선지 반영되지 않았다…
  • 관련해서 구글링을 해봤는데 도움되는 내용을 찾지 못했다
  • 그러다가 다른 확장도구를 발견했다

Conventional Commits

  • Conventional Commits이라는 확장도구이다
  • 사용법은 Commitizen과 동일하다
  • 이 확장도구를 사용하면 body(description)가 정상적으로 등록된다!

참고

디바운스와 스로틀

  • 스크롤 이벤트가 발생하면 1을 출력하는 함수를 실행시킨다고 하자

기본

  • 아날로그 신호처럼 콘솔창에 1이 찍힌다

throttle

  • 설정한 주기를 간격으로 1이 찍힌다
  • 쓰로틀은 연속된 이벤트를 설정한 시간 간격
  • 예) 스크롤중 300ms 마다 함수 실행
  • 예) 네이버 검색창 자동완성

debounce

  • 스크롤 이벤트 도중에는 아무것도 일어나지 않는다
  • 이벤트가 끝나고 설정한 1이 찍힌다
  • 디바운스는 연속된 이벤트가 멈추고, 한번 실행한다
  • 마지막 이벤트로부터 설정한 시간만큼 이벤트가 발생하지 않으면 실행한다
  • 예) 스크롤이 멈추고 300ms 뒤에 함수 실행
  • 예) 버튼을 연속적으로 여러 번 눌러도 한 번만 실행되는… (광클 방지)

쓰는 이유

  • 디바운스와 쓰로틀은 부하를 줄이기 위해 사용한다
  • 매 이벤트마다 비싼 API를 호출하는 것을 방지할 수 있다