2020. 6. 26. 23:42ㆍJavascript/33가지 개념
1. 명시적 변환 vs 암시적 변환
형 변환은 어떤 타입을 다른 타입으로 변환하는 과정을 말한다. 자바스크립트에서 형 변환은 명시적, 암시적 두 형태로 나뉘어진다. 명시적 형 변환은 프로그래머가 형 변환을 직접적으로 한 것을 말한다. 하지만 암시적 형 변환은 자바스크립트의 판단 하에 형 변환을 한 것을 말한다.
// 명시적 형 변환
console.log(Number("1"));
console.log(String(1234));
// 암시적 형 변환
console.log(1 + "23"); // "123"
console.log(true + 55); // 56
console.log(false + 0); // 0
암시적 형 변환은 보통 원시적 타입이든 참조적 타입이든 타입 간에 연산자를 적용하는 경우와 if같은 조건문을 사용하는 경우에 발생한다. 반면에 암시적 형 변환이 일어나지 않는 연산자가 있다. 이퀄 연산자인 === 이다. 이퀄 연산자는 ===, == 두 개가 있다. === 연산자는 값 뿐만 아니라 데이터 타입까지 비교한다. 하지만 == 연산자는 오직 값 만을 비교하기 때문에 암시적 형 변환이 일어난다. 아래 예제를 보면 이해할 수 있을 것이다.
console.log("123" == 123); // true
console.log("123" === 123); // false
첫 번째 코드는 == 연산자를 이용해서 String 타입과 Number 타입의 일치 유무를 판별을 목적으로 한다. 값의 일치 유무를 알기 위해서는 둘 중 하나를 다른 하나에 맞춰 암시적으로 형 변환을 해주어야한다. 그러면 둘 중 누가 형 변환을 해야할까? 정답은 문자열이다. String 타입과 Number 타입을 == 연산자를 이용해서 비교할 때 자바스크립트는 String타입을 Number 타입으로 암시적으로 형 변환을 하라고 지시한다. 또한 Number와 Boolean 타입을 비교할 때, 자바스크립트는 Boolean을 Number로 형 변환 해서 연산을 수행하라고 명령한다. 이는 규칙이 있기 때문이다. 이 규칙에 관해 자바스크립트 == 연산자에 관한 MDN에서 따로 정리한 표가 존재한다.
본론으로 돌아와보자. 형 변환이라는 관점에대해서 지금까지 얘기한 것을 짧게 정리하면 다음과 같다.
- 형 변환은 명시적 형 변환, 암시적 형 변환이 있다.
- 명시적 형 변환은 프로그래머가 형 변환을 직접적으로 하는 것을 말한다.
- 암시적 형 변환은 자바스크립트가 자동으로 형 변환을 해주는 것을 말한다.
그런데, 암시적이든 명시적이든 우리는 형 변환에 대해서 두 가지 개념을 알고 가야한다. 두 가지 개념은 다음과 같다.
- 자바스크립트에서의 형 변환은 to String, to Boolean, to Number 세 가지 유형만 있다.
- 원시 타입과 참조 타입의 변환 로직은 다르게 작동되지만, 결국 이 두 개 모두 세 가지 유형으로만 변환된다.
따라서 이제 원시 타입부터 어떻게 형 변환이 이루어지는지 알아보려고 한다.
2. To String
String 타입으로 명시적 형 변환을 하는 것은 간단하다. String() 메소드만 써주면 된다. 그런데 암시적 형 변환은 + 연산자에 의해 발생된다.
String(123); // 명시적 형 변환
123 + ""; // 암시적 형 변환
또한, 우리는 원시 타입 데이터에 대한 명시적 형 변환의 결과를 쉽게 예측할 수 있다.
String(123); // "123"
String(-123); // "-123"
String(null); // "null"
String(undefined); // "undefined"
String(true); // "true"
String(false); // "false"
3. To Boolean
Boolean 타입으로 명시적 형 변환을 하기 위해서는 간단하게 Boolean 메소드를 써주면 된다. 그런데 암시적 형 변환을 하기 위해서는 조건 문이나 논리 연산자( !, ||, &&)을 써주어야 한다. 참고로, 논리 연산자 ||와 &&는 내부적으로 boolean 타입으로 형 변환을 하지만, 반환할 때는 Boolean 타입이 아닌 피 연산자 값을 반환한다(세 번째 코드 참조).
Boolean(0) // false, 명시적 형 변환
if(1) { ... } // true, 암시적 형 변환
1 || 2 // 1, 암시적 형 변환
!!3 // true, 암시적 형 변환
Boolean 타입 형 변환의 결과는 오직 True, False만 존재한다. 그런데 어떤 것이 false고, 어떤 것이 true인 것일까? 그것을 알기 위해서는 false로 변환되는 것이 어떤 것이 있는지 알기만하면 된다. 왜냐하면 false로 변환되는 데이터는 7 개밖에 없기 때문이다. 그 7개는 다음과 같다.
Boolean('') // false
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(false) // false
이 것들을 제외한 나머지를 형 변환하면 모두 true 값이 나온다.
Boolean({}) // true
Boolean([]) // true
Boolean(Symbol()) // true
!!Symbol() // true
Boolean(function() {}) // true
4. 숫자 변환
숫자 타입으로 명시적 형 변환을 하기 위해서는 Number() 메소드를 써주면 된다. 그런데 암시적 형 변환은 쉽지 않다. 왜냐하면 아래와 같이 많은 조건들이 있기 때문이다.
- 비교 연산자( >, <, <=, >=)
- 비트 연산자( ~, &, |, ^)
- 산수 연산자(+, -, /, *, %)
- 단항 연산자(+, -)
- 느슨한 동일 연산자(==, !=)
원시 데이터를 Number 형으로 형 변환 결과는 다음과 같다.
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(" 12 ") // 12
Number("-12.34") // -12.34
Number("\n") // 0
Number(" 12s ") // NaN
Number(123) // 123
Number("") // 0
그리고 숫자형으로의 형 변환에는 두 가지의 특수한 경우가 존재한다.
첫째, null와 undefined를 ==연산자를 이용하여 일치 유무를 판별할 때, 형 변환은 일어나지 않는다. 그리고 null은 오직 null, undefined를 같다고 판별한다. 둘 다 의미 없는 값을 가지고 있기 때문이다. 하지만 === 연산자를 이용할 때 둘은 false를 출력한다.
null == 0 // false
null == null // true
undefined == undefined // true
null == undefined // true
null === undefined // false
둘째, NaN는 그 자체가 다른 값이다. 서로 비교를 해도 False를 출력한다.
NaN == NaN // false
NaN === NaN // false
5. 참조 타입에서의 형 변환
참조 타입에서 자바스크립트는 [1] + [2, 3]과 같은 좀 더 하드레벨한 표현식을 맞닥뜨리게 된다. 이런 표현식을 맞닥뜨려 형 변환 할 때는 먼저, 원시 타입으로 변환 후, 그 다음 최종 타입(객체?)으로 변환해야 한다. 그런데 결국 참조 타입도 세 개의 유형으로 형 변환을 해야한다는 것은 변함없다. 그 중에서 가장 간단한 것은 Boolean 타입으로의 변환이다. 왜냐하면 어느 참조 타입을 변환하든 true라는 결과 값을 얻기 때문이다.
참조 타입 객체는 [[ToPrimitive]] 내부 메서드를 이용해서 원시 타입으로 변환할 수 있다. [[ToPrimitive]] 함수는 입력 값과 변환을 원하는 타입을 인자로 받는다. 이 때 Number, String 변환에 객체 내부에 있는 toString, valueOf 함수가 실행된다. 두 메서드 모두 객체 프로토타입에 선언되어 있으며, array, Date 객체 등의 참조 타입도 이 함수를 이용할 수 있다. 일반적으로 참조 타입에서의 형 변환은 다음과 같이 이루어진다.
- 이미 입력 값이 원시 타입인 경우, 아무것도 하지 않고 그 값을 반환한다.
- toString 함수를 호출하여, 반환 값이 원시 타입이 되면 이를 반환한다.
- valueOf 함수를 호출하여, 반환 값이 원시 타입이 되면 이를 반환한다.
- toString, valueOf 모두 반환 값이 원시 타입이 아니라면, 에러를 일으킨다.
그렇다면, 참조 타입에서의 형 변환은 toString, valueOf 함수를 한번에 호출하는 걸까? 꼭 그런 것은 아니다. 만약 변환을 원하는 타입이 String인 경우 먼저 toString을 호출하고, 차선책으로 valueOf를 호출한다. 하지만 Number 형으로 변환을 원하는 경우는 valueOf를 먼저 호출하고, 차선책으로 toString을 호출한다. 아래의 예제를 통해 toString, valueOf 메소드가 어떤 방식으로 동작하는지 유추해보도록 하자.
/* valueOf만 썼을 경우 */
const obj = {
valueOf: () => 4
}
/* 변환 값이 Number 타입이라 + 연산자와 함께 있을 때는 Number 형을 반환*/
console.log(obj.valueOf() + 2); // 6
console.log(obj.valueOf() * 8); // 32
console.log(obj.valueOf() + ""); // 4
/* toString만 썼을 경우 */
const ref = {
toString: () => "123"
}
/* 변환 값이 String 타입이라 + 연산자와 함께 썼을 경우 String 형을 반환*/
console.log(ref.toString() + 123); // 123123
console.log(ref.toString() * 2); // 246
console.log(ref + 124); // 123124
/* 두 개 다 썼을 경우, valueOf 값을 이용한다. */
const both = {
toString: () => "123",
valueOf: () => 8
}
console.log(both + 33); // 41
const both2 = {
valueOf: () => "8",
toString: () => 16
}
console.log(both2 + 34); // 834
그런데, 배열 객체에서의 toString 함수는 약간 다르게 동작한다. 배열에서의 toString 메서드는 아무것도 넣지 않은 join 메서드를 실행한 것과 비슷하게 동작한다. 이제 밑에 예제를 통해 배열 객체에 toString을 적용했을 때 어떻게 돌아가는지 확인해보자.
console.log([1, 2, 3].toString()); // 1, 2, 3
console.log([1, 2, 3].join()); // 1, 2, 3
console.log(typeof [1, 2, 3].toString()); // String
console.log(typeof [1, 2, 3].join()); // String
console.log("me"+[1,2,3]); // me1, 2, 3
console.log(4 + [1,2,3]); // 41, 2, 3
console.log([]*4); // 0
console.log([].toString()); // ''
console.log(4/[2].toString()); // 2
console.log(Number([].toString())); // 0
여기까지 형 변환에 대해 포스팅을 마치도록 하겠다.
6. 참고 자료
- JavaScript type coercion explained - Alex Samoshkin
- 자바 스크립트 개발자라면 알아야 할 33가지 개념 #4 암묵적 타입 변환(implicit coercion) (번역)
'Javascript > 33가지 개념' 카테고리의 다른 글
[JS 33가지] 6. 스코프(Scope) (0) | 2020.07.05 |
---|---|
[JS 33가지] 5. typeof vs instanceof (0) | 2020.06.30 |
[JS 33가지 개념] 3. 값(Value) VS 참조(Reference) (0) | 2020.06.25 |
[JS 33가지 개념] 2. Primitive Types (0) | 2020.06.23 |
[JS 33가지 개념] 1. 함수의 동기적 호출과 콜 스택의 관계성 (0) | 2020.06.11 |