js empty array

new Array(n)으로 초기화한 배열에 array api가 동작하지 않았다

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
const arr = new Array(5);

console.log(arr.length, arr); // 5 [ <5 empty items> ]
console.log(arr.push(1), arr); // 6 [ <5 empty items>, 1 ]
console.log(arr.push(2), arr); // 7 [ <5 empty items>, 1, 2 ]

console.log(arr.map((v) => v * 2)); // [ <5 empty items>, 2, 4 ]

arr.forEach((v) => {
console.log(v);
});
// 1
// 2

console.log(`-----------`);

for (let index = 0; index < arr.length; index++) {
const element = arr[index];
console.log(element);
arr[index] = undefined;
}
// undefined
// undefined
// undefined
// undefined
// undefined
// 1
// 2

console.log(`-----------`);

arr.forEach((v) => {
console.log(v);
});
// undefined
// undefined
// undefined
// undefined
// undefined
// undefined
// undefined
  • 위에 map이나 forEach를 보면 array api가 초기화하지 않은 인덱스에 대해 동작하지 않는 것을 확인할 수 있다
  • 직접 for문으로 찍어보면 undefined이라 나온다
  • 그러면서 undefined으로 전부 초기화해줬다
  • 직접 초기화해주니까 array api forEach가 동작하는 것을 확인할 수 있다
  • array api를 동작하지 않는 것을 경험했다
1
const arr = new Array(5).fill(0);
  • 이런 식으로 특정 값으로 직접 초기화를 해줘야 스킵되지 않고 정상적으로 api를 사용할 수 있다

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함수를 받을 때 주로 사용했지,
  • 이렇게 이미 선언해서 쓰던 변수에 값을 덮어쓸 때 사용해서 약간 충격이다
  • 배열에 변수가 아닌 정적인 상수 인덱스를 통해서 꺼내는 경우 구조 분해 할당을 사용하라고 추천해줘서 알게 되었다
  • 근데 상수 인덱스가 너무 큰 경우에는 오히려 가독성이 떨어질 것 같다

참고

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보다 효율적이다

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.

  • 위 에러가 나왔다

참고

parseInt vs. Math.floor

1
2
3
4
5
6
7
8
9
10
11
let n = 1.1;
console.log(Math.floor(n), parseInt(n)); // 1 1

n = 1.9;
console.log(Math.floor(n), parseInt(n)); // 1 1

n = -1.1;
console.log(Math.floor(n), parseInt(n)); // -2 -1

n = -1.9;
console.log(Math.floor(n), parseInt(n)); // -2 -1
  • 소수점 이하를 버릴 때 Math.floor()(내림)를 주로 썼는데 음수에서 기대한 것과 다르게 동작하는 것을 알게 되었다…
  • parseInt()는 매개변수로 문자열을 받기 때문에 주로 문자열로부터 숫자를 파싱 할 때 사용하는 줄 알았다
  • parseInt()는 매개변수로 string 타입이 아니면 스트링으로 자동 캐스팅하여 그냥 숫자를 넘겨줘도 잘 동작한다
  • ⚠️ 음 근데 문자열로 된 숫자를 인풋으로 줄 때 주의해야 한다
  • parseInt나 Math.floor나 상관없이 엄청나게 큰 문자열로 된 숫자를 number 타입으로 캐스팅하는 용도로 사용하면 안 된다

참고

js scope 스코프, 렉시컬 스코프

  • 전역 변수, 지역 변수 구분하는 것이다
  • 스코프 ; 중괄호로 묶인 영역

전역, 지역 변수

지역변수는 바깥에서 사용할 수 없다
1
2
3
4
5
6
7
const f = () => {
const x = 123;
console.log(x);
};

f(); // 123
console.log(x); // 에러 ; ReferenceError: x is not defined
  • 함수 f 에 선언된 f의 지역변수 x를 바깥에서 사용하지 못하는 모습이다
전역 변수 사용
1
2
3
4
5
6
7
8
const x = 123;

