본문 바로가기

FrontEnd 공부/자바스크립트 이론

You don't konw JS- 4장 강제변환

4.1 값 변환

: 어떤 값을 다른 타입의 값으로 바꾸는 과정

    → 명시적 : 타입캐스팅 - 코드만 봐도 의도적으로 타입변환을 일으킨다는 사실이 명확(명백한)

    → 암시적 : 강제변환 - 다른 작업 도중 불분명한 부수효과로부터 발생하는 타입변환()

  • 강제변환시 문자열, 숫자, 불리언 같은 원시값 중 하나가 됨!
  • 객체, 함수 같은 합성 값 타입을 변환될 일 ❌ → ‘박싱'은 값을 감싸는 것, 강제변환 ❌
var a = 123;
var b = a + ""; //암시적 강제변환
var c = String(a);  // 명시적 강제변환

4.2 추상연산

: 값이 특정 타입으로 변환되는 기본규칙

  • ToString
  • ToNumber
  • ToBoolean
  • ToPrimitive

1) ToString

: ‘문자열이 아닌 값'→’문자열'의 변환 작업

 

    (1)내작 원시값은 본연의 문자열화 방법이 정해져 있음

  • null → “null”
  • undefined→ “undefined”
  • true→”true”

    (2)숫자 : 그냥 문자열로 바뀜 (너무 작거나 큰값은 지수 형태로 바뀜)

var a = 12.31*10000000000000000 *100000000000000; 
console.log(a.toString()); //1.231e+31

 

   (3)기본적으로는 Object.prototype.toString()toString()메서드가 내부 Class를 반환

   

   (4)자신이 toString() 메서드를 가진 객체는 자신의 toString()메서드를 호출

  • 배열 → 원소값이 ‘,’로 분리된 형태로 출력
var arr = [1,2,3,1];

console.log(arr.toString()); //1,2,3,1
  • JSON 문자열화
    • JSON.stiringify() : toString()과 기본적인 로직은 같음
    • 안전값 : JSON 표현형 으로 나태낼 수 있는 값은 모두 문자열 변환 가능
    • 안전한 값이 아닌 경우 : 다른 언어로 인식하여 JSON값으로 쓸 수 없는, 표준 JSON규격을 벗어난 값
      • ex) undefined, 함수, 심볼, 환형 참조객체
        • 환형참조객체 : 마지막 객체가 첫번째 객체를 참조하는 등 순환 참조가 발생하여 메모리 누수를 유발하는 객체, HTML DOM객체와 자바스크립트 간 연동 시 실수로 환형참조 하는 경우 종종 발생 → JSON.stringify : 에러
      • 이런 값이 배열에 포함되어 있으면 null로 바뀜
      • 객체 프로퍼티에 있으면 지움
    console.log(JSON.stringify("12"));  //"12"
    console.log(JSON.stringify(12));    //12
    console.log(JSON.stringify(null));  //null
    console.log(JSON.stringify(undefined)); //undefined
    console.log(JSON.stringify(true));  //true
    console.log(JSON.stringify(function (){})); //undefined
    console.log(JSON.stringify([1,12,undefined,31]));   //[1,12,null,31]
    console.log(JSON.stringify({a:12, b:function (){}}));   //{a:12}
    
    • 부적절한 JSON값이나 직렬화하기 곤란한 객체값을 문자열화 하려면 toJSON()메서드를 따로 정의
      • toJSON() : 문자열화하기 적당한 JSON 안전 값으로 바꾸는 것( JSON 문자열로 바꾸는 것 ❌ )

2) ToNumber

: ‘숫자가 아닌 값' →’수식 연산이 가능한 숫자’

  • true →1, false → 0, undefinedNaN, null →0
  • 숫자 리터럴 규칙과 비슷
  • 변환이 실패하면 NaN
  • 8진수는 10진수로 처리
  • 객체 : 동등한 원시 값으로 바꾼 뒤 toNumber변환

3)ToBoolean

   (1) falsy

     : boolean 문맥에서 특정 값을 false 로 형변환

  • false
  • 0
  • -0 : 음수 0
  • 0n : Bigint
  • “” : 빈 String
  • null
  • undefined
  • NaN

→ 이 falsy가 아닌 값을 가지면 boolean에서는 모두 true

      → 빈 배열([])은 true로 변환❗️

