[JS] 암묵적 타입 변환 (Implicit coercion)

JIOO·2023년 12월 4일
0

ES6

목록 보기
13/17

🙋‍♂️ 우선 이 글의 바탕이 되는 참조 블로그를 소개 한 후 포스팅 함을 알립니다.
https://velog.io/@design0728/%EC%95%94%EB%AC%B5%EC%A0%81-%ED%83%80%EC%9E%85-%EB%B3%80%ED%99%98implicit-coercion-0gqe2tex


자바스크립트의 암묵적 타입 변환

자바스크립트의 암묵적인 타입 변환(implicit coercion)은
예상치 못한 타입을 받았을 때 예상 가능한 타입으로 바꿔준다고 생각하면 된다.

대표적인 사례로는 사용자가 숫자 값을 넘겨야 하는 곳에 문자를 넣을 수도 있고 배열, 객체, 불리언을 넣을 수도 있다.

만약 이러한 일이 발생했을 때 자바스크립트 엔진은 사용자가 잘못 넣은 타입을 "올바른 타입"으로 변환하려고 시도한다.

이 작용은 코드가 간단할 땐 장점으로 작용할 수 있겠지만 코드가 고도화 될 수록 치명적인 단점으로 바뀔 수 있다.


🔎 장단점의 예시는 다음과 같다.

장점의 사례

function calcurlator(value) {
    return 3 * value
}

calcurlator("3")

// 장점은 간단하다 

단점의 사례

개발자는 하루 방문자 수를 계산하는 함수를 만들어 주려고 한다.
사용자는 공책 1장에 열명의 이름을 적고, 공책의 장수를 세어 함수에 입력하게 된다.

개발자는 사용자가 잘못 입력 했을 때를 대비해 하루 방문자수가 NaN으로 출력 될 시 잘못된 입력이라고 알림창이 뜨게 끔 에러처리를 해놓았다.

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9 ~ 99]

const number = window.prompt("숫자를 입력하시오.") // 사용자의 입력값 100

calcurlator(array.includes(number))

function calcurlator(value) {
  const result = 10 * value
  if (isNaN(result)) {
      window.alert("잘못된 입력입니다.")
  } else {
      window.alert("오늘 방문자수는 " + result + " 명입니다.")
  }
}

사용자는 하루 방문자수 1000명을 달성해 기분 좋게 함수에 100이라는 입력값을 넣었지만,
결과값은 0명 이라고 출력된다. 마음이 급한 사용자는 직접 함수를 수정해보려 검색을 하게된다.

📖 사용자가 알게된 정보

1. 함수안의 받아온 인자를 제대로 받아왔는 지 확인할 것
2. 숫자 + 문자를 넣었을 경우 NaN으로 바뀐다는 사실
3. 사용자는 1번을 콘솔로 찍어본 결과 false라는 문자를 확인하였다.

사용자는 알아본 정보를 바탕으로 10 + "false(문자)"가 되는거 까지 알아냈다.

이를 추론하면 if문의 첫번째 조건이 성립되어 첫번째의 값을 출력해야하지만,
함수는 계속 else의 값을 출력해내어 결국 사용자는 당일안에 문제를 해결하지 못하였다.

🔑 이 문제가 일어나게 된 이유

위에 언급한 자바스크립트 엔진이 사용자가 잘못 넣은 타입을 "올바른 타입"으로 변환하려고 시도하여 
입력된 false의 값을 0으로 타입변환을 하였기 때문이다.
이것이 자바스크립트의 암묵적인 타입 변환(implicit coercion) 이고 
이런 사안들을 미리 방지하도록 만들어진 것이 타입스크립트이다.

앞으로 이 글에선 자바스크립트의 이러한 암묵적 타입변환이 어떤 것이 있고 암묵적으로 타입을 어떻게 바꾸는 지 알아 볼 것이다


👨‍🏫 숫자 표현식에서 숫자가 아닌 값

문자열

사용자가 숫자 표현식에서 사칙연산을 제외한 문자열을 인자로 넘겼을 때 숫자의 암묵적 타입변환 프로세스는 받아온 문자열을 자바스크립트 내부에 내장된 Number 함수에 넣고 실행시킨다고 보면 된다.

function inputString(number) { //예제 함수
  return 3 * number
}
const result = inputString("3");

inputString 함수는 인자로 "3"를 받고 함수 내에서 return값을 출력하기 위해 계산에 들어간다.
이때 자바스크립트는 인자로 받은 3이 문자열이라는 점을 발견하여, 암묵적으로 인자를
number = Number("3")로 바꾼다.

그 외의 사례들은 다음과 같다

3 * "3" // 3 * 3
3 * Number("3") // 3 * 3
Number("5") // 5

Number("1.") // 1
Number("1.34") // 1.34
Number("0") // 0
Number("012") // 12

Number("1,") // NaN
Number("1 + 1") // NaN
Number("1a") // NaN
Number("la") // NaN
Number("one") // NaN
Nuber("text") // NaN

문자열과 +연산자

연산자는 다른 사칙연산 연산자들과는 다르게 2가지 기능을 한다.

1. 수학적 덧셈
2. 문자열 합치기 
function inputString(number) { //예제 함수
  return 3 + number
}
const result = inputString("3");

이전과 같은 똑같은 함수이다

결과값은 ?

6을 생각 했다면 뭐 맞는 말이긴 하다.
하지만 위에서 봤듯 + 연산자는 2가지 기능을 한다고 하였다.