const f = () => {
console.log(x);
};

console.log(x); // 123
f(); // 123
  • 반대로 함수에서는 바깥에 선언된 전역 변수 x를 사용할 수 있다
같은 변수명 일 때는 지역변수
1
2
3
4
5
6
7
8
9
const x = 123;

const f = () => {
const x = 999;
console.log(x);
};

console.log(x); // 123
f(); // 999
  • 전역 변수, 지역 변수에 동일한 이름의 변수가 있으면 가까운 변수를 참조한다

스코프 체인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let x = 1;

{
console.log(x); // 1

{
x = 2;
console.log(x); // 2

{
let x = 111;
console.log(x); // 111

{
console.log(x); // 111
x = 222;
console.log(x); // 222
}
}
}
}
  • 가깝다는 것은 일단 {} 스코프를 기준으로
  • 일단 자기 자신이 속한 스코프에서 변수 x를 찾는다
  • 없으면 상위 스코프에서 찾는다
  • 찾을 때까지 반복한다
  • 이를 스코프 체인이라고 한다
  • 근데 이 스코프에 의한 전역, 지역 변수 구분은,
  • 그러니까 내가 진짜로 참조하는 변수는 스코프를 선언할 때 결정된다 (렉시컬 스코핑)

렉시컬 스코핑

렉시컬 스코핑
1
2
3
4
5
6
7
8
9
10
11
12
13
let x = 1;

const f = () => {
console.log(x);
};

const main = () => {
let x = 999;
f(); // 1
console.log(x); // 999
};

main();
  • 선언할 때 결정된다는 것은
  • 위 코드의 결과처럼
  • 함수 f 가 선언 당시에 최상위 스코프의 let x = 1을 바라본다는 것이다
  • 함수 main을 보면 함수 f가 실행되기 전에 스코프의 let x = 999는 함수 f에 영향을 주지 않는다
  • 이런 개념들은 js를 하면서 그냥 익숙해져 있어서 용어로 설명할 수 있을 정도는 아니었다
  • 이번 기회에 js 개념, 용어들을 정리해봐야겠다

참고

js !! not not (double not)

  • 어느 날 조건문에서 !!을 본 적이 있다
  • 처음에는 왜 쓸모없이 not을 2번 썼지?하면서 리팩토링이랍시고 !!을 지워버렸다 ㅋㅋㅋ
  • 그런데 알아보니까 Boolean 타입으로 캐스팅해주는 것이었다
1
2
3
if (!obj) return; // obj가 존재하면 다음 로직 진행

// obj ...
  • 나는 보통 obj가 유효한지 체크하려고 !을 붙여서 사용하곤 했는데
  • 이미 !한 개를 붙이면 자동으로 Boolean으로 캐스팅된다
  • 거기에 한 번 더 not을 하면 not not 이라서 Boolean(obj)와 똑같아진다는 것을 알 수 있다

참고

Map을 포함한 Object JSON stringify

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
interface MyType {
id: number;
cover: string;
outComes: Map<number, string>;
questions: Map<number, Map<number, string>>;
}

const stringify = (object: any) => {
for (const eachIdx in object) {
if (object[eachIdx] instanceof Map) {
object[eachIdx] = Array.from(object[eachIdx]);
stringify(object);
} else if (typeof object[eachIdx] == 'object') stringify(object[eachIdx]);
}
return JSON.stringify(object, null, 2);
};