if(false)
    console.log("hello false");
if(0)
    console.log("hello 0");
if(-0)
    console.log("hello -0");
if(0n)
    console.log("hello 0n");
if(null)
    console.log("hello null");
if(undefined)
    console.log("hello undefined");
if(NaN)
    console.log("hello NaN");
if ("")
    console.log("hello");

*️⃣ 참고 *️⃣

  • 빈 배열의 false 반환 방법

         → length 프로퍼티 이용 : 빈 배열.length = 0을 반환

var arr = [];

if(arr)
    console.log("arr is not empty");  
		// 빈 배열이지만 arr is not empty 출력
if(arr.length)
    console.log("arr is not empty"); 
		// arr.lenth = 0 이기에 if 안이 false, 출력 X
  • falsy 객체 : 해당 객체가 boolean값에서 false로 판단되는 객체
    • document.all : 웹 페이지의 요소르 자바 스크립트 프로그램에 가져올 수 있게 함. 그러나 “비표준
      • 그냥 없애려고 하였으나 의존하는 레거시 코드 베이스가 너무 많아서 falsy인 것처럼 돌아가게 함

(2) truthy

: falsy한 값이 아닌 값(falsy 목록에 없는 값)

 


4.3 명시적 강제변환

1) 문자열 ↔ 숫자

  • String(), Number() : 원시 문자열/숫자로 강제변환(ToString과 ToNumber추상연산로직을 따름)
var a = 3;
String(a); // "3"
var b = "15.23";
Number(b); // 15.23
  • 다른 방법
var a = 3;
a.toString(); // "3"
var b = "15.23";
+b; // 15.23
  • a 를 변환할 때는 toString() 메서드를 사용했는데 당연히 a 에는 없기 때문에 객체 래퍼로 박싱해서 호출
  • b 를 변환할 때는 단항연산자(Unary Operator) +,-를 사용하여 강제변환
    • 단항연산자의 경우, 가독성이 떨어지고 실수할 수 있는 확률이 높기 때문에 쓰지 않는 것이 좋음.

2) 날짜 → 숫자

: Date객체 → 숫자로 강제 변환

  • 현재 시각을 타임스탬프로 바꾸기 : Date 객체를 활용
var now = +(new Date());

 

다음 과정으로 변환

  • new Date() : 현재 날짜/시각을 가리키는 객체
  • 단항연산자 + 를 사용해서 객체를 숫자로 강제변환 : ToNumber 추상 연산이 적용
  • valueOf() 메서드를 확인하니, 있고 원시 숫자값 반환
var now1 = (new Date()).getTime();var now2 = Date.now();

이외에도 2가지 방법이 더 있다.


3) 틸드(~) 연산자 활용

: 강제변환 연산자

  • 자바스크립트의 비트 연산자는 32비트 연산만 가능하기 때문에 피연산자를 32비트로 바꾸는 ToInt32 추상 연산을 수행하고 NOT 연산을 수행. 이런 성질은 -1 과 같은 값에 활용될 수 있는데, 여기에 틸드연산을 적용하면 0 이 되기 때문이다. 문자열의 메서드인 indexOf() 는 대상을 못 찾으면 -1 을 반환하는데 보통 다음과 같이 사용한다.
if ("123".indexOf("3") !== -1) {  console.log("찾았다!");}

하지만 -1 이 “실패” 라는 의미를 가졌다는 것을 노출시키기 때문에 그다지 좋지 않고 여기에 틸드 연산을 활용한다.

if (~("123".indexOf("3"))) {  console.log("찾았다!");}

만약에 "4" 를 못찾으면 -1 을 반환하는데 여기다 틸드연산을 적용할 경우 0 이 되어서 false 가 된다. 따라서 찾았을 경우는 true 이므로 위와 같이 사용하게 되는 것이다. 만약, if문 안에 직접적으로 불리언 값을 적어주고 싶다면 !! 를 적용하면 된다.

if (!!~("123".indexOf("3"))) {  console.log("찾았다!");}

틸드연산은 이외에도 비트 잘라내기에 유용, 소수의 소수점 이상을 잘라내기 위해서 사용되고는 한다.

~~123.14; // 123

이외의 방법으로는 Math.floor() 를 사용하지만 음수의 경우 2개의 작동방식이 다르다 는 점을 주의‼️


4) 숫자형태의 문자열 파싱

