jsisweird

  • JS Is Weird 풀이 (자바스크립트는 이상해~~~)
  • js 퀴즈 25문제 푸는 곳이다
  • 나는 15/25점을 맞았다

후기

  • 소름 돋는 건 문법 에러가 정답인 문제는 없다는 것이었다 ㅋㅋ
  • 혹시 문제를 풀어보지 않았다면 먼저 문제를 풀고 오는 것을 추천한다!
  • https://jsisweird.com/

오답노트

2. [,,,].length

Output: 3
You answered: 4

  • 마지막 콤마는 trailing commas로 인식된다
example
1
2
3
4
5
6
7
const arr = [
1, //
2,
3,
];

console.log(arr.length); // 3
  • arr 배열에 원소가 3개 있고, 콤마도 3개 있다

  • trailing commas 는 나중에 원소를 추가할 때 불편하지 않도록 하는 좋은 녀석이다

  • 문제에서는 마지막 콤마를 trailing commas 로 하고, undefined이 3개 있는 배열에서의 length를 물어본 것이다

  • eslint에서는 룰에 따라 trailing commas 를 사용할지 안 할지 선택할 수 있다

  • 참고 ; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas

3. [1, 2, 3] + [4, 5, 6]

Output: “1,2,34,5,6”
You answered: NaN

  • 배열 더하기를 사용해본 적이 없어서 몰랐다
  • 내 예상은 +로 넘버로 캐스팅되어서 숫자가 아니니까 NaN이고 NaN 끼리 더해서 NaN이 나올 것을 예상했는데
  • 알고 보니 문자열로 캐스팅이 되는 것이었다…
  • [1, 2, 3] + [4, 5, 6] === [1, 2, 3].toString() + [4, 5, 6].toString()

4. 0.2 + 0.1 === 0.3

Output: false
You answered: false

  • 이 문제는 맞았다!
  • 이전에 자바스크립트에 부동소수점 계산 문제가 있다는 것을 경험하고 mathjs 라이브러리를 통해 정확한 계산을 해본 적이 있다
  • 0.2 + 0.1 === 0.30000000000000004 // true

5. 10,2

Output: 2
You answered: 10

  • 이게 뭔가 싶다 ㅋㅋ
  • 이런 코드를 작성할까 싶다
  • 설명으로는 간단하게 맨 마지막 값을 반환한다고 되어있다
example
1
2
const a = (1, 2, 3);
console.log(a); // 3

9. true == “true”

Output: false
You answered: true

  • 서로 다른 타입을 Abstract Equality Comparison하면 같은 타입으로 coercion 된다
  • 참고 ; https://262.ecma-international.org/11.0/#sec-abstract-equality-comparison
  • 참고 링크에 의하면 정해진 우선순위에 따라 결괏값을 반환하는데,
  • boolean과 string 비교는 number, string 으로 1차 변환되고,
  • number, string에서 number ,number로 변환되어 비교된다
  • 결국 true == "true" -> 1 == "true" -> 1 == NaN -> false 가 된다

11. “” - - “”

Output: 0
You answered: SyntaxError

  • - 마이너스가 2번 연속 나오는데 이게 왜 문법 에러가 아니지… 충격
  • 생각해보니까 10 - (-10) 같은 게 떠올랐다
  • -를 붙여서 --로 썼다면 SyntaxError 라고 한다
    - -“”; // -> 0
    –””; // -> SyntaxError

12. null + 0

Output: 0
You answered: NaN

  • nullundeifend, NaN이랑 다르게 0으로 변환된다
  • 참고로 false도 넘버로 캐스팅하면 0으로 변환된다

13. 0/0

Output: NaN
You answered: Infinity

  • 0/0NaN
  • 1/0Infinity

18. true + (“true” - 0)

Output: NaN
You answered: 2

  • "true"가 숫자로 캐스팅되면 1이 아니라 NaN이다
  • "true"를 1로 생각하고 문제를 풀었다 ㅠ

21. NaN === NaN

Output: false
You answered: true

  • 이건 너무…
example
1
2
3
NaN === NaN; // -> false
Object.is(NaN, NaN); // -> true
isNaN(NaN) == isNaN(NaN); // -> true
  • NaN 비교는 이런 식으로 해야 생각대로 동작한다

23. undefined + false

Output: NaN
You answered: 0

  • undefinedNaN 으로 변환된다

참고

🍌 바나나 만들기

make banana
1
('b' + 'a' + +'a' + 'a').toLocaleLowerCase();

prototype function vs. method function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(name, nickname) {
this.name = name;
this.nickname = nickname;

// method function
this.sayHi = function () {
console.log(this.name, this.nickname, 'hello', this);
};
}

const sung = new Person('sung', 'superpower');
const sung2 = new Person('sung', 'superpower');

console.log(sung.sayHi === sung2.sayHi); // false

Person.prototype.sayHello = function () {
console.log('sayHello prototype');
console.log(this.name, this.nickname, 'hello', this);
};

console.log(sung.sayHello === sung2.sayHello); // true
  • 생성자가 실행될때마다 새로운 메서드 함수가 생성된다
  • 프로토타입으로 정의된 함수는 모든 인스턴스가 하나의 함수를 바라본다

참고

javascript this

나는 평소 js에서 this나 prototype을 활용해본 적이 없다
c, java를 하다가 js를 넘어왔는데, 공부하면서 use strict를 기본으로 사용했다
리액트는 타입스크트와 함께 시작했고,
리액트에서 컴포넌트를 만들 때도 함수형으로 작성을 시작해서 this를 사용해본 경험이 없었다
자연스럽게 thisless한 코드를 작성하게 되고 선호하게 되었다
근데 this를 사용한 코드를 볼 때, 해석할 수 있어야 한다
그동안 애매하게 알고 있었던 this에 대해 공부해보았다

