Falsy, Truthy
강제변환하면 false로 변하는 값입니다.
JS에 있는 모든 값들은 Falsy 혹은 Truthy 한 값입니다.
Falsy 값은 얼마 되지 않아서 외워두면 되고, 나머지는 Truthy한 값이라고 생각하면 됩니다.
Falsy :
undefined
null
false
+0, -0, NaN
""
Equality ( == , 동등 연산자 )
느슨한 동등 비교로, 비교 대상의 타입이 다를 경우 강제 변환을 실시하여 타입 비교 후 결과를 반환한다.
추상 비교 알고리즘(Abstract Equality Comparison Algorithm)을 사용하며,
비교 후 같은지 결과를 true 혹은 false로 반환한다.
작동 알고리즘
먼저 비교대상이 되는 x와 y는 true와 false로 표현될 수 있는 값이어야 합니다.
- x와 y의 타입이 같은 경우
- undefined의 경우 true 반환
- null의 경우 true 반환
- Number의 경우
- x가 NaN이면 false 반환
- y가 NaN이면 false 반환
- x와 y가 같은 값을 가지고 있으면 true 반환
- x가 +0, y가 -0 이면 true
- x가 -0, y가 +0 이면 true
- false를 반환한다 ( 위의 조건들에 하나도 해당되지 않았다)
- String인 경우, 만약 x와 y가 정확히 같은 순서의 문자들이라면 (같은 length를 가지고 모든 문자들이 위치가 같다면!!) true를 반환하고 아닌 경우는 false를 반환
- Boolean인 경우, ( x가 true 이고 y도 true ) 이면 true를 반환하고, ( x가 false이고 y가 false ) 이면 true를 반환한다. 아니라면 두 개가 다른 경우이므로 false를 반환
- x와 y가 같은 객체를 참조하고 있으면 true, 아니면 false를 반환
- x가 null 이고 y가 undefined인 경우 true
- x가 undefined 이고 y가 null인 경우 true
- x가 Number이고 y가 String인 경우 y를 ToNumber 함수로 숫자로 만들어 비교하여 결과를 반환
- x가 String 이고 y가 Number이면 ToNumber함수로 숫자로 만들어 비교하여 결과를 반환
- x가 Boolean이면 x를 ToNumber함수로 숫자로 만들어 y와 비교하여 결과를 반환
- y가 Boolean이면 y를 ToNumber함수로 숫자로 만들어 x와 비교하여 결과를 반환
- x가 String이거나 Number이고 y가 Object 이면 y를 ToPrimitive 함수를 통해 원시 타입으로 변환하여 비교 후 결과를 반환
- x가 Object이고 y가 String이거나 Number 이면 x를 ToPrimitive 함수를 통해 원시 타입으로 변환하여 비교 후 결과를 반환
- 위의 조건에 해당하지 않으면 false를 반환
Note
- String 비교는 "" + a == "" + b 같은 식으로 강제로 실시할 수 있음.
- Numberic 비교는 +a == +b 이렇게 강제로 변환하여 실시할 수 있음. 참고로 +를 앞에 붙이면 숫자로 변환됩니다. (그래서 +new Date() 식의 관용적 코드를 많이 사용하는데, 이렇게 쓰면 Date 객체가 UNIX timeStamp 형식으로 변환됩니다)
- Boolean 비교는 !a == !b 이렇게 강제로 변환하여 실시할 수 있음
- A != B 는 !( A == B )
- A == B 를 비교하는 거나 B == A 를 비교하는거나 결과는 같습니다. 순서의 차이일 뿐 결과는 같습니다.
- 동등 비교 연산자가 꼭 다른 비교 대상이 필요하지는 않습니다. 예를 들어, 서로 다르지만 같은 값을 바라보는 2개의 String 객체가 있다면 각각의 String 객체는 == 연산자를 통해 같은 문자열을 가졌다고 판단될 수 있습니다. 그러나 2개의 String 객체는 각각 다른 것과 같을 수 없습니다.
- new String("a") == "a" / "a"==new String("a") 의 경우는 true로 판단됩니다.
- new String("a") == new String("a")는 false로 판단이 됩니다.
- 문자열 비교는 코드의 개별 값의 순서에 의거한 간단한 동등 비교를 사용합니다. 기본적으로 유니코드에 정의된 문자나 문자열을 따르며, 이보다 복잡하고 의미적인 것을 사용하지 않습니다. 그러므로 문자열 비교는 유니코드 기준에 맞게 테스트 됩니다. 이 알고리즘은 이미 표준화 된 형식의 두 개의 문자열에만 사용할 수 있습니다.
Identity ( ===, 일치 연산자 )
작동 알고리즘
먼저 비교대상이 되는 x와 y는 true와 false로 표현될 수 있는 값이어야 합니다.
- x와 y의 타입이 다르면 false를 반환.
- x의 타입이 Undefined라면 true를 반환.
- x의 타입이 Null 이라면 true를 반환.
- x의 타입이 Number라면,
- 만약 x가 NaN이라면 false를 반환.
- 만약 y가 NaN이라면 false를 반환.
- x와 y가 같은 값을 가지고 있다면 true를 반환
- x가 +0 이고 y가 -0이면 true를 반환
- x가 -0 이고 y가 +0이면 true를 반환
- 아니면 false를 반환힙니다.
- String인 경우, 만약 x와 y가 정확히 같은 순서의 문자들이라면 (같은 length를 가지고 모든 문자들이 위치가 같다면!!) true를 반환하고 아닌 경우는 false를 반환
- Boolean인 경우, ( x가 true 이고 y도 true ) 이면 true를 반환하고, ( x가 false이고 y가 false ) 이면 true를 반환한다. 아니라면 두 개가 다른 경우이므로 false를 반환
** 위의 알고리즘은 JS에서 같은 값을 비교할 때 사용하는 SameValue(x, y) 알고리즘과 비슷하게 보일 수 있는데,
SameValue에 알고리즘은 위의 0의 값을 비교하는 부분이 다르다.
위에서는 +0과 -0을 같은것으로 보아 true를 반환하지만,
SameValue 알고리즘은 +0과 -0을 구분하여 판단하므로, 0의 부호까지 일치해야 true를 반환한다.
또한, SameValue 알고리즘은 x가 NaN이고 y가 NaN인 경우 같다고 판단하여 true를 반환한다.
typeof
JS에서 지정되어 있는 Unray Operator의 일종(Single input, Single operation 한국어로는 단항 연산자)이다
** JS에서 지정되어 있는 Unray Expression 들을 확인해 보자
UnaryExpression :
PostfixExpression
delete UnaryExpression
void UnaryExpression
typeof UnaryExpression
++ UnaryExpression
-- UnaryExpression
+ UnaryExpression
- UnaryExpression
~ UnaryExpression
! UnaryExpression
작동 알고리즘
val은 UnrayExpression으로 연산된 결과여야 합니다.
- 만약 val의 타입이 Reference라면,
- UnresolvableReference(val) 이 true 라면 undefined를 반환합니다.
- getValue(val) 함수를 통해 값을 꺼내 옵니다.
- 아래 표에 해당하는 타입을 문자열로 반환합니다.
** 여기서 이상한 점은 Null은 분명 원시타입인데 왜 객체라고 반환이 될까?
이건 JS에 존재하는 버그의 일종이며, 제대로 되돌리기엔 많은 문제를 야기할 수 있어 수정할 수 없는 버그다.
여기에 대한 설명을 좀 더 보고 싶으면 아래의 링크를 참조하세요.
Loose Equals ( == ) vs Strict Equals ( === )
** 간단하게 이해하기
Loose Equals는 강제변환을 허용하지만,
Strict Equals는 강제변환을 허용하지 않습니다.
Loose Equals 는 두 타입이 다를 경우 강제변환하는 복잡한 절차를 거쳐야 합니다.
그러나, 강제변환 하는데 걸리는 시간은 불과 몇 마이크로초의 시간 밖에 안되서 성능상 차이는 미미합니다.
위에서 보았듯이, 두 연산의 동등 비교 로직은 동일합니다. 다만 TypeCasting 하는 로직이 있고 없는 차이일 뿐..
예제로 이해해보기
숫자와 문자열
Strict Equals의 경우 두 비교 대상의 타입이 다르므로, 처음부터 false로 바뀌었고,
Loose Equals의 경우 a가 숫자이고 b가 문자열입니다.
b가 toNumber(b) 메소드가 내부적으로 호출되고 숫자로 바뀌어 비교되므로 true를 반환합니다.
Boolean의 경우 (중요!!)
분명 42는 truthy한 값이고, 당연히 true로 강제변환되어야 할 것 같은데.. 이상하다
이것은 위에 알고리즘에서 보았듯이 한 쪽이 Boolean이면, toNumber()를 통해 숫자로 바꾼 후에 비교 연산을 수행하는데
지금 여기서는 b를 toNumber(b)로 변환하여 1로 바꾼 후에 비교 연산을 수행했기 때문에
false의 결과를 뱉은것이다.
이 경우는 어떨까?
방금 전과 같은 경우인데, 다른 점은 a가 문자열로 "42" 이다.
b가 Boolean이므로 당연히 숫자 1로 변환되고
a와 비교하는데 문자열과 숫자를 비교할 수 없으므로 ("42" == 1 ? )다시 처음 조건으로 돌아가서 타입을 변환한다. 즉 재귀적으로 알고리즘을 수행한다.
a가 문자열이므로, a를 toNumber 연산을 통해 숫자보 변환하여 비교하면 42 == 1 ? 의 연산이 되므로 결과는 false를 반환한다.
객체와 원시값을 비교하는 경우
a의 경우 string 값이고, b의 경우는 Array Object 이다.
이 경우 b가 ToPrimitive 연산을 통해 문자열로 변환되고, 비교하여 true가 된다.
(Array의 경우 valueOf() 메소드를 통해 string 타입으로 변경이 됩니다)
Falsy 값 비교시..
첫 번째로 false와 null을 비교할 경우, 한 쪽이 Boolean이니 ToNumber 연산을 통해 false가 0으로 변환되고, 0과 null을 비교해야 하는데 타입이 다르므로 다시 재귀연산을 수행한다.
그 결과 매칭되는 case가 없으므로 false를 반환한다.
두 번째 경우를 보자. false == 0 으로 비교하면, false를 0으로 바꾸면 0과 0을 비교하게 되는데 같은 타입이니 값을 비교하게 되고 당연히 true를 반환한다.
세 번째 경우에, false가 0으로 변환되고, 다음 재귀에서는 []가 원시타입으로 바뀌어야 하는데, 문자열로 변경되면 ""로 변경이 된다.
자 그럼 0과 ""을 비교할 차례인데, 앞이 Number 타입이므로 "" 를 ToNumber 연산을 통해 숫자로 바꿔주면 0이 된다.
그럼 0과 0을 비교해서 같으니 true를 반환한다.
네 번째 경우, false가 0으로 변환되고 0과 {}를 비교해야 하는데 걸리는 조건이 없으므로 false를 반환한다.
마지막의 경우 Number와 Object의 연산이니까 뒤에 있는 배열 객체가 ToPrimitive 연산을 통해 문자열로 변경되고 다시 재귀하여,
Number와 문자열을 비교하게 되는데 현재의 문자열은 ""이다. ""은 아무것도 없는 것이므로 숫자로 변환하면 0이 된다.
결국 0과 0을 비교하게 되므로 true가 된다.
위에서 보았듯이 만약 추상 동등 비교의 경우 Boolean을 숫자로 변환하므로 정확히 True or False로 비교해야 할 경우에는 사용하지 않는 것이 좋다.
또한 Falsy 한 값인 [], " ", 0 등은 False로 변할 가능성이 있으면 사용하지 않는게 낫다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
"0" = = false;
false = = 0;
false = "";
false = = [];
"" = = 0;
"" = = [];
0 = = [];
|
cs |
이 식들의 결과는 모두 true 이다.
그러나 예상치 못하게 강제변환 될 상황이 그렇게 많지 않고, 예측 가능한 상황이면 동등 비교 연산자를 사용하는것이 낫다.
"Don't throw the baby out with the bath water"
You don't know JS의 저자 카일 심슨은 강제 변환을 두고 " 목욕 물을 버릴때 아기도 같이 내다 버리지 말라고 " 조언 한다.
이는 나쁜 것 없애려다 중요한 것 까지 없애지 말란 말이다. 우리나라 속담으로 치면 "빈대 잡으려다 초가집 태운다" 라는 우리 속담과 비슷할 것이다.
강제변환을 막으려고 방어적인 코드를 덕지덕지 붙이는 것 보다는 강제 변환을 활용하여 좀 더 깔끔하게 작성하는게 더 도움이 되지 않을까?
참고 자료 :
http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.6
You don't know JS 1권
https://dorey.github.io/JavaScript-Equality-Table/