앞서배운 문자열 강제변환과는 차이점이 있는데, 비 숫자형 문자를 허용한다는 것이다.

Number("123a"); // NaNparseInt("123a"); // 123

단, parseInt() 의 경우에는 첫번째 인자로 문자열을 받는다. 비 문자열은 문자열로 강제변환되는데, 여기서 예측하기 힘들기 때문에 그냥 비 문자열은 인자로 전달하지 말자. 두번째 인자로는 기수(radix)를 전달하는데, 이 정보를 기반으로 첫번째 인자로 전달된 문자열을 파싱한다. 만약, 두번째 인자가 없다면 ES5 이후부턴 10진수로 처리하고 이전에는 첫번째 문자만 보고 추정한다.


5) 비 불리언 → 불리언

ToBoolean 추상연산과 동일하게 엄청 간단하다.

Boolean([]); // trueBoolean({}); // trueBoolean("false"); // trueBoolean("0"); // trueBoolean(null); // falseBoolean(undefined); // false

이외에도 단항연산자 ! 를 사용할 수 있다.

!!null; // false

 

4.4 암시적 강제변환

: 숨겨진 형태로 일어나는 타입변환으로 명백하게 보이지 않는 타입변환의 총칭

1) 문자열 ↔ 숫자

피연산자 한쪽이 문자열이라면 + 연산자는 항상 문자열 붙이기를 한다. 만약, 피연산자가 객체라면 다음 과정을 거친다.

  • 객체를 ToPrimitive 추상연산으로 원시값으로 변환
  • 원시값을 ToString 추상연산으로 문자열로 변환
  • 문자열 붙이기를 한다.

따라서 아래와 같은 일이 일어난다.

var a = [1,2];
var b = [3,4];
a + b; // 1,23,4

보통 숫자를 문자열로 변환할 때의 가장 많이 쓰는 일반적인 방식은 다음과 같다.

var a = 4;
a + ""; // "4"

주의할 점은 String(a) 의 경우 toString() 을 바로 호출하지만 암시적 변환의 경우 valueOf() 를 먼저 호출한다는 사실이다. ES5 9.1 ToPrimitive 연산을 참고하면 보다 확실히 알 수 있다.


2) 비 불리언 → 불리언

불리언으로의 암시적 강제변환이 일어나는 경우는 다음과 같다.

  • if 문의 조건 표현식
  • for 문의 2번째 조건 표현식
  • while 및 do~while문의 조건 표현식
  • 삼항연산자의 첫번째 조건 표현식
  • 논리연산자 및 좌측 피연산자

3. &&와 || 연산자

: 보통 다른 언어에선 &&와 ||의 반환값이 불리언 값이지만 자바스크립트에선 피연산자 중 하나로 귀결된다.

var a = 1;
var b = null;
a && b; // null
a || b; // 1
  • && : boolean으로 변환시 앞쪽이 true일 경우에 뒤쪽을 반환
  • || : boolean으로 변환시 앞쪽이 false일 경우에 뒤쪽을 반환

4.5 느슨한/엄격한 동등비교

: 흔히 == 와 === 의 차이점을 타입을 비교하냐의 여부로 따지고는 한다. 하지만 엄격히 말하자면, 강제변환을 허용하는가의 여부 이다. 즉, == 는 강제변환을 허용하고 === 는 강제변환을 허용하지 않는다.

결국 == 에서 암시적 강제변환이 발생하며 이는 여러가지 예측하기 힘든 결과들을 내놓는다.

 

[느슨한 동등 비교(==)의 규칙]

  • 피연산자 중 하나가 문자열일 경우 : 문자열을 숫자로 강제변환
  • 피연산자 중 하나가 불리언일 경우 : 불리언을 숫자로 강제변환
  • 피연산자가 null 또는 undefined 일 경우 : 양쪽이 모두 null 이나 undefined 일 경우에만 참이다.
  • 피연산자가 비객체와 객체인 경우 : 객체를 원시값으로 강제변환
  • 따라서 다음이 성립
"0" == false; // true
    • 피연산자 중 하나가 불리언이므로 숫자로 변환 "0" == 0
    • 피연산자 중 하나가 문자열이므로 숫자로 변환 0 == 0

