Hyunjung Im

Frontend Developer

github

타입 강제변환

2022-12-28

강제변환 Type Coercion

JS 엔진에 의해 타입이 자동 변환된다는 의미

값 변환

  • 타입 캐스팅: 어떤 값을 다른 타입의 값으로 바꾸는 과정이 명시적인 것
  • 강제변환: ~ 과정이 암시적인 것
    • 암시적 강제변환은 다른 작업 도중 불분명한 Side Effect 로부터 발생하는 타입변환이다.
var a = 42;
var b = a + ''; // 암시적 강제변환
var c = String(a); // 명시적 강제변환
  • 공백 문자열과의 + 연산은 문자열 접합 처리를 의미한다.

추상 연산 (Abstract Operation)

ToString, ToNumber, ToBoolean, ToPrimitive

ToString

  1. 객체 -> string
  • 일반 객체는 특별히 지정하지 않으면 기본적으로 toString() 메서드가 내부 [[Class]]를 반환한다.
const obj = {};
obj.toString(); // [object Object]
  1. 배열 -> string
  • 배열은 기본적으로 재정의된 toString()이 있다. 문자열 변환 시 모든 원소 값이 (각각 문자열로 바뀌어) 콤마로 분리된 형태로 이어진다.
const array = [1, 2, 3];
array.toString(); // "1, 2, 3"

JSON 문자열화

  • JSON 안전 값이 아닌 것
    • undefined
    • 함수
    • symbol
    • 환형 참조
      var o = {};
      var a = { b: 42, c: o };
  • JSON.stringify()는 인자가 안전 값이 아니면 자동으로 누락시키며 이런 값들이 만약 배열에 포함되어 있으면 (배열 인덱스 정보가 뒤바뀌지 않도록) null로 바꾼다. 객체 프로퍼티에 있으면 간단히 지워버린다.
  • JSON.stringify()는 직접적인 강제변환의 형식은 아니지만 밑과 같은 이유로 ToString 강제변환과 연관된다.
    1. 문자열, 숫자, 불리언, null 값이 JSON으로 문자열화하는 방식은 ToString 추상 연산의 규칙에 따라 문자열 값으로 강제변환되는 방식과 동일하다.
    2. JSON.stringify()에 전달한 객체가 자체 toJSON() 메서드를 갖고 있다면, 문자열화 전 toJSON()가 자동 호출되어 JSON 안전 값으로 강제변환된다.

ToNumber

  • 객체는 일단 동등한 원시 값으로 변환 후 그 결괏값 (아직 숫자가 아닌 원시 값)을 ToNumber 규칙에 의해 강제변환한다.
  • 동등한 원시 값으로 바꾸기 위해 ToPrimitive 추상 연산 과정에서 해당 객체가 valueOf() 메서드를 구현했는지 확인한다. valueOf() 를 쓸 수 있고 반환 값이 원시 값이면 그대로 강제변환하되, 그렇지 않을 경우 toString() 메서드가 존재하면 toString()을 이용하여 강제변환한다. 어찌해도 원시 값으로 바꿀 수 없을 땐 TypeError 오류를 던진다.

Object.prototype.valueOf()

valueOf() 메서드는 특정 객체의 원시 값을 반환한다.

  • JS는 객체를 원시 값으로 변환할 때 valueOf 메서드를 호출한다. 보통 원시값을 필요로 할 때 알아서 사용하므로 직접 호출해야 하는 경우는 드물다.
  • 기본적으로 Object의 모든 후손 객체는 valueOf를 상속받는다.
var a = {
  valueOf: function() {
    return "42";
  }
}

var b = {
  toString function() {
    return "42";
  }
}

var c = [4, 2];
c.toString = function() {
  return this.jon("");
}

Number(a); // 42
Number(b); // 42
Number(c); // 42
Number(""); // 0
Number([]); // 0
Number([ "abc" ]); // NaN

ToBoolean

  • 1을 true로 0을 false로 강제변환할 수 있지만 그렇다고 두 값이 똑같은 건 X

Falsy 값

  1. undefined
  2. null
  3. false
  4. +0, -0, NaN
  5. ""
  • 자바스크립트의 모든 값은 다음 둘 중 하나다.
    1. 불리언으로 강제변환하면 false가 되는 값
    2. 1번을 제외한 나머지(즉, 명백히 true인 값)

명시적 강제변환

var a = 42;
var b = a.toString();
  • a.toString() 호출은 겉보기에는 명시적이지만, 몇 가지 암시적인 요소가 있다. 원시 값 42에는 toString() 메서드가 없으므로 엔진은 toString()을 사용할 수 있게 자동으로 42를 객체 래퍼로 Boxing 한다. 즉 명시적으로 암시적인 작동이다.

+ 단항 연산자

피연산자가 하나뿐인 연산자

  • 단항 연산자 +는 덧셈을 하는 게 아니라 피연산자를 숫자로, 명시적 강제변환한다.
var c = '3.14';
var d = 5 + +c;
d; //8.14;
  • 이런 식으로 쓴다면 별로 좋지 않은 생각이다. 명시적으로 변환하여 혼동을 줄이는 게 좋다.

날짜 -> 숫자

  • 단항 연산자는 ‘Date 객체 -> 숫자’ 강제변환 용도로도 쓰인다.
  • var timeStamp = +new Date()
var timeStamp1 = +new Date();
var timeStamp2 = new Date().getTime();
var timeStamp1 = Date.now();
  • Date.now()을 쓰는 걸로..

