📢 22/07/26 복습
javascript.info, https://ko.javascript.info/logical-operators
javascript.info, https://ko.javascript.info/nullish-coalescing-operator
참고 사이트에 내용을 개인적으로 복습하기 편하도록 재구성한 글입니다.
자세한 설명은 참고 사이트를 살펴보시기 바랍니다.
자바스크립트엔 세 종류의 논리 연산자 ||
(OR), &&
(AND), !
(NOT)이 있습니다.
연산자에 "논리"라는 수식어가 붙긴 하지만 논리 연산자는 피연산자로 불린형뿐만 아니라 모든 타입의 값을 받을 수 있습니다. 연산 결과 역시 모든 타입이 될 수 있습니다.
OR 연산자는 두 개의 수직선 기호로 만들 수 있습니다.
result = a || b;
전통적인 프로그래밍에서 OR 연산자는 불린값을 조작하는 데 쓰입니다. 인수 중 하나라도 true
이면 true
를 반환하고, 그렇지 않으면 false
를 반환하죠.
자바스크립트의 OR 연산자는 다루긴 까다롭지만 강력한 기능을 제공합니다. OR을 어떻게 응용할 수 있는지 알아보기 전에 먼저, OR 연산자가 불린값을 어떻게 다루는지 알아보도록 합시다.
OR 연산자는 이항 연산자이므로 아래와 같이 네 가지 조합이 가능합니다.
alert( true || true ); // true
alert( false || true ); // true
alert( true || false ); // true
alert( false || false ); // false
피연산자가 모두 false
인 경우를 제외하고 연산 결과는 항상 true
입니다.
피연산자가 불린형이 아니면, 평가를 위해 불린형으로 변환됩니다.
예를 들어, 연산 과정에서 숫자 1
은 true
로, 숫자 0
은 false
로 바뀌죠.
if (1 || 0) { // if( true || false ) 와 동일하게 동작합니다.
alert( "truthy!" );
}
지금까진 피연산자가 불린형인 경우만을 다뤘습니다. 전통적인 방식이죠. 이제 자바스크립트에서만 제공하는 논리연산자 OR의 "추가"기능에 대해 알아보겠습니다.
OR 연산자와 피연산자가 여러 개인 경우는 아래와 같은 알고리즘으로 동작합니다.
result = value1 || value2 || value3;
이때, OR 연산자는 다음 순서에 따라 연산을 수행합니다.
🕐 가장 왼쪽 피연산자부터 시작해 오른쪽으로 나아가며 피연산자를 평가합니다.
🕑 각 피연산자를 불린형으로 변환합니다. 변환 후 그 값이 true
이면 연산을 멈추고 해당 피연산자의 변환 전 원래 값을 반환합니다.
🕒 피연산자 모두를 평가한 경우(모든 피연산자가 false
로 평가되는 경우)엔 마지막 피연산자를 반환합니다.
💥 여기서 핵심은 반환 값이 형 변환을 하지 않은 원래 값이라는 것입니다.
alert( 1 || 0 ); // 1 (1은 truthy임)
alert( null || 1 ); // 1 (1은 truthy임)
alert( null || 0 || 1 ); // 1 (1은 truthy임)
alert( undefined || null || 0 ); // 0 (모두 falsy이므로, 마지막 값을 반환함)
이런 OR 연산자의 추가 기능을 이용하면 여러 용도로 OR 연산자를 활용할 수 있습니다.
OR 연산자가 제공하는 또 다른 기능은 "단락 평가(short circuit evaluation)"입니다.
위에서 설명해 드린 바와 같이 ||
은 왼쪽부터 시작해서 오른쪽으로 평가를 진행하는데, truthy를 만나면 나머지 값들은 건드리지 않은 채 평가를 멈춥니다. 이런 프로세스를 "단락 평가"라고 합니다.
단락 평가의 동작 방식은 두 번째 피연산자가 변수 할당과 같은 부수적인 효과(side effect)를 가지는 표현식 일 때 명확히 볼 수 있습니다.
아래 예시를 실행하면 두 번째 메시지만 출력됩니다.
true || alert("not printed");
false || alert("printed");
첫 번째 줄의 ||
연산자는 true
를 만나자마자 평가를 멈추기 때문에 alert
가 실행되지 않습니다.
단락 평가는 연산자 왼쪽 조건이 falsy일 때만 명령어를 실행하고자 할 때 자주 쓰입니다.
두 개의 앰퍼샌드를 연달아 쓰면 AND 연산자 &&
를 만들 수 있습니다.
result = a && b;
전통적인 프로그래밍에서 AND 연산자는 두 피연산자가 모두가 참일 때 true
를 반환합니다. 그 외의 경우는 false
를 반환하죠.
alert( true && true ); // true
alert( false && true ); // false
alert( true && false ); // false
alert( false && false ); // false
OR 연산자와 마찬가지로 AND 연산자의 피연산자도 타입에 제약이 없습니다.
if (1 && 0) { // 피연산자가 숫자형이지만 논리형으로 바뀌어 true && false가 됩니다.
alert( "if 문 안에 falsy가 들어가 있으므로 alert창은 실행되지 않습니다." );
}
AND 연산자와 피연산자가 여러 개인 경우를 살펴봅시다.
result = value1 && value2 && value3;
AND 연산자 &&
는 아래와 같은 순서로 동작합니다.
🕐 가장 왼쪽 피연산자부터 시작해 오른쪽으로 나아가며 피연산자를 평가합니다.
🕑 각 피연산자는 불린형으로 변환됩니다. 변환 후 값이 false
이면 평가를 멈추고 해당 피연산자의 변환 전 원래 값을 반환합니다.
🕒 피연산자 모두가 평가되는 경우(모든 피연산자가 true
로 평가되는 경우)엔 마지막 피연산자가 반환됩니다.
💥 여기서 핵심은 반환 값이 형 변환을 하지 않은 원래 값이라는 것입니다.
// 첫 번째 피연산자가 truthy이면,
// AND는 두 번째 피연산자를 반환합니다.
alert( 1 && 0 ); // 0
alert( 1 && 5 ); // 5
// 첫 번째 피연산자가 falsy이면,
// AND는 첫 번째 피연산자를 반환하고, 두 번째 피연산자는 무시합니다.
alert( null && 5 ); // null
alert( 0 && "아무거나 와도 상관없습니다." ); // 0
AND 연산자에도 피연산자 여러 개를 연속해서 전달할 수 있습니다. 첫 번째 falsy가 어떻게 반환되는지 예시를 통해 살펴봅시다.
alert( 1 && 2 && null && 3 ); // null
아래 예시에선 AND 연산자의 피연산자가 모두 truthy이기 때문에 마지막 피연산자가 반환됩니다.
alert( 1 && 2 && 3 ); // 마지막 값, 3
🔥 &&
의 우선순위가 ||
보다 높습니다.
AND 연산자 &&
의 우선순위는 OR 연산자 ||
보다 높습니다. 따라서 a && b || c && d
는 (a && b) || (c && d)
와 동일하게 동작합니다.
🔥 if
를 ||
나 &&
로 대체하지 마세요.
어떤 개발자들은 AND 연산자 &&
를 if
문을 "짧게" 줄이는 용도로 사용하곤 합니다.
let x = 1;
(x > 0) && alert( "0보다 큽니다!" );
&&
의 오른쪽 피연산자는 평가가 &&
우측까지 진행되어야 실행됩니다. 즉, (x > 0)
이 참인 경우에만 alert
문이 실행되죠.
위 코드를 if
문을 써서 바꾸면 다음과 같습니다.
let x = 1;
if (x > 0) alert( "0보다 큽니다!" );
&&
를 사용한 코드가 더 짧긴 하지만 if
문을 사용한 예시가 코드에서 무엇을 구현하고자 하는지 더 명백히 드러내고, 가독성도 좋습니다. 그러니 if
조건문이 필요하면 if
를 사용하고 AND 연산자는 연산자 목적에 맞게 사용합시다.
논리 연산자 NOT은 느낌표 !
를 써서 만들 수 있습니다.
NOT 연산자의 문법은 매우 간단합니다.
result = !value;
NOT 연산자는 인수를 하나만 받고, 다음 순서대로 연산을 수행합니다.
🕐 피연산자를 불린형(true
/ false
)으로 변환합니다.
🕑 위에서 변환된 값의 역을 반환합니다.
alert( !true ); // false
alert( !0 ); // true
NOT을 두 개 연달아 사용(!!
)하면 값을 불린형으로 변환할 수 있습니다.
alert( !!"non-empty string" ); // true
alert( !!null ); // false
이때, 첫 번째 NOT 연산자는 피연산자로 받은 값을 불린형으로 변환한 후 이 값의 역을 반환하고, 두 번째 NOT 연산자는 첫 번째 NOT 연산자가 반환한 값의 역을 반환합니다. 이렇게 NOT을 연달아 사용하면 특정 값을 불린형으로 변환할 수 있습니다.
참고로, 내장 함수 Boolean
을 사용하면 !!
을 사용한 것과 같은 결과를 도출할 수 있습니다.
alert( Boolean("non-empty string") ); // true
alert( Boolean(null) ); // false
NOT 연산자의 우선순위는 모든 논리 연산자 중에서 가장 높기 때문에 항상 &&
나 ||
보다 먼저 실행됩니다.
nullish 병합 연산자(nullish coalescing operator) ??
를 사용하면 짧은 문법으로 여러 피연산자 중 그 값이 "확정되어있는" 변수를 찾을 수 있습니다.
a ?? b
의 평가 결과는 다음과 같습니다.
📌 a
가 null
도 아니고 undefined
도 아니면 a
📌 그 외의 경우는 b
nullish 병합 연산자 ??
없이 x = a ?? b
와 동일한 동작을 하는 코드를 작성하면 다음과 같습니다.
x = (a !== null && a !== undefined) ? a : b;
비교 연산자와 논리 연산자만으로 nullish 병합 연산자와 같은 기능을 하는 코드를 작성하니 코드 길이가 길어지네요.
또 다른 예시를 살펴봅시다. firstName
, lastName
, nickName
이란 변수에 사용자 이름이나 별명을 저장하는데, 사용자가 아무런 정보도 입력하지 않는 케이스도 허용한다고 해보겠습니다.
화면엔 세 변수 중 실제 값이 있는 변수의 값을 출력하는데, 세 변수 모두 값이 없다면 "익명의 사용자"가 출력되도록 해보죠.
이럴 때 nullish 병합 연산자 ??
를 사용하면 값이 정해진 변수를 간편하게 찾아낼 수 있습니다.
let firstName = null;
let lastName = null;
let nickName = "바이올렛";
// null이나 undefined가 아닌 첫 번째 피연산자
alert(firstName ?? lastName ?? nickName ?? "익명의 사용자"); // 바이올렛
nullish 병합 연산자는 OR 연산자와 상당히 유사해 보입니다. 실제로 위 예시에서 ??
를 ||
로 바꿔도 그 결과는 동일하기까지 하죠.
그런데 두 연산자 사이에는 중요한 차이점이 있습니다.
📌 ||
는 첫 번째 truthy 값을 반환합니다.
📌 ??
는 첫 번째 정의된(defined) 값을 반환합니다.
🔥 null
과 undefined
, 숫자 0
을 구분 지어 다뤄야 할 때 이 차이점은 매우 중요한 역할을 합니다.
height = height ?? 100;
height
에 값이 정의되지 않은경우 height
엔 100
이 할당됩니다.
이제 ??
와 ||
을 비교해봅시다.
let height = 0;
alert(height || 100); // 100
alert(height ?? 100); // 0
height || 100
은 height
에 0
을 할당했지만 0
을 falsy 한 값으로 취급했기 때문에 null
이나 undefined
를 할당한 것과 동일하게 처리합니다. 따라서 height || 100
의 평가 결과는 100
입니다.
반면 height ?? 100
의 평가 결과는 height
가 정확하게 null
이나 undefined
일 경우에만 100
이 됩니다. 예시에선 height
에 0
이라는 값을 할당했기 때문에 얼럿창엔 0
이 출력됩니다.
이런 특징 때문에 높이처럼 0
이 할당될 수 있는 변수를 사용해 기능을 개발할 땐 ||
보다 ??
가 적합합니다.
??
의 연산자 우선순위는 5로 꽤 낮습니다.
따라서 ??
는 =
와 ?
보다는 먼저, 대부분의 연산자보다는 나중에 평가됩니다.
그렇기 때문에 복잡한 표현식 안에서 ??
를 사용해 값을 하나 선택할 땐 괄호를 추가하는 게 좋습니다.
let height = null;
let width = null;
// 괄호를 추가!
let area = (height ?? 100) * (width ?? 50);
alert(area); // 5000
그렇지 않으면 *
가 ??
보다 우선순위가 높기 때문에 *
가 먼저 실행됩니다.
결국엔 아래 예시처럼 동작하겠죠.
// 원치 않는 결과
let area = height ?? (100 * width) ?? 50;
??
엔 자바스크립트 언어에서 규정한 또 다른 제약사항이 있습니다.
안정성 관련 이슈 때문에 ??
는 &&
나 ||
와 함께 사용하지 못합니다.
아래 예시를 실행하면 문법 에러가 발생합니다.
let x = 1 && 2 ?? 3; // SyntaxError: Unexpected token "??"
이 제약에 대해선 아직 논쟁이 많긴 하지만 사람들이 ||
를 ??
로 바꾸기 시작하면서 만드는 실수를 방지하고자 명세서에 제약이 추가된 상황입니다.
제약을 피하려면 괄호를 사용해주세요.
let x = (1 && 2) ?? 3; // 제대로 동작합니다.
alert(x); // 2