덧셈 함수가 있다는 가정, 문자열이 +덧셈함수의 인자로 주어졌을 때, 인자와 더하는 것이 숫자든 문자열이든 자바스크립트 암묵적 타입변환 프로세스에선 인자와 더하는 앞의 값을 문자열로 바꿔버린다.

그 외의 사례들은 다음과 같다

// concatenation
1 + "2" // "12"
1 + "js" // "1js"

// addition
1 + 2 // 3
1 + 2 + 1 // 4

// addition, then concatenation
1 + 2 + "1" // "31"
(1 + 2) + "1" // "31"

// concatenation all through
1 + "2" + 1 // "121"
(1 + "2") + 1 // "121"

배열

배열의 암묵적 타입변환에선 for문으로 배열 각각의 item들과 계산식을 진행하고 않고

"문자열" 또는 숫자 + [1,2,3] // 계산식에서 그냥 배열을 더하게 되면 자바스크립트는 암묵적으로 배열 자체를 toString 내장함수를 사용하여 문자열로 바꾸고 배열을 제거한다.

만약 배열에 값이 없고 숫자와 +을 포함하지 않는 수학적 계산식을 진행할 때 빈 배열은 0이 된다.

그 예시는 이렇다

4 * [] // 0
4 / [2] // 2

// similar to 
4 * Number([].toString())
4 * Number("")
4 * 0

4 / Number([2].toString())
4 / Number("2")
4 / 2

NaN

NaN === NaN // false

const notANumber = 3 * "a" // NaN

notANumber == notANumber // false
notANumber === notANumber // false

if( notANumber !== notANumber) // true

NaN은 자바스크립트에서 유일하게 자기 자신과 같지 않은 값이다.

그래서 ECMAScript6는 NaN을 체크하기 위한 메소드(Number.isNaN)를 만들었다.


Falsy, Truthy

모든 자바스크립트 값은 true나 false로 변환될 수 있는 특성을 갖고 있다.
true로 형변환을 강제하는 것을 truthy라고 하고 false로 형변환을 강제하는 것을 falsy라고 한다.


다음은 자바스크립트에서 반환시에 falsy로 취급되는 값들이다.

1. false
2. 0
3. null
4. undefined
5. ""
6. NaN
7. -0

이 외에는 전부 truthy로 취급된다.


if (typeof counter === "number")
아래 함수는 number 타입의 변수를 받아 실행하도록 하고싶어 만든 함수이다.

const add = (number) => {
	if(!number) new Error("Only accepts arguments of type: number")
	// your code
}

add(0) // Error: Only accepts arguments of type: number

위와 같은 함수의 경우 인자 값으로 0을 주면 의도치 않은 에러가 발생한다. 왜냐하면 0은 false로 표현 되기도 하기 때문이다.

💡 이문제를 ↓ 이렇게 바꿔주면

const add = (number) => {
	if(typeof number !== "number") new Error("Only accepts arguments of tyep: number")
}

암묵적 타입변환으로 인한 사이드이펙트를 방지 할 수 있다.

추가내용

암묵적 Falsy의 실제 사례

사실 이 글을 작성하고 있는 이유이다.
리액트로 개인 사이드 프로젝트를 진행하면서 이 사례를 접했기 때문이다

 const [pageData, setPageData] = useState([]);

  useEffect(() => {
    if (!URLID) URLID = location.state;
    async function init() {
      await db
        .collection("post")
        .doc(URLID)
        .onSnapshot((snapshot) => {
          const postArray = { ...snapshot.data() };
          setPageData(postArray);
        });
    }

    init();
  }, []);

위 코드는 파이어베이스에서 비동기로 데이터를 불러와 pageData라는 State에 데이터를 넣는 함수이다.

useEffect(() => {
    if (pageData.length) {
      setFileNameArr(pageData.fileName);
      let imgTarget = Array.from(document.getElementsByClassName("att"));
      let grid = document.getElementsByClassName("grid");
      imgTarget.map(function (a, i) {
        naturalWidths = document.getElementsByClassName("att")[i].naturalWidth;
        clientWidths = document.getElementsByClassName("att")[i].offsetWidth;
        if (naturalWidths < clientWidths)
          imgTarget[i].classList.add("natural-size");
      });
      if (imgTarget.length > 1) grid[0].classList.add("grids");
    }
  }, [pageData]);

PageData State가 업데이트되서 빈 배열이 아니게 될 때 if의 조건이 실행되고 아니면 else가 실행된다.

문제는 PageData State가 업데이트 되서 UseEffect가 작동 하는데도 계속 else의 조건이 작동하는 것이다

왜그런지 하루종일 찾아본 결과

원래 PageData의 기본 값 [ ] 빈 배열이 false를 뜻하고 비동기 처리 때문에 PageData에 값이 들어와도 배열의 렌더링이 늦게 되는 것 때문이였다.

그래서 계속 else의 조건으로 빠지는 것이고 이 문제를 해결하기 가장 쉬운 방법은
PageData.length 를 PageData.length !== 0으로 바꾸면 된다. 하지만 구글에 찾아보고 챗 GPT 한테 물어봐도 이 두가지 표현식은 표현하는 방법만 다르지 같다고 나온다.

💡 결국 문제점으로 발견한 것이 자바스크립트의 암묵적 타입변환이다.


profile
프론트엔드가 좋은 웹쟁이 with Notion (요즘은 노션과 병행 중)

0개의 댓글