const jsonString2ObjectWithMap = <ReturnType>(
jsonString: string
): ReturnType => {
const object = JSON.parse(jsonString);
console.log(`-------------변환전`);
console.log(object);

const jsonstringToObject = (object) => {
for (const eachIdx in object) {
if (
object[eachIdx] instanceof Array &&
object[eachIdx].length > 0 &&
object[eachIdx][0].constructor === Array
) {
object[eachIdx] = new Map(object[eachIdx]);
jsonstringToObject(object);
} else if (typeof object[eachIdx] == 'object')
jsonstringToObject(object[eachIdx]);
}

return object;
};

console.log(`-------------변환후`);
const result = jsonstringToObject(object);

console.log(result);

return result;
};
실행
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
const myObject: MyType = {
id: 30,
cover: 'cover',
outComes: new Map([
[0, 'o1'],
[1, 'o2'],
]),
questions: new Map([
[
0,
new Map([
[1, 'answer1'],
[2, 'ansewr2'],
]),
],
[
1,
new Map([
[1, 'ansewr1'],
[2, 'ansewr2'],
]),
],
[2, new Map([])],
]),
};

console.log(`-----------------map을 포함한 오브젝트 json stringify`);
const rst = stringify(myObject);
console.log(rst);
console.log(typeof rst);

console.log(`-----------------다시 오브젝트화`);
const result = jsonString2ObjectWithMap<MyType>(rst);
output
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
-----------------map을 포함한 오브젝트 json stringify
{
"id": 30,
"cover": "cover",
"outComes": [
[
0,
"o1"
],
[
1,
"o2"
]
],
"questions": [
[
0,
[
[
1,
"answer1"
],
[
2,
"ansewr2"
]
]
],
[
1,
[
[
1,
"ansewr1"
],
[
2,
"ansewr2"
]
]
],
[
2,
[]
]
]
}
string
-----------------다시 오브젝트화
-------------변환전
{
id: 30,
cover: 'cover',
outComes: [ [ 0, 'o1' ], [ 1, 'o2' ] ],
questions: [ [ 0, [Array] ], [ 1, [Array] ], [ 2, [] ] ]
}
-------------변환후
{
id: 30,
cover: 'cover',
outComes: Map { 0 => 'o1', 1 => 'o2' },
questions: Map { 0 => [ [Array], [Array] ], 1 => [ [Array], [Array] ], 2 => [] }
}

상황

  • 서버로 json 형태로 값을 전달하고 싶은데…
  • Map은 json stringify로 변환되지 않는다
  • 변환하기 위해서는 한번 Array로 변환한 다음에야 가능했다
  • 어떤 객체에 맵이 중첩으로 사용된 경우 수동으로 바꿔주는 게 귀찮다

해결

stringify.ts
1
2
3
4
5
6
7
8
9
const stringify = (object: any) => {
for (const eachIdx in object) {
if (object[eachIdx] instanceof Map) {
object[eachIdx] = Array.from(object[eachIdx]);
stringify(object);
} else if (typeof object[eachIdx] == 'object') stringify(object[eachIdx]);
}
return JSON.stringify(object, null, 2);
};
  • 객체 안 멤버들을 하나씩 돌아가면서 Map이거나 오브젝트인지 확인한다
  • Map이면은 JSON stringify 할 수 있는 Array로 변환한다
  • 오브젝트이면 중첩되어있는 Map을 찾기 위해 재귀적으로 반복한다

사용법

  • myObject와 같은 중첩 map을 포함하고 있고,
  • 포함되어있는 Map이 Map<number,string> 일 경우에만 때만 테스트해보아서, 더 다양한 경우에도 동작할지는 모르겠다
  • input : { a:… } 처럼 오브젝트를 넣어야 한다
  • console.log 찍어봤을 때 { }로 묶여있는…
  • 만약에 그냥 Map인 경우에는 이런 식으로 { a: new Map() } 한번 감싸주면 된다

repo

array to hashmap js

ex1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const arr = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
];

let map = new Map();

arr.reduce((newMap, cur) => {
const { id, value } = cur;
newMap.set(id, value);
return newMap;
}, map);

console.log(map);
map.get(1);
  • reduce로 합쳐주는 방식으로 할 수 있고
ex2
1
2
3
4
5
6
7
8
9
const arr = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
];

const map = new Map(arr.map((item) => [item.id, item.value]));

console.log(map);
  • array.map()과 Map 생성자로 짧고 이쁘게 변화해줄 수 있다

참고