강제변환 Type Coercion
JS 엔진에 의해 타입이 자동 변환된다는 의미
값 변환
- 타입 캐스팅: 어떤 값을 다른 타입의 값으로 바꾸는 과정이 명시적인 것
- 강제변환: ~ 과정이 암시적인 것
- 암시적 강제변환은 다른 작업 도중 불분명한 Side Effect 로부터 발생하는 타입변환이다.
var a = 42
var b = a + '' // 암시적 강제변환
var c = String(a) // 명시적 강제변환
- 공백 문자열과의
+
연산은 문자열 접합 처리를 의미한다.
추상 연산 (Abstract Operation)
ToString
, ToNumber
, ToBoolean
, ToPrimitive
ToString
- 객체 -> string
- 일반 객체는 특별히 지정하지 않으면 기본적으로
toString()
메서드가 내부[[Class]]
를 반환한다.
const obj = {}
obj.toString() // [object Object]
- 배열 -> 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
강제변환과 연관된다.- 문자열, 숫자, 불리언, null 값이 JSON으로 문자열화하는 방식은
ToString
추상 연산의 규칙에 따라 문자열 값으로 강제변환되는 방식과 동일하다. JSON.stringify()
에 전달한 객체가 자체toJSON()
메서드를 갖고 있다면, 문자열화 전toJSON()
가 자동 호출되어 JSON 안전 값으로 강제변환된다.
- 문자열, 숫자, 불리언, null 값이 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 값
- undefined
- null
- false
- +0, -0, NaN
- ""
- 자바스크립트의 모든 값은 다음 둘 중 하나다.
- 불리언으로 강제변환하면 false가 되는 값
- 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()로 직렬화) 숫자로 강제변환 된다.
암시적 강제변환: 불리언 -> 숫자
암시적 강제변환의 효용성은 복잡한 형태의 불리언 로직을 단순한 숫자 덧셈 형태로 단순화할 때 빛을 발한다.
암시적 강제변환: * -> 불리언
- 불리언으로서의 암시적 강제변환이 일어나는 표현식
if ()
문의 조건 표현식for ( ; ; )
에서 두 번째 조건 표현식while ()
및do - while()
루프의 조건 표현식? :
삼항 연산 시 첫 번째 조건 표현식||
및&&
의 좌측 피연산자
- 이런 콘텍스트에서 불리언 아닌 값이 사용되면,
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다.
객체 -> 객체
- 객체의 느슨한 동등 비교에 대해 두 객체가 정확히 똑같은 값에 대한 레퍼런스일 경우에만 동등하다고 기술되어 있다.
객체 -> 비객체
- 객체/함수/배열과 단순 스칼라 원시 값의 비교시에는
- 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
}