▶들어가기 전
자바스크립트는 느슨한 타입을 가진 언어이기 때문에 관대한 타입 변환을 수행합니다.
이런 부분은 많은 개발자가 자바스크립트를 이해하기 힘들어하고 싫어하는 이유이기도 합니다.
앞으로 자바스크립트의 타입 변환 규칙을 ECMAScript에 명시된 명세를 기준으로 알아볼 것입니다.
이 내용들은 어떤 식으로 타입 변환을 제어하는 것이 좋을지 판단할 때 큰 도움이 될 것입니다.
또한, 자바스크립트의 꽃이라고도 할 수 있는 함수의 선언과 호출 방법 그리고 this 바인딩에 대해 다룰 것입니다.
언급한 내용 모두 자바스크립트에서 혼란스럽게 여겨지지만, 알아보고 나면 생각보다 간단한 규칙이 있다는 것을 이해할 수 있을 것입니다.
▶타입 변환
자바스크립트에서 타입 변환을 이해하는 것은 매우 중요합니다.
어떤 경웨 데이터 타입 변환이 발생하는지를 알아야, 의도한 대로 동작하는 코드를 작성할 수 있습니다.
자바스크립트의 타입 변환은 명시적 강제 변환, 암시적 강제 변환 두 가지가 있습니다.
둘의 차이는 명확합니다.
명시적 강제 변환은 의도적인 타입 변환을 나타내는 것이고, 암시적 강제 변환은 표현식의 평가 중 타입이 변환되는 것입니다.
1) 명시적 강제 변환
명시적 강제 변환은 명확하게 의도를 갖고 타입을 변환하는 것입니다.
문자열, 숫자, 불리언 타입 별로 몇 가지 방법이 있습니다.
▷문자열로 변환
문자열로 변환하는 가장 간단한 방법은 [랩퍼 객체]에서 보았던 String() 함수를 호출하는 것입니다.
new 키워드를 붙이지 않고 String() 함수를 호출하면 랩퍼 객체를 생성하는 것이 아니라 타입 변환 함수로 동작합니다.
console.log(String(3)); // '3'
console.log(String(false)); // 'false'
console.log(String(null)); // 'null'
그렇다면 다른 타입에서 문자열 타입으로의 변환은 어떤 기준으로 이뤄지는 것일까요?
다음 표에서 볼 수 있듯이 타입별로 문자열 변환 방법이 정해져 있습니다.
ECMAScript에서는 이 규칙을 ToString() 추상 연산이라고 명시하고 있으며, 명시적이든 암시적이든 문자열로 변환될 때 수행됩니다.
[NOTE]
정확히 말하면 String() 함수는 심볼 이외의 타입에 대해서 ToString 추상 연산을 따릅니다.
심볼 타입의 경우 SymbolDescriptiveString 연산을 통해 변환됩니다.
타입 | 문자열 변환 결과 |
undefined | 'undefined' |
null | 'null' |
boolean | true → 'true', false → 'false' |
number | Number.toString() 변환 결과(3 → '3') |
Symbol | 문자열로 변환을 시도하는 경우 TypeError 발생 |
BigInt | BigInt.toString()의 결과 반환(3n → '3n') |
object | 객체를 원시 타입으로 변환한 후 다시 한번 ToString 추상 연산을 수행한 값을 반환합니다. 객체를 원시 타입으로 변환하는 과정을 정확히는 ToPrimitive 추상 연산이라고 합니다. |
const boolVal = true;
const numVal = 2;
console.log(String(boolVal)); // 'true'
console.log(String(numVal)); // '2'
[NOTE]
심볼은 연산을 통해 문자열 또는 숫자로 변환될 경우 TypeError가 발생합니다.
이는 심볼이 변환되어 객체의 다른 프로퍼티에 접근하는 것을 방지하기 위해서입니다.
myobject['__' + Symbol('key')]; // TypeError
또 다른 문자열 변환 방법은 toString() 메서드를 사용하는 것입니다.
const num = 4;
console.log(num.toString()); // '4'
- String()과 toString()의 차이점
앞서 살펴본 String()과 toString()은 대부분 동일한 결과를 반환하지만, 그렇지 않은 경우도 있습니다.
null과 undefined 타입이 그에 해당합니다.
console.log(String(null)); // 'null'
console.log(String(undefined)); // 'undefined'
undefined.toString(); // TypeError 발생
null.toString(); // TypeError 발생
예제 코드의 결과는 당연합니다.
null과 undefined 타입은 값이 비어 있음, 할당되지 않은 상태를 나타내는 원시 타입이므로 toString() 메서드를 호출할 수 있다는 논리상 말이 되지 않습니다.
또한 이 타입들은 객체가 아니기 때문에 객체에서 사용할 수 있는 프로퍼티들을 사용할 수 없는 것이 옳습니다.
반면, String() 함수의 경우에 심볼 이외의 모든 타입이 위에서 정의된 규칙을 정확하게 따르기 때문에 문자열로 명시적 강제 변환을 하고 싶은 경우에는 toString() 메서드보다는 String() 함수를 사용하는 것이 더 적합합니다.
▷숫자로 변환
숫자로의 변환도 문자열과 유사하게 Number() 함수를 호출하여 변환하는 방법이 있으며, BigInt 타입을 제외한 데이터는 ECMAScript의 ToNumber 추상 연산 명세를 기준으로 변환됩니다.
[NOTE]
숫자로의 변환 시 정확히는 ToNumber 추상 연산 명세를 기준으로 하여, 여기서 몇 가지 단계를 거쳐 ToNumber 추상 연산을 실행합니다.
타입 | 숫자 변환 결과 |
undefined | NaN |
null | +0 |
string | 숫자로 변경이 불가능한 값 → NaN, 숫자형 문자열 → 숫자 |
boolean | true → 1, false → 0 |
Symbol | 숫자로 변환을 시도하는 경우 TypeError 발생 |
BigInt | 숫자로 변환을 시도하는 경우 TypeError 발생 |
object | 객체를 원시 타입으로 변환한 후 다시 한번 ToNumber 추상 연산을 수행한 값을 반환 |
console.log(Number('3')); // 3
console.log(Number(true)); // 1
console.log(Number(null)); // 0
[NOTE]
BigInt 타입은 연산 중 숫자로 변환되는 것을 방지하기 위해 TypeError를 발생시킵니다.
BigInt 타입은 숫자 타입과는 다르게 큰 정수(안전한 숫자 타입의 범위를 벗어나는 정수)의 연산을 위해 나온 타입이기 때문에 숫자와는 타입이 구분되어야 합니다.
- parseInt() 함수
숫자로 변환하는 또 다른 방법으로는 parseInt() 함수를 이욯하는 방법이 있습니다.
다만, parseInt() 함수는 문자열만 대상으로 변환합니다.
값이 문자열이 아닌 경우에는 해당 값을 문자열로 변환한 후 사용합니다.
문자열의 변환 과정은 ToString 추상 연산 과정을 따릅니다.
console.log(parseInt('10', 10)); // 10
console.log(parseInt('-1', 10)); // -1
parseInt() 함수의 두 번재 인자는 기수를 의미합니다.
예제 코드는 기수를 10으로 지정하였디 깨문에 10진수를 기준으로 문자열을 숫자로 변환합니다.
기수를 생략하면 첫 번째 인자를 기준으로 추정하여 변환하므로 의도하지 않은 결과가 나올 수 있습니다.
버그를 만들고 싶지 않다면 반드시 기수를 지정하여 사용하길 권장합니다.
parseInt() 함수는 Number() 함수와는 달리 인내심을 가지고 끝까지 변환을 수행합니다.
console.log(Number('10A', 10)); // NaN
console.log(parseInt('10A', 10)); // 10
Number() 함수의 경우는 숫자로 변경 불가능한 문자가 있으면 곧바로 NaN을 반환하지만, parseInt() 함수는 변경 불가능한 문자가 나타날 때까지 최대한 숫자로 변환하여 결과를 반환합니다.
▷불리언으로 변환
Boolean도 ECMAScript의 ToBoolean 추상 연산에 따라 타입을 변환합니다.
타입 | 불리언 변환 결과 |
undefined | false |
null | false |
string | 빈 문자열 → false, 그 외 문자열 → true |
number | +0, -0, NaN → false, 그 외 숫자 → true |
Symbol | true |
BigInt | 0n → false, 그 외 BigInt 정수 → true |
object | true |
[논리 연산자]에서 살펴본 falsy 값이 기억나시나요?
falsy 값의 기준은 위의 표에 정의된 규칙을 그대로 따른 것입니다.
불리언 타입으로의 변환은 Boolean() 함수를 호출하는 방법과 이중 부정 연산자를 사용하는 방법이 있으며, 두 연산은 모두 동일한 결과를 반환합니다.
const a = null;
const b = 0;
const c = '';
const d = {};
const e = [];
console.log(Boolean(a)); // false
console.log(Boolean(b)); // false
console.log(Boolean(c)); // false
console.log(Boolean(d)); // true
console.log(Boolean(d)); // true
console.log(!!a); // false
console.log(!!b); // false
console.log(!!c); // false
console.log(!!d); // true
console.log(!!d); // true
2) 객체의 원시 타입 변환
암시적 강제 변환을 살펴보기 전에 객체의 원시 타입 변환에 대해 먼저 알아보겠습니다.
객체의 원시 타입 변환은 문자열로 변환, 숫자로 변환 두 가지로 나눌 수 있습니다.
그리고 이 과정에서 valueOf()와 toString() 메서드가 중요한 역할을 합니다.
▷문자열로 변환
객체가 문자열로 변환되는 과정을 아래와 같은 단계로 진행됩니다.
1단계
객체에 정의된 toString() 메서드를 호출합니다. 별도로 정의한 toString() 메서드가 없다면 기본적으로 Object.prototype.toString() 메서드를 실행합니다. Object.prototype.toString() 메서드는 결과 값으로 '[Object object]' 문자열을 반환합니다.
2단계
1단게의 결과가 원시 타입이라면 그 결과를 문자열로 변환하여 반환하고, 그렇지 않다면 valueOf() 메서드를 호출합니다. valueOf() 메서드 역시 객체에 별도로 정의한 valueOf() 메서드가 없다면 기본적으로 Object.prototype.valueOf() 메서드를 실행합니다.
3단계
valueOf() 메서드의 결과 값이 원시 타입이라면 그 결과를 문자열로 변환하여 반환하고, 그렇지 않다면 TypeError가 발생합니다.
console.log(String({})); // '[Object object]'
빈 객체가 '[Object object]' 문자열로 변환되었습니다.
'[Object object]'가 어떻게 나온 결과 값인지 의아할 수 있을 것입니다.
하지만 이 결과는 위의 단계를 정확하게 따른 결과입니다.
1단계
빈 객체를 문자열로 변환하기 위해 객체의 toString() 메서드를 호출합니다. 직접 정의한 toString() 메서드가 없기 때문에 Object.prototype.toString() 메서드를 실행합니다.
2단계
1단계의 결과값은 '[Object object]' 문자열이며 원시 타입이기 때문에 이 결과를 반환합니다.
빈 객체의 toString() 메서드의 결과값이 '[Object object]' 문자열 원시 타입이기 때문에 이 결과를 반환하는 것입니다.
또한 toString() 메서드의 결과값이 원시 타입이기 때문에 valueOf() 메서드는 호출되지 않습니다.
▷숫자로 변환
객체가 숫자로 변환되는 과정 역시 단계별로 보겠습니다.
1단계
객체에 정의된 valueOf() 메서드를 호출합니다. 별도로 정의한 valueOf() 메서드가 없다면 기본적으로 Object.prototype.valueOf() 메서드를 실행합니다. Object.prototype.valueOf() 메서드는 결과 값으로 객체를 그대로 반환합니다.
2단계
1단계의 결과가 원시 타입이라면 그 결과를 숫자로 변환하여 반환하고, 그렇지 않다면 toString() 메서드를 호출합니다. 별도로 정의한 toString() 메서드가 없다면 기본적으로 Object.prototype.toString() 메서드를 실행합니다.
3단계
toString() 메서드의 결과 값이 원시 타입이라면 그 결과를 숫자로 변환하여 반환하고, 그렇지 않다면 TypeError가 발생합니다.
valueOf() 메서드를 먼저 호출한 후 toString() 메서드를 호출하는 것을 제외하면 객체를 문자열로 변환하는 과정과 유사합니다.
console.log(Number({})); // NaN
1단계
빈 객체를 숫자로 변환하기 위해 객체의 valueOf() 메서드를 호출합니다. 직접 정의한 valueOf() 메서드가 없기 때문에 Object.prototype.valueOf() 메서드를 실행합니다.
2단계
1단계의 결과값은 원시 타입이 아닌 빈 객체를 그대로 반환하기 때문에 toString() 메서드를 호출합니다.
3단계
toString() 메서드의 결과값은 '[Object object]' 문자열이며 원시 타입이기 때문에 이 결과를 숫자로 변환합니다. '[Object object]' 문자열은 숫자로 변환할 수없는 값이기 때문에 최종적으로 NaN을 반환합니다.
▷객체의 valueOf()와 toString()
만약 valueOf()와 toString() 메서드를 직접 정의한 경우는 어떤 결과 값을 반환할까요?
const obj = {
valueOf() {
return 1;
},
toString() {
return 'toString';
}
}
console.log(String(obj)); // 'toString'
console.log(Number(obj)); // 1
객체의 valueOf()와 toString() 메서드를 직접 정의하면 타입 변환의 결과도 달라짐을 알 수 있습니다.
그렇기 때문에 valueOf()와 toString() 메서드를 재정의할 때는 주의해야 합니다.
배열, Date, 정규식과 같은 특수한 객체들은 자체적인 toString() 또는 valueOf() 메서드를 가지고 있습니다.
예를 들어 배열을 문자열로 변경하면 배열의 원소를 콤마(,)로 구분하여 문자열로 병합합니다.
3) 암시적 강제 변환
암시적 강제 변환은 연산 중에 내부적으로 타입을 변환하는 것입니다.
명시적 강제 변환과 달리 코드에서 명확하게 타입을 변환하는 것인지 알기 어렵습니다.
많은 개발자가 자바스크립트를 좋아하지 않는 이유 중 하나이며, 초보 개발자들이 가장 헷갈려 하는 부분이기도 합니다.
하지만 암시적 강제 변환 역시 ECMAScript 명세의 기준대로 정확하게 동작하고 있습니다.
이 글에서 자바스크립트의 타입 변환은 명확한 기준이 있다는 점이 전달되기 바랍니다.
암시적 강제 변환은 타입이 아닌 연산자를 기준으로 살펴보겠습니다.
▷덧셈 연산자
덧셈 연산자는 숫자 연산이나 문자열을 병합할 때 사용하는 것이라고 생각할 수 있습니다.
하지만 덧셈 연산자는 몇 가지 특징이 있습니다.
① 피연산자 중 하나가 문자열 타입인 경우 나머지 타입도 문자열로 변환하여 병합합니다.
// 문자열과 숫자의 덧셈 연산은 숫자를 문자로 변환하여 병합합니다.
console.log(1 + ''); // '1'
[NOTE]
String()과 a + ''의 문자열 변환은 차이가 있습니다. 피연산자의 타입이 객체인 경우 String() 함순느 객체의 toString(), valueOf()의 순서로 메서드를 호출하여 결과를 변환하며, a+''는 valueOf(), toString()의 순서로 호출된 결과를 반환합니다.
② 피연산자 중 하나가 객체이며 문자열로 변환 가능한 경우 문자열로 변환하여 연산합니다.객체의 문자열 변환 과정은 문자열로 변환에서 설명한 과정과 동일합니다.
// 빈 객체는 '[Object object]' 문자열로 변환이 가능하므로 숫자 1을 문자열로 변환하여 두 문자열을 병합합니다.
console.log(1 + {}); // '1[Object object]'
③ 피연산자가 모두 문자열과 객체가 아닌 경우 숫자로 변환하여 연산합니다. 만약 변환 결과의 타입이 각각 다른 경우 TypeError가 발생합니다.
// 피연산자 중 객체나 문자열이 없기 때문에 true를 숫자로 변환하여 연산합니다.
console.log(1 + true); // 2
보통 숫자 연산에 사용하던 덧셈 연산자에 생각보다 많은 타입 변환 규칙이 존재한다는 것을 알 수 있습니다.
처음 보면 이게 뭐지? 라고 생각할 수 있지만 위의 세 가지 규칙만 이해하면 어떻게 동작할지 알 수 있습니다.
ECMAScript 명세에는 더 세부적인 단계가 있지만, 위의 세 가지 규칙만 잘 이해해도 덧셈 연산자를 올바르게 사용하는데 큰 문제가 없을 것입니다.
▷동등 연산자
앞서 [관계형 연산자]에서 동등 연산자의 가장 큰 특징은 암시적 강제 변환을 허용하는 것이라고 하였습니다.
물론 피연산자의 타입이 서로 같은 경우에는 변환하지 않습니다.
동등 연산자가 어떤 규칙에 의해 동작하는지 살펴봅시다.
① 피연산자 중 하나는 문자열, 하나는 숫자인 경우 문자열을 숫자로 변환하여 동등함을 비교합니다.
// 문자열 '1'을 숫자로 변환하여 동등함을 판단합니다.
console.log(1 == '1') // true
② 피연산자 중 하나는 문자열, 다른 하나는 BigInt인 경우 문자열을 BigInt로 변환하여 동등함을 비교합니다.
// 문자열 '1'을 BigInt로 변환하여 동등함을 판단합니다.
console.log(1n == '1'); // true
③ 피연산자 중 하나는 null, 다른 하나는 undefined인 경우 동등하게 판단합니다.
console.log(null == undefined); // true
console.log(undefined == null); // true
④ 피연산자 중 하나가 불리언일 경우 불리언을 숫자로 변환하여 동등함을 비교합니다.
// 불리언 true를 숫자로 변환하여 비교합니다.
console.log(true == 1); // true
⑤ 피연산자 중 하나는 객체, 다른 하나가 문자열, 숫자, BigInt, 심볼 중 하나일 경우 객체를 원시 타입으로 변환하여 동등함을 비교합니다.
// 빈 객체를 원시 타입으로 변환한 후 비교합니다.
console.log('[Object object]' == {}); // true
⑥ 피연산자 중 하나는 숫자, 다른 하나는 BigInt인 경우 내부적인 숫자 비교 알고리즘에 따라 비교한 결과를 반환합니다.
console.log(1 == 1n); // true
동등 비교를 하는 과정에서 다양한 암시적 강제 변환이 일어나는 것을 알 수 있습니다.
규칙들을 하나하나 살펴보면 명확한 기준이 있고 이해하기 어렵지 않지만, 타입 변환으로 인해 가독성이 떨어지는 경우가 있습니다.
console.log(true == '1'); // true
true와 문자열 '1'은 전혀 다른 타입으로 동등 비교 시 false가 나올 것 같지만 그렇지 않습니다.
불리언 true는 숫자 1로 변환되어 문자열 '1'과 비교하게 되고, 이 과정에서 문자열 '1'은 숫자 1로 변환되어 최종적으로 숫자 1과 1을 비교하게 됩니다.
위에서 설명한 변환 규칙을 충실히 따른 결과이지만 확실히 가독성이 떨어집니다.
이런 경우에는 명확하게 엄격한 동등 연산자를 사용하는 것이 가독성에 훨씬 좋습니다.
그럼 헷갈리게 동등 연산자를 사용하지 말고 엄격한 동등 연산자만 사용하는게 좋은 것 아닌가요?
하지만 동등 연산자도 무조건 나쁜 것만은 아닙니다.
function isEmpty(a) {
if (a == null) {
//...
}
}
특정 값의 비어 있음을 판단하기 위해 동등 연산자를 사용하였습니다.
동등 연산자는 null과 undefined를 동등하게 보기 때문에 두 경우의 수를 모두 편리하게 찾아낼 수 있습니다.
동등 연산자는 코드 가독성을 위해 팀원들과 컨벤션을 정하여 사용하는 것이 좋습니다.
"우리 팀에서는 암시적 강제 변환을 허용하지 않겠다!"는 결정이 나면 안전하게 엄격한 동등 연산자만 사용하면 됩니다.
반면, 만약 팀 내에서 명확한 기준을 가지고 동등 연산자를 효율적으로 사용할 수 있다면 동등 연산자도 그 팀에서는 충분히 좋은 역할을 할 수 있습니다.
그리고 이러한 판단을 할 수 있으려면 적어도 앞서 설명한 규칙들은 알고 있어야 합니다.
▷비교 연산자
비교 연산자는 숫자 데이터의 대소 비교에서 사용하는 경우가 대부분일 것입니다.
하지만 비교 연산자 역시 동등 연산자처럼 피연산자가 서로 다른 타입인 경우 암시적 강제 변환이 발생합니다.
ECMAScript 명세는 a < b 연산 기준으로 설명하고 있기 때문에 이 글에서도 이 기준을 따라 알아보겠습니다.
비교 연산자는 크게 문자열 데이터의 비교, 그 이외의 경우 두 가지로 나누어져 있습니다.
다만, 피연산자가 객체인 경우 먼저 객체를 원시 타입으로 변환한 후 비교합니다.
변환된 결과가 모두 문자열이라면 문자열 비교를 하고 이외에는 문자열 외의 비교 규칙에 따라 비교합니다.
- 문자열 비교
문자열 비교는 각 문자를 알파벳 순서로 비교합니다.
또한 왼쪽에서부터 문자 단위로 비교합니다. 즉 '1'과 '04' 로 보았을 때, 왼쪽 가장 처음 문자열 '1'과 '0'을 비교했을 때 이미 왼쪽이 더 큼을 알 수 있습니다.
console.log('a' < 'b'); // true
// 왼쪽에서부터 문자 단위로 비교한다는 것을 주의하세요. 왼쪽부터 '1'과 '0'을 먼저 비교하고 그 다음 문자를 비교합니다.
console.log('1' < '04') // false
피연산자가 배열인 경우를 알아봅시다.
console.log(['a'] < ['b']); // true
배열은 각각 'a', 'b' 문자열로 변환되기 때문에 문자열끼리의 비교를 수행합니다.
- 문자열 외의 비교
문자열끼리의 비교가 아닌 그 외의 경우는 아래와 같은 규칙으로 동작합니다.
① 피연산자 중 하나는 문자열, 다른 하나는 BigInt인 경우 문자열을 BigInt로 변환하여 비교합니다.
// 문자열 '1'을 BigInt로 변환하여 비교합니다.
console.log('1' < 2n); // true
② 피연산자를 모두 숫자로 변환하여 비교합니다. 만약 피연산자 중 하나는 숫자, 다른 하나는 BigInt인 경우 내부적인 숫자 비교 알고리즘에 의해 비교를 수행합니다.
// 불리언 true를 숫자로 변환합니다.
console.log(1 < true); // false
// 숫자와 BigInt는 내부 숫자 비교 알고리즘에 의해 비교됩니다.
console.log(1n < 2); // true
- 비교 연산과 타입 변환
비교 연산은 동등 연산과 달리 엄격한 비교 연산자 같은 표현식은 존재하지 않습니다.
즉 다른 타입 간의 비교 연산에서 암시적인 강제 연산을 막을 수 없습니다.
하지만 동등 연산과 달리 비교 연산은 서로 다른 타입에 대해 사용할 일이 드물고, 객체나 배열을 원시 타입과 비교하는 일도 거의 없습니다.
// 아래와 같은 비교 연산 코드는 실제로 거의 사용하지 않을 것입니다.
const a = '1';
const b = ['02'];
if (a < b) {
// ...
}
만약 서로 다른 타입을 대상으로 비교 연산자를 사용한다면, 명시적 강제 변환을 통해 변환한 후 사용하는 것이 안전합니다.
const a = '1';
const b = ['02'];
if (Number(a) < Number(b)) {
// ...
}
Number() 함수를 사용하여 모두 숫자로 변환한 후 비교합니다.
이처럼 다른 타입의 비교 연산은 명시적 타입 변환을 통해 동일한 타입으로 변환한 후 실행하는 것이 안전합니다.
▷조건 표현식과 논리 연산자
조건 표현식에서 암시적 강제 변환은 아주 흔하게 사용됩니다.
모든 값은 불리언으로 변환되어 조건 표현식에서 평가됩니다.
빈 문자열, null, undefined 등 falsy한 값을 필터링하기 위해 많이 사용하며 명시적 강제 변환보다 더 많이 사용됩니다.
const a = 0;
const b = 'javascript';
const c = null;
if (a) {
console.log('호출되지 않음');
}
while (b) {
console.log('thuthy');
break;
}
console.log(c ? 'truthy' : 'falsy'); // 'falsy'
조건 표현식에서의 암시적 강제 변환은 Boolean() 함수나 이중 부정 연산자를 사용하여 명시적 강제 변환을 수행하는 것보다 훨씬 간결하게 조건식을 표현할 수 있습니다.
- 논리 연산자(&&, ||)
자바스크립트의 논리 연산자는 단락 평가 방식을 따릅니다.
이는 다른 프로그래밍 언어에서도 쉽게 볼 수 있는 동작입니다.
하지만 자바스크립트의 논리 연산자는 특이한 점이 있습니다.
논리 연산자의 결과 값이 불리언 타입이 아닐 수 있다는 것입니다.
그럼 어떤 값이 결과 값이 되는 걸까요?
결과 값은 아래와 같은 규칙을 따라 반환됩니다.
①
&& 논리 연산자는 첫 번째 피연산자의 값이 true로 평가되는 경우 두 번째 피연산자의 값을 반환하고, false로 평가되면 첫 번째 피연산자의 값을 반환합니다.
②
|| 논리 연산자는 첫 번째 피연산자의 값이 true로 평가되는 경우 첫 번째 피연산자의 값을 반환하고, false로 평가되면 두 번째 피연산자의 값을 반환합니다.
const a = null;
const b = 'javascript';
const c = 1;
console.log(a && b); // null
console.log(b || c); // 'javascript'
&& 연산의 결과 값이 null인 이유부터 단계별로 알아보겠습니다.
1단계
&& 논리 연산자는 첫 번째 피연산자 a부터 평가합니다.
2단계
첫 번째 피연산자 a는 불리언 값이 아니므로 암시적 타입 변환을 통해 불리언 값으로 변환됩니다. null은 falsy 값이기 때문에 false로 변환됩니다.
3단계
a의 평가 결과가 false이기 때문에 단락 평가 방식에 따라 다음 피연산자인 b는 평가하지 않습니다.
4단계
최종적으로 피연산자 a의 값을 반환합니다.
&& 연산자의 첫 번째 피연산자가 false로 평가되어 첫 번째 피연산자의 값인 null을 반환합니다.
이 과정은 위에서 설명한 && 논리 연산자의 규칙과 동일함을 알 수 있습니다.
|| 연산의 결과 값이 나온 이유도 단계별로 알아보겠습니다.
1단계
|| 논리 연산자는 첫 번째 피연산자 b부터 평가합니다.
2단계
첫 번째 피연산자 b는 불리언 값이 아니므로 암시적 타입 변환을 통해 불리언 값으로 변환됩니다. 'javascript' 문자열은 truthy 값이기 때문에 true로 변환됩니다.
3단계
b의 평가 결과가 true이기 때문에 단락 평가 방식에 따라 다음 피연산자인 c는 평가하지 않습니다.
4단계
최종적으로 피연산자 b의 값을 반환합니다.
마찬가지로 위에서 설명한 || 논리 연산자의 규칙과 동일한 결과를 반환합니다.
- 논리 연산자의 활용
앞서 설명한 논리 연산자의 특징은 디폴트 값을 설정하거나 조건에 따라 함수를 실행할 때 유용합니다.
function setDefault(a) {
return a || 'default string';
}
setDefault() 함수는 a의 값이 falsy 값이 경우 'default string'이라는 문자열을 디폴트 값으로 설정합니다.
const a = 'javascript';
function doSomething() {
// ...
}
a && doSomething();
위 예제 코드는 피연산자 a가 truthy 값인 경우에만 doSomething() 함수를 실행합니다.
만약 React를 사용해 보았다면 이런 코드가 익숙할 것입니다.
컴포넌트를 조건에 따라 렌더링할 때 자주 사용하는 표현식입니다.
falsy 값이 아닌 null, undefined처럼 값이 비어 있는 경우에만 디폴트 값을 설정하고 싶을 때도 있을 것입니다.
그런 경우에는 || 연산자가 아닌 ES2020에서 등장한 null 병합(nullish coalescing) 연산자를 사용할 수 있습니다.
const a = '';
// a가 null, undefined인 경우만 'default' 문자열이 b의 값으로 할당됩니다.
const b = a ?? 'default';
'12 Javascript > 30 각종 함수' 카테고리의 다른 글
자바스크립트-String(slice, substring, substr, replace, replaceAll, includes, split, trim) (0) | 2023.01.03 |
---|---|
30 parseInt 와 Number비교 (0) | 2021.07.16 |
30 대소문자전환 toUpperCase() toLowerCase() (0) | 2021.07.16 |
30 replace 문제점 (0) | 2021.07.16 |
31 날자관련 각종 utils (0) | 2021.07.16 |