this

  • use strict / default(sloppy mode)

최상위 스코프에서

  • window / windows

함수안에서 (function)

  • undefined / window

객체안에서

  • 자기 자신{} / 자기 자신
  • 객체 멤버로 화살표 함수가 있는 경우 : 자기 자신{}이 아니라 자기 자신{}보다 한 단계 위

화살표함수에서

  • window / window

정리

  • strict mode일 때와 아닐 때 동작이 달라진다
  • strict mode가 아닌 경우(sloppy mode)는 생각하지 말자
  • 화살표 함수는 항상 한 꺼풀 위와 동일한 this
  • 함수(function keyword)와 객체의 경우 자기 자신만의 스코프를 만들고 자기를 바라본다
  • this는 누가 실행했냐에 따라 달라진다
  • this를 고정시키려면 bind, apply를 사용한다

생각

  • this, 컨텍스트, 스코프 체인, 렉시컬 스코핑 뒤죽박죽 개념이 떠올라서 항상 헷갈린다

참고

js event loop

https://youtu.be/8aGhZQkoFbQ 을 보고 정리

  • 비동기 코드는 webapi에서 실행된다
  • 실행이 완료되면 큐에 들어간다
  • 큐에서 스택에 아무것도 없기를 기다렸다가
  • 스택에 들어가고 실행된다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 첫 번째로 스택에 들어감
console.log(1);

// 두 번쨰로 스택에 들어가고 바로 webapi로 들어감
setTimeout(
// webapi에서 큐로 던져짐. console.log(3)이 끝나고 스택이 텅텅 비워져서야 네 번째로 스택에 들어감
() => {
console.log(2);
},
0
);

// 세 번째로 스택에 들어감
console.log(3);

// 1 3 2

참고

js spread syntax `...` 연산자

1
2
3
4
5
6
const arr = [1, 2, 3];
console.log(arr); // [1,2,3]
console.log(...arr); // 1 2 3
console.log(1, 2, 3); // 1 2 3
console.log([...arr]); // [1,2,3]
console.log(arr === [...arr]); // false
  • 리액트를 처음 공부할 때 자주 접하면서 알게 된 연산자다
  • 상태를 변경하려면, 완전히 새로운 객체를 대입시켜줘야 한다
  • (참조하는 주소를 이전이랑 다르게 해야 state 갱신이 일어났다고 판단하고 화면을 갱신한다)
  • 자바스크립트에는 괴랄한 문법이 많구나 느꼈었다
  • 한 꺼풀 벗겨내는 연산자라고 생각하면 쉽다

배열에서 사용

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 pram = [1, 2];

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

add(...pram); // === add(1, 2); // 3
}

// 함수 파라미터
{
const pram = [1, 2, 3, 4, 5];

function add2(...args) {
return args.reduce((p, c) => p + c, 0);
}

const result1 = add2(...pram);
console.log(result1);
const result2 = add2(10, 20, 30);
console.log(result2);
}

// 배열 이어 붙이기
{
const prefix = 111;
const subfix = 999;
const arr = [1, 2, 3];
const newArr = [prefix, ...arr, subfix];
console.log(newArr); // [111, 1, 2, 3, 999]
}

객체에서 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 객체 복사 및 값 일부 수정
{
const obj = {
a: 1,
b: 2,
};
const newObj = {
...obj,
b: 10,
};

console.log(obj === newObj); // false
console.log(obj); // b===2
console.log(newObj); // b===10
}

참고

js array.sort()가 원본을 바꾼다

  • 가끔 원본이 바뀌어서 예상치 못한 동작을 할 때가 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
// 원본인 arr 이 정렬됨
{
const arr = [1, 2, 3];
const sortedArr = arr.sort((a, b) => b - a);
console.log(arr === sortedArr); // true
}

// 원본인 arr를 살리면서 정렬된 새 배열 얻기
{
const arr = [1, 2, 3];
const sortedArr = [...arr].sort((a, b) => b - a);
console.log(arr === sortedArr); // false
}
  • [...arr] 이런 식으로 배열을 복사할 수 있다

하나 더, 배열 복사할 때 주의할 점

  • 그런데 number[] 타입이 아니라 {...}[]같은 객체 배열이라면
  • [...arr] 연산으로 새로운 배열이 만들어지기는 하지만
  • 원소 하나하나는 여전히 call by reference 이기 때문에
  • 완전한 복사본(깊은 복사)을 얻은 게 아니라는 점을 유의해야 한다

참고

regexp lastIndex

정규식 test는 true인데 exec가 동작하지 않는다?!

work!
1
2
3
4
5
6
7
8
9
if (regexp.test(line)) {
// 내부 포인터 재설정
regexp.lastIndex = 0;

let match = regexp.exec(line);
while (match) {
// ...
}
}
  • regexp.lastIndex = 0

잘못된 코드

not work!
1
2
3
4
5
6
7
8
if (regexp.test(line)) {
// 여기서 lastIndex 갱신
let match = regexp.exec(line); // 갱신된 lastIndex부터 검색을 시작해서 첫번째 결과가 스킵됨.
while (match) {
// 라인에 일치하는 패턴이 한개라면 반복문한 한번도 돌지 않음
// ...
}
}
  • 처음에 작성한 코드다
  • 파일의 라인 한 줄 한 줄 읽으면서
  • 정규식 패턴과 일치하는 라인이라면
  • 결괏값을 받도록 만들었는데, 이상하게도 제대로 동작하지 않았다
  • 알고 보니까 test() 메서드도 lastIndex를 갱신했었다…
  • 사실 저 if (regexp.test(line)) {은 필요 없는 코드 같다
  • testexec를 같이 쓸 때 주의가 필요하다

참고

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

참고