명시적 강제변환: 숫자 형태의 문자열 파싱

  • 문장열에 포함된 숫자를 파싱하는 것은 문자열 -> 숫자 강제변환과 결과를 비슷하지만, 앞서 배운 타입변환과는 분명한 차이가 있다.
var a = '42';
var b = '42px';

Number(a); // 42
parseInt(a); // 42

Number(b); //NaN
parseInt(b); //42
  • 문자열로부터 숫자 값의 파싱은 비 숫자형 문자를 허용한다. 좌 -> 우 방향으로 파싱하다가 숫자 같지 않은 문자를 만나면 즉시 멈춘다.
  • 강제변환은 비 숫자형 문자를 허용하지 않기 때문에 NaN
  • parseInt()는 문자열에 쓰는 함수이다.
    • 인자가 비 문자열이면 제일 먼저 자동으로 문자열로 강제변환한다. 절대로 parseInt()에 비 문자열 값을 넘기지 말자
  • ES5 이전에는 두 번째 인자로 기수를 지정하지 않으면, 인자 첫 번째 문자에 따라 마음대로 추정하여 “08”을 넣었을 때 0이 나오는 경우가 있었는데 ES5 이후부터는 제멋대로 추측하지 않는다고 한다. “0x”로 시작할 때만 16진수로 처리하고, 그밖에는 두 번째 인자가 없으면 무조건 10진수로 처리한다고 한다.

명시적 강제변환: -> 불리언

    • 단항 연산자가 값을 숫자로 강제변환하는 것처럼 ! 부정 단항 연산자도 값을 부릴언으로 명시적으로 강제변환한다. 일반적으로 불리언 값으로 명시적인 강제변환을 할 땐 !! 이중부정 연산자 를 사용한다.
  • Boolean()이나 !!를 쓰지 않으면 if() 문 등의 불리언 콘텍스트에서 암시적인 강제변환이 일어난다.
var a = 42;
var b = a ? true : false;
  • 여기엔 암시적인 강제변환이 들어있다. Boolean(a) 또는 !!a 같은 명시적 강제변환이 훨씬 좋은 코드.

암시적 강제변환

  • + 연산자는 숫자의 덧셈, 문자열 접합 두 가지 목적으로 오버로드 된다.
var a = {
	valueOf: function () {
		return 42;
	},
	toString: function () {
		return 4;
	}
};

a + ''; // "42"
String(a); // "4"
var a = '3.14';
var b = a - 0;

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

a + b; // "1, 23, 4"
  • + 알고리즘(피연산자가 객체 값일 경우)은 한쪽 피연산자가 문자열이거나 다음 과정을 통해 문자열 표현형으로 나타낼 수 있으면 문자열 붙이기를 한다. 피연산자중에 하나가 객체라면, 먼저 이 값에 ToPrimitive 추상 연산을 수행하고, 다시 ToPrimitive는 number 콘텍스트 힌트를 넘겨 [[DefaultValue]] 알고리즘을 호출한다.
  • valueOf()에 배열을 넘기면 단순 원시 값을 반환할 수 없으므로 toString()으로 넘어간다. 그래서 두 배열은 각각 “1, 2”와 “3, 4”가 된다.
  • 두 배열은 우선 문자열로 강제변환된 뒤 (toString()로 직렬화) 숫자로 강제변환 된다.

암시적 강제변환: 불리언 -> 숫자

암시적 강제변환의 효용성은 복잡한 형태의 불리언 로직을 단순한 숫자 덧셈 형태로 단순화할 때 빛을 발한다.

암시적 강제변환: * -> 불리언

  • 불리언으로서의 암시적 강제변환이 일어나는 표현식
  1. if () 문의 조건 표현식
  2. for ( ; ; )에서 두 번째 조건 표현식
  3. while ()do - while() 루프의 조건 표현식
  4. ? : 삼항 연산 시 첫 번째 조건 표현식
  5. ||&& 의 좌측 피연산자
  • 이런 콘텍스트에서 불리언 아닌 값이 사용되면, ToBoolean추상 연산 규칙에 따라 일단 불리언 값으로 암시적 강제변환된다.

느슨한/엄격한 동등 비교

  • 느슨한 동등 비교 시 강제변환을 허용한다.
  • 엄격한 동등 비교 시 강제변환을 허용하지 않는다.
var a = 42;
var b = '42';

a == b; // true

null -> undefined

  • null과 undefined간의 변환은 느슨한 동등 비교 == 이 암시적 강제변환을 하는 또 다른 예다.
  • 이 둘을 느슨한 동등 비교 시 상호 간의 암시적인 강제변환이 일어나므로 비교 관점에서 구분이 되지 않는 값으로 취급된다.
  • null ↔ undefined 강제변환은 안전하고 예측 가능하며, 어떤 다른 값도 비교 결과 긍정 오류를 할 가능성이 없다.
var a = doSomething();

if (a == null) {
	// ..
}
  • a가 null이나 undefined를 반환할 경우에만 true가 된다.
  • 0, false, ""… falsy한 값이 넘어와도 false다.

객체 -> 객체

  • 객체의 느슨한 동등 비교에 대해 두 객체가 정확히 똑같은 값에 대한 레퍼런스일 경우에만 동등하다고 기술되어 있다.

객체 -> 비객체

  • 객체/함수/배열과 단순 스칼라 원시 값의 비교시에는
  1. Type(x)가 String 또는 Number고 Type(y)가 객체라면, x == ToPrimitive(y)의 비교 결과를 반환한다.

희귀 사례

if (a === 2 && a === 3) {
	// ...
}
let i = 2;
Number.prototype.valueOf = function () {
	return i++;
};

if (a == 2 && a == 3) {
	console.log(a == 4); // true
}