[JavaScript] {} + {}의 결과는 뭘까?
해당 글은 JavaScript values: not everything is an object를 번역한 글입니다.
질문
{} + {}
의 결과는 뭘까? 자바스크립트에서 객체나 배열들을 더할 때 우리는 종종 예상치 못한 결과물을 얻곤한다. 이러한 결과물이 생긴 이유에 대해서 알아보자!
자바스크립트의 더하기 연산 법칙은 간단한다.
오직 숫자(number)나 문자열(string)만 더할 수 있다.
숫자와 문자열 이외의 다른 모든 값들은 더하기 연산을 만나면 이 둘 중 하나로 변하게 된다. 해당 변환이 어떻게 이루어지는 지 알기 위해서는, 몇 가지 사실에 대해서 이해해야한다.
자바스크립트는 원시값(primitives)과 객체(objects) 두 종류의 값으로 이루어져 있다. 원시값에는 undefined, null, boolean, numbers, strings가 있다. 그리고 배열(arrays)과 함수(functions)를 포함한 모든 다른 값들은 objects이다.
값 변환하기
더하기 연산자(+)는 세 종류의 변환을 하는데, 원시값을 숫자나 문자열로 바꿔준다.
ToPrimitive()를 이용하여 값을 원시값으로 바꾸기
ToPrimitive()는 다음과 같이 정의된다.
ToPrimitive(input, PreferredType?)
추가적 인자인 PreferredType
은 Number
혹은 String
이며 선호하는 타입을 알려주는 역할을 한다. 해당 함수의 결과물은 항상 어떠한 원시값이다. 만약 PreferredType
이 Number
이면 다음과 같은 변환 과정이 일어난다.
- 만약 입력값이 원시값이면, 그대로 출력한다.
- 그렇지 않고, 입력값이 객체이면,
obj.valueOf()
를 호출한다. 해당 함수의 결과값이 원시값이라면, 이를 반환한다. - 그렇지 않으면,
obj.toString()
을 호출한다. 만약 결과값이 원시값이라면, 이를 반환한다. - 그렇지 않으면,
TypeError
를 발생시킨다.
만약 PreferredType
이 String
이라면, 2, 3번 단계는 서로 바뀌게된다. 만약 PreferredType
이 없다면 Date
나 Number
대신 String
으로 설정된다.
ToNumber()를 이용하여 값을 숫자로 바꾸기
아래 표는 ToNumber()
에 의해 원시값이 어떻게 숫자로 바뀌는 지 보여주는 표이다.
인자 | 결과물 |
---|---|
undefined | NaN |
null | +0 |
boolean | true는 1로 변환되고, false는 +0으로 변환된다. |
number | 변환이 필요없다. |
string | 숫자를 문자열로 파싱한다. 예를들어 "324"는 324로 변환된다 |
객체 obj
는 ToPrimitive(obj,Number)
를 이용하여 숫자로 변환된다. 그리고 결과물에 ToNumber()
를 적용한다.
ToString()을 이용하여 값을 문자열로 바꾸기
아래 표는 ToString()
에 의해 원시값이 어떻게 문자열로 바뀌는 지 보여주는 표이다.
인자 | 결과물 |
---|---|
undefined | "undefined" |
null | "null" |
boolean | true는 "true"로 변환되고, false는 "false" 으로 변환된다. |
number | 숫자를 문자열로 표시한 값. 예를 들어 324는 "324"로 변환된다. |
string | 변환이 필요없다. |
객체 obj
는 ToPrimitive(obj,String)
을 이용하여 숫자로 변환된다. 그리고 결과물에 ToString()
을 적용한다.
예시
let obj = {
valueOf: function () {
console.log("valueOf");
return {}; // not a primitive
},
toString: function () {
console.log("toString");
return {}; // not a primitive
}
}
Number
는 내부적으로 ToNumber()
를 호출하는 함수이다.
> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value
toString()
의 결과가 원시값이 아니기 때문에 Number
에서 TypeError
가 발생한다.
더하기 연산
다음 표현식이 주어졌다고 생각해보자.
value1 + value2
다음 표현식을 계산하기 위해서는 다음과 같은 과정을 거쳐야한다.
양쪽 연산대상을 원시값으로 변환한다.
prim1 := ToPrimitive(value1) prim2 := ToPrimitive(value2)
만약
prim1
이나prim2
가 문자열이라면, 두 연산대상 모두를 문자열로 바꾸고 둘을 이은(concatenation) 값을 결과로 내놓는다.그렇지 않으면, 둘을 숫자로 바꿔서 둘을 더한 값을 결과로 내놓는다.
결과 예상해보기
두 비어있는 배열을 더할 때, 모두 예상하는 것처럼 동작한다.
> [] + []
''
우선 []
를 원시값으로 바꾸는 과정은 valueOf()
를 사용한다. 이 경우 배열은 배열 그 자체를 내놓게 된다.
> let arr = [];
> arr.valueOf() === arr
true
결과가 원시값이 아니기 때문에 toString()
이 호출될 것이고, 원시값이 아닌 빈 문자열을 결과로 내놓는다. 따라서, [] + []
의 결과는 두 빈 문자열을 이은 값인 ""
가 된다. 배열과 객체를 더하는 것도 우리가 예상하는 것처럼 작동한다.
> [] + {}
'[object Object]'
빈 객체를 문자열로 바꾸면 다음과 같이 된다.
> String({})
'[object Object]'
이전의 결과인 ""
과 "[object Object]"
를 이어서 최종 결과인 "[object Object]"
가 된다.
이외에도 객체를 원시값으로 바꾸는 다양한 예제들이 있다.
> 5 + new Number(7)
12
> 6 + { valueOf: function () { return 2 } }
8
> "abc" + { toString: function () { return "def" } }
'abcdef'
예상치 못한 결과들
하지만 만약 더하기 연산자의 첫번째 연상대상이 비어있는 객체({})라면 상황이 좀 달라진다.
> {} + {}
NaN
무슨 일이 일어난걸까? 문제는 자바스크립트가 첫번째 {}
를 비어있는 코드블록으로 해석하고 무시한 것이다. 따라서 NaN
은 +{}
로 계산된 결과이다. 여기서 보이는 +
는 이진법 더하기 연산자가 아니라, 연산대상을 숫자로 바꿔주는 접두사이다. 이는 Number()
와 비슷한 역할이다.
> +"3.65"
3.65
다음 표현식들은 모두 같은 의미이다.
+{}
Number({})
Number({}.toString()) // {}.valueOf()는 원시값이 아니다.
Number("[object Object]")
NaN
왜 첫번째 {}
는 코드 블록으로 해석되었을까? 왜냐하면 선언문 맨 앞에 있는 중괄호쌍은 코드 블록으로 해석되기 때문이다. 따라서 다음과 입력값을 다음과 같이 바꾸면 원하는 결과를 얻는다.
> ({} + {})
'[object Object][object Object]'
함수나 메소드의 인자는 항상 표현식으로 파싱된다.
> console.log({} + {})
'[object Object][object Object]'
이제 우리는 다음과 같은 결과도 해석할 수 있을 것이다.
> {} + []
0
다시 말하지만, {}
가 코드 블록으로 해석되어, +[]
만 남게된다. 다음 표현식들도 모두 같은 의미이다.
+[]
Number([])
Number([].toString()) // [].valueOf()는 원시값이 아니다.
Number("")
0
흥미로운 사실은, Node.js
는 다른 방식으로 파싱한다. Node.js
는 다음의 결과물들은 예상되는 결과를 보여준다.
> {} + {}
'[object Object][object Object]'
> {} + []
'[object Object]'
Node.js
는 console.log()
를 사용했을 때처럼 더욱 예상되는 결과를 얻는다. 하지만, 입력으로 선언문을 사용하지 않는 경향을 보인다.
그럼 배열이나 객체는 어떻게 합칠까?
대부분의 환경에서, 자바스크립트에서 +
가 어떻게 작동하는 지 이해하는 것은 어렵지 않다. 당신은 그저 숫자나 문자열을 더하기만 하면 될 뿐이다. 객체들은 문자열이나 숫자로 변환된다. 만약 당신이 배열을 연결하고 싶다면 다음과 같은 메소드를 사용하면 된다.
> [1, 2].concat([3, 4])
[1, 2, 3, 4]
자바스크립트에서 빌트인으로 객체를 합치는 방법은 없다. 대신 Lodash같은 라이브러리를 이용해야한다. ES6
의 Destructing을 이용해도 같은 결과를 얻을 수 있다.
> var obj1 = { kim: 1, han: 2 };
> var obj2 = { suji: 3, minsu: 4 };
> _.extend(obj1, obj2)
{ kim: 1, han: 2, suji: 3, minsu: 4 }
>{ ...obj1, ...obj2 }
{ kim: 1, han: 2, suji: 3, minsu: 4}
참고 자료
'Web > JavaScript' 카테고리의 다른 글
Creative Coding 개발환경 설정 및 기본 프로젝트 (0) | 2020.11.22 |
---|---|
Creative Coding을 들어가며 (0) | 2020.11.14 |
[JavaScript] Ajax (0) | 2019.12.11 |
[JavaScript] 클로저(Closure) (0) | 2019.11.24 |
🕵️♂️ 자동완성 모듈의 법칙 (0) | 2019.08.22 |
댓글
이 글 공유하기
다른 글
-
Creative Coding 개발환경 설정 및 기본 프로젝트
Creative Coding 개발환경 설정 및 기본 프로젝트
2020.11.22 -
Creative Coding을 들어가며
Creative Coding을 들어가며
2020.11.14 -
[JavaScript] Ajax
[JavaScript] Ajax
2019.12.11 -
[JavaScript] 클로저(Closure)
[JavaScript] 클로저(Closure)
2019.11.24