이런 결과들을 보고나면 무조건 엄격한 동등 비교인 === 를 쓰면 되는 것이 아닌가? 라고 생각할 수 있다. 저자의 의견은 무조건적으로 사용하지 말고 그 근본 원리를 이해하고 적절히 사용하는 것이 바람직하다고 한다.

예를 들어, 피연산자 중 하나가 null 이나 undefined 라면 == 를 쓰는 것이 훨씬 간단하다.


1) 추상 동등 비교

  • NaN은 그 자신과도 동등❌
  • +0과 -0은 동등❌

(1) 문자열 → 숫자 변환

var a = 42;
var b = "42";

a === b;// false
a == b;// true
💡 [비교 과정]

x == y 에서

     1. Type(x)가 Number이고, Type(y)가 String이면, x == ToNumber(y) 비교 결과 반환
     2. Type(x)가 String이고, Type(y)가 Number면, ToNumber(x) == y 비교결과 반환

→ 위의 예시에서는 Type(a) = Number, Type(b) = String 이므로, “42”가 42로 변환되어 비교

 

 

 

(2)비불리언 → 불리언

       : boolean 타입과 비교할 때는, boolean을 ToNumber()값에 넣어서 비교하기 때문에 주의‼️

var a = "42";
var b = true;

a == b;// false
💡 [비교 과정]

x == y 에서

    1. Type(x)가 불리언이면, ToNumber(x) == y 비교결과 반환
    2. Type(y)가 불리언이면, x == ToNumber(y) 비교 결과 반환 

    → 위의 예시는 2에 해당하므로 truetoNumber로 변환한 1 반환

    → 42 == 1 이므로 false (”42”도 42로 변환 )

 

 

 

(3)nullundefined를 느슨한 동등비교(==)를 하면 서로에게 타입을 맞춤

💡 [비교 과정]

x == y 에서

   1. x가 null이고, y가 undefinedtrue 반환
   2. x가 undefined이고, y가 null이면, true 반환 
var a = null;
var b;   //undefined

a == b;        // true
a == null;    // true
b == null;    // true

a == false;    // false
b == false;    // false
a == "";    // false
b == "";    // false
a == 0;        // false
b == 0;        // false

 

 

 

(4)객체의 경우 ToPrimitive() 결과를 통해 비교

💡 [비교 과정]

x == y 에서

   1. Type(x)가 String 또는 Number이고 Type(y)가 Object라면, x == ToPrimitive(y)비교결과 반환
   2. Type(x)가 ObjectType(y)가 String 또는 Number이면, ToPrimitive(x) == y 비교 결과 반환

var a = 42;
var b = [ 42 ];

a == b;// true

    → 위의 예시에서는 toPrimitive([42]) →”42”이므로 42 == ”42”

 

한번 더 타입변환을 거쳐 42 == 42 이므로 true 반환

  • null과 undefined의 경우는 객체 래퍼가 따로 없으므로 예외

2) 희귀사례

"0" == false;            // true
false == 0;                // true
false == "";            // true
false == [];            // true
"" == 0;                // true 
"" == [];                // true
0 == [];                // true

암시적 강제변환을 안전하게 사용하려면 다음을 명심하자.

  • 피연산자 중 하나가 true/false일 가능성이 있으면 절대 == 연산자를 쓰지 마라.
  • 피연산자 중 하나가 [], "", 0이 될 가능성이 있으면 가급적 == 연산자를 쓰지 마라.

4.6 추상 관계 비교

: < 연산에 대한 것으로, ToPrimitive 연산을 한 후의 피연산자가 모두 문자열일 경우와 그렇지 않은 경우로 나뉨

  • 피연산자가 모두 문자열 : 알파벳 순서로 비교
var a = ["abc"];
var b = ["zd"];
a < b; // true
  • 피연산자가 모두 문자열 ❌ : ToNumber를 통해 숫자로 변환해서 비교
var a = [1];
var b = ["2"];
a < b; // true

단, <= 의 작동방식은 기존에 알고 있던 대로 동작하지 않는다. 의 결과를 부정한 방식을 적용

var a = {};
var b = {};
a < b; // false
a >= b; // true

: 여기서 와 b 는 문자열 [object Object] 로 변환되기 때문에 a<b 의 결과는 false

   → 따라서, >= 의 결과는 그걸 부정한 참

 

 

 

 

질문이나 궁금하신점 또는 제가 잘못 알고있는 정보는 댓글 부탁드립니다😊