js를 사용하며 쉽게 실수할 수 있는 요소들

김성현·2022년 5월 26일
0
post-thumbnail

자바스크립트는 인터프리터 언어로 처음에는 간단한 사용을 위해 만들어 진 언어다.

하지만 이젠 단언코 아니라고 장담 할 수 있다.

자바스크립트는 이젠 서버 프로그래밍에도 쓰일 정도로 성공적으로 주류 언어가 되었다.

주류 언어가 될 수 있던 이유는 물론 여러가지가 있지만, 그 중 하나는 인터프리터 언어로서 간편한 사용에 있었을 것이다.

하지만 나는 개인적으로 인터프리터 언어가 사용하기 쉽다는 이야기에 반대하는 사람 중 하나다.

물론 코딩할 때, 만드는 것이 쉽다는 점을 인정한다.

그러나 이를 조금만 진지하게 사용하려면 금방 언어적으로, 근본적으로 해결하기 힘든 문제들이나, 이해하기 힘든 버그들을 마주치고는 한다.

특히 자바스크립트는 개인적으로 인터프리터 언어 중에서도 예상하기 힘든 특징이 많은 언어라고 생각하는데 지금까지 마주쳤던 자바스크립트 본연의 특징 중 이상하고, 예측하기 힘들었던 특징들을 몇가지 이야기 해 보고 싶다.

null과 undefined

단언컨데 자바스크립트에서 제일 난해한 부분은 null과 undefined이다.

둘은 유사한 의미이면서 약간 다른 의미로 사용되는데, 사실 대부분의 경우, null과 undefined는 서로 섞어 사용하더라도 큰 차이는 없을 것이라 생각되었다.

그러나 이를 값으로 사용할 때에는 문제가 생길 때가 있는데 아래를 생각해 보자.

나는 자바스크립트를 배운 처음부터 ===, !==연산자만 사용했기에 쉽게 눈치채지 못했는데
null == undefined의 결과에 대해 우연히 알게 되었을 때 놀람을 금치 못했었다.

null == undefinedtrue이다.
하지만 null === undefinedfalse이다.

이는 코드 상에서 문제를 일으킬 가능성은 적지만, 만약 당신이 자바스크립트를 처음 접해서 ==로 비교를 한다면 문제를 일으킬 가능성이 있다.

typeof null

나는 재귀적으로 객체를 순회하며 값을 변환하는 함수를 만들면서 우연히 이 문제를 알게 되었다.

typeof nullobject이다.

typeof undefinedundefined여서 당연히 typeof nullnull이라 생각했는데, 놀랍게도 아니였다.

이는 나처럼 값을 런타임에 typeof로 가져올 때 object를 감지한다면 반드시 null 체크를 해 줘야 한다는 것을 의미한다.

Array의 인덱스

자바스크립트의 배열은 오브젝트이다.

우리는 배열의 특정 요소에 접근 할 때 다음과 같이 접근한다.

const n = ['a', 'b', 'c']
console.log(n[0])

그런데 자바스크립트의 배열은 오브젝트의 특별한 형태라고 했다.

그리고 오브젝트는 키를 string 타입으로 받는다.

그렇다면 어떻게 배열은 키를 0같은 숫자로 받을 수 있을까?

놀랍게도... 자바스크립트는 배열의 요소에 접근할 때 0 대신 '0'으로 배열에 접근한다.

즉 아래와 같은 코드에서 n2를 변경하는 방법은 완벽하게 n1을 변경하는 방법하고 동일하다.

const n0 = ['a', 'b', 'c'];
const n1 = ['a', 'b', 'c'];
const n2= ['a', 'b', 'c'];

n1[0] = 'aaa';
n2['0'] = 'aaa';

console.log(n0);
console.log(n1);
console.log(n2);

console.log(%HaveSameMap(n0, n1));
console.log(%HaveSameMap(n0, n2));

위 코드를 실행시키면 결과는 다음과 같이 나온다.


> node --allow-natives-syntax .\test.js
[ 'a', 'b', 'c' ]
[ 'aaa', 'b', 'c' ]
[ 'aaa', 'b', 'c' ]
true
true

%HaveSameMap은 v8의 Built-in functions로 프로토타입 간의 비교를 수행한다.
다만 이 함수는 매우 예민해서 약간만 타입이 틀어져도 false를 반환한다.
즉 한마디로 이 함수에서 true를 반환했다는 의미는 두 타입이 오해의 여지가 없이 완벽히 같은 타입임을 의미한다.

이로서 알 수 있는 사실은, 자바스크립트는 []안에 들어온 값을 toString()을 통해 변환해 프로토타입 체인을 검사한다는 사실이다.

즉 아래 코드와 결과를 보면 대체 어떻게 이런 결과가 나왔는지 한번에 이해가 가능할 것이다.

const b = {
    toString() {
        console.log("called!")
        return 'b'
    }
}

const a = {
    b: 1
}

console.log(a[b])

정말... 예상도 못핸 내부 동작방식이였다.

Object의 숨겨진 키

자바스크립트는 object에서 존재하지 않는 키를 찾아가는 경우, undefined 값을 제공한다.

즉 아래와 같은 코드를 예로 들 수 있겠다.

const a = {}
console.log(a.b) // undefined

그러면 위의 사례처럼 b가 존재하는지 아닌지를 확인하기 위해서 undefined 를 사용할 수 있을까?

답은 아니다. 아래와 같은 코드의 결과는 다음과 같다.

const a0 = {}
const a1 = { b: undefined }

console.log(a0.b) // undefined
console.log(a1.b) // undefined

console.log('b' in a0) // false
console.log('b' in a1) // true


console.log(Object.keys(a0)) // []
console.log(Object.keys(a1)) // [ 'b' ]

b를 직접 undefined로 설정하면, 키는 존재하더라도 undefined를 반환 할 수 있다.

사실 이런 특징은 대부분의 경우에는 문제가 되지 않을 수도 있다.

하지만 이 특징이 진짜 문제가 될 수 있는 경우는 아래 코드처럼 JSON으로 변환하는 경우이다.

const a0 = { b: undefined }
const a1 = JSON.parse(JSON.stringify(a0))

console.log(Object.keys(a0)) // [ 'b' ]
console.log(Object.keys(a1)) // []

JSON으로 데이터를 주고받을 때 undefined값은 자동으로 키에서 누락된다.

이런 특징을 잘 이해하지 못했거나, 실수로(대부분은 이 경우다.) JSON.stringify를 호출하면 예상치 못한 에러를 일으킬 수 있으니 조심해야 한다.


자바스크립트는 매우 특이한 동작 방식을 가지고 있다.

따라서 자바스크립트를 짤 때에는 항상 조심해야 한다.

특히 자바스크립트에 조금 익숙해 졌다 싶을 때 위와 같은 문제들을 마주하게 된다.

벼는 익을수록 고개를 숙인다는데, 조금 자바스크립트에 자신감이 생겼을 때 이 문제를 마주했었다.

역시 사람은 겸손해야 하는 것 같다.

profile
수준 높은 기술 포스트를 위해서 노력중...

0개의 댓글