표현식과 연산자

표현식과 연산자 (Expressions and operators)

이 장은 JavaScript의 표현식과 연산자를 설명한다. 여기에는 할당(assignment), 비교(comparison), 산술(arithmetic), 비트(bitwise), 논리(logical), 문자열(string), 삼항(ternary) 연산자 등이 포함된다.

출처: Expressions and operators - JavaScript | MDN

본문

표현식 이란

높은 수준에서 보면 **표현식(expression)**은 값으로 귀결되는 유효한 코드 단위다. 표현식에는 두 가지 종류가 있다: 부작용(side effect)을 가진 것(값을 할당하는 것)과 순수하게 평가만 하는 것.

x = 7은 첫 번째 종류의 예다. 이 표현식은 = 연산자를 사용해 값 7을 변수 x에 할당한다. 표현식 자체는 7로 평가된다.

3 + 4는 두 번째 종류의 예다. 이 표현식은 + 연산자로 3과 4를 더해 값 7을 만든다. 그러나 이것이 결국 더 큰 구조의 일부가 아니라면(예: const z = 3 + 4 같은 변수 선언), 그 결과는 즉시 버려진다. 이는 대개 평가가 어떤 효과도 만들지 않기 때문에 프로그래머의 실수다.

위의 예에서도 보듯, 모든 복잡한 표현식은 =+ 같은 연산자로 결합된다. 이 절에서는 다음 연산자들을 소개한다.

  • 할당 연산자 (Assignment operators)
  • 비교 연산자 (Comparison operators)
  • 산술 연산자 (Arithmetic operators)
  • 비트 연산자 (Bitwise operators)
  • 논리 연산자 (Logical operators)
  • BigInt 연산자 (BigInt operators)
  • 문자열 연산자 (String operators)
  • 조건(삼항) 연산자 (Conditional (ternary) operator)
  • 쉼표 연산자 (Comma operator)
  • 단항 연산자 (Unary operators)
  • 관계 연산자 (Relational operators)

이 연산자들은 더 높은 우선순위의 연산자가 만들거나 기본 표현식 중 하나인 피연산자들을 결합한다. 연산자와 표현식의 완전하고 상세한 목록은 참조(reference)에서도 볼 수 있다.

연산자 우선순위 (Operator precedence)

연산자 우선순위는 표현식을 평가할 때 연산자가 적용되는 순서를 결정한다. 예:

const x = 1 + 2 * 3;
const y = 2 * 3 + 1;

*+가 서로 다른 순서로 오는데도 두 표현식 모두 7이 된다. 왜냐하면 *+보다 우선순위가 높아 *로 결합된 표현식이 항상 먼저 평가되기 때문이다. 괄호(그룹화 표현식, 기본 표현식)를 사용하면 연산자 우선순위를 재정의할 수 있다. 우선순위의 전체 표와 여러 주의사항은 Operator Precedence Reference 페이지를 보라.

이항·단항·삼항 연산자

JavaScript는 **이항(binary)**과 단항(unary) 연산자, 그리고 하나의 특별한 삼항(ternary) 연산자인 조건 연산자를 가진다.

  • 이항 연산자는 두 개의 피연산자(연산자 앞 하나, 뒤 하나)를 요구한다: operand1 operator operand2. 예: 3 + 4 또는 x * y. 이 형태를 중위(infix) 이항 연산자라고 하며, 연산자가 두 피연산자 사이에 놓이기 때문이다. JavaScript의 모든 이항 연산자는 중위다.
  • 단항 연산자는 단일 피연산자를 요구하며 연산자 앞이나 뒤에 온다: operator operand 또는 operand operator. 예: x++ 또는 ++x. operator operand 형태를 전위(prefix) 단항 연산자, operand operator 형태를 후위(postfix) 단항 연산자라고 한다. ++--가 JavaScript에서 유일한 후위 연산자다. !, typeof 등 다른 모든 연산자는 전위다.

할당 연산자 (Assignment operators)

할당 연산자는 오른쪽 피연산자의 값에 기반해 왼쪽 피연산자에 값을 할당한다. 기본 할당 연산자는 등호(=)이며 오른쪽 피연산자의 값을 왼쪽 피연산자에 할당한다. 즉 x = f()f()의 값을 x에 할당하는 할당 표현식이다.

다음 표의 연산을 축약하는 **복합 할당 연산자(compound assignment operators)**도 있다.

이름 축약 연산자 의미
할당 (Assignment) x = f() x = f()
덧셈 할당 (Addition assignment) x += f() x = x + f()
뺄셈 할당 (Subtraction assignment) x -= f() x = x - f()
곱셈 할당 (Multiplication assignment) x *= f() x = x * f()
나눗셈 할당 (Division assignment) x /= f() x = x / f()
나머지 할당 (Remainder assignment) x %= f() x = x % f()
거듭제곱 할당 (Exponentiation assignment) x **= f() x = x ** f()
왼쪽 시프트 할당 (Left shift assignment) x <<= f() x = x << f()
오른쪽 시프트 할당 (Right shift assignment) x >>= f() x = x >> f()
부호 없는 오른쪽 시프트 할당 (Unsigned right shift assignment) x >>>= f() x = x >>> f()
비트 AND 할당 (Bitwise AND assignment) x &= f() x = x & f()
비트 XOR 할당 (Bitwise XOR assignment) x ^= f() x = x ^ f()
비트 OR 할당 (Bitwise OR assignment) `x = f()`
논리 AND 할당 (Logical AND assignment) x &&= f() x && (x = f())
논리 OR 할당 (Logical OR assignment) `x
nullish 병합 할당 (Nullish coalescing assignment) x ??= f() x ?? (x = f())

속성에 할당하기 (Assigning to properties)

표현식이 객체로 평가된다면 할당 표현식의 왼쪽이 그 표현식의 속성에 할당을 할 수 있다. 예:

const obj = {};

obj.x = 3;
console.log(obj.x); // 3을 출력
console.log(obj); // { x: 3 }을 출력

const key = "y";
obj[key] = 5;
console.log(obj[key]); // 5를 출력
console.log(obj); // { x: 3, y: 5 }을 출력

객체에 대한 더 자세한 내용은 Working with Objects를 읽으라.

표현식이 객체로 평가되지 않으면 그 표현식의 속성에 대한 할당은 할당되지 않는다.

const val = 0;
val.x = 3;

console.log(val.x); // undefined를 출력
console.log(val); // 0을 출력

strict mode에서는 프리미티브에 속성을 할당할 수 없으므로 위 코드는 예외를 던진다.

수정할 수 없는 속성, 또는 속성이 없는 표현식(null이나 undefined)의 속성에 값을 할당하는 것은 오류다.

구조 분해 (Destructuring)

더 복잡한 할당을 위해 구조 분해(destructuring) 문법이 있다. 이는 배열 리터럴과 객체 리터럴의 구성을 반영하는 문법을 사용해 배열이나 객체에서 데이터를 추출할 수 있게 해주는 JavaScript 표현식이다.

구조 분해 없이는 배열과 객체에서 값을 추출하는 데 여러 문장이 필요하다.

const foo = ["one", "two", "three"];

const one = foo[0];
const two = foo[1];
const three = foo[2];

구조 분해를 사용하면 단일 문장으로 여러 값을 서로 다른 변수로 추출할 수 있다.

const [one, two, three] = foo;

평가와 중첩 (Evaluation and nesting)

일반적으로 할당은 변수 선언 안(즉 const, let, var와 함께) 또는 독립 문장으로 사용된다.

// 변수 x를 선언하고 f()의 결과로 초기화한다.
// x = f() 할당 표현식의 결과는 버려진다.
let x = f();

x = g(); // 변수 x를 g()의 결과로 재할당한다.

그러나 다른 표현식처럼 x = f() 같은 할당 표현식도 결과 값으로 평가된다. 이 결과 값은 보통 사용되지 않지만 다른 표현식이 사용할 수도 있다.

할당을 체이닝하거나 다른 표현식에 중첩하면 놀라운 동작이 나올 수 있다. 이런 이유로 일부 JavaScript 스타일 가이드는 할당의 체이닝·중첩을 권장하지 않는다. 하지만 할당 체이닝·중첩이 때때로 발생할 수 있으므로 그것들이 어떻게 동작하는지 이해하는 것이 중요하다.

할당 표현식을 체이닝·중첩함으로써 그 결과를 다른 변수에 할당하거나, 기록하거나, 배열 리터럴이나 함수 호출 안에 넣을 수 있다.

let x;
const y = (x = f()); // 또는 동등하게: const y = x = f();
console.log(y); // 할당 x = f()의 반환 값을 기록

console.log(x = f()); // 반환 값을 직접 기록

// 할당 표현식은 일반적으로 표현식이 허용되는 어디든 중첩될 수 있다.
// 예: 배열 리터럴의 요소 또는 함수 호출의 인자.
console.log([0, x = f(), 0]);
console.log(f(0, x = f(), 0));

평가 결과는 위 표의 "의미" 열에서 = 기호 오른쪽의 표현식과 일치한다. 즉 x = f()f()의 결과가 무엇이든 그 결과로 평가되고, x += f()는 합인 x + f()로, x **= f()는 거듭제곱인 x ** f()로 평가되는 식이다.

논리 할당의 경우 x &&= f(), x ||= f(), x ??= f()의 반환 값은 할당 없는 논리 연산의 값, 즉 각각 x && f(), x || f(), x ?? f()다.

이 표현들을 괄호나 배열 리터럴 같은 그룹화 연산자 없이 체이닝하면, 할당 표현식은 오른쪽에서 왼쪽으로 그룹화(오른쪽 결합)되지만 왼쪽에서 오른쪽으로 평가된다.

= 자체를 제외한 모든 할당 연산자에 대해 결과 값은 항상 연산 전의 피연산자 값에 기반한다는 점에 주목하라.

예를 들어 다음 함수 f, g와 변수 x, y가 선언되었다고 가정하자.

function f() {
  console.log("F!");
  return 2;
}
function g() {
  console.log("G!");
  return 3;
}
let x, y;

세 가지 예를 보자.

y = x = f();
y = [f(), x = g()];
x[f()] = g();

평가 예 1: y = x = f()는 할당 연산자 =가 오른쪽 결합이므로 y = (x = f())와 동등하다. 그러나 왼쪽에서 오른쪽으로 평가된다.

  • 할당 표현식 y = x = f()가 평가를 시작한다.
  • 이 할당의 왼쪽 y는 변수 y에 대한 참조로 평가된다.
  • 할당 표현식 x = f()가 평가를 시작한다.
  • 이 할당의 왼쪽 x는 변수 x에 대한 참조로 평가된다.
  • 함수 호출 f()는 콘솔에 "F!"를 출력하고 숫자 2로 평가된다.
  • f()의 그 2 결과가 x에 할당된다.
  • 할당 표현식 x = f()는 이제 평가를 마쳤고, 그 결과는 x의 새 값인 2다.
  • 그 2 결과는 차례로 y에도 할당된다.
  • 할당 표현식 y = x = f()는 이제 평가를 마쳤고, 그 결과는 y의 새 값인 2다. xy는 2로 할당되고 콘솔에는 "F!"가 출력됐다.

평가 예 2: y = [ f(), x = g() ]도 왼쪽에서 오른쪽으로 평가된다.

  • 할당 표현식 y = [ f(), x = g() ]가 평가를 시작한다.
  • 이 할당의 왼쪽 y는 변수 y에 대한 참조로 평가된다.
  • 내부 배열 리터럴 [ f(), x = g() ]가 평가를 시작한다.
  • 함수 호출 f()는 "F!"를 출력하고 2로 평가된다.
  • 할당 표현식 x = g()가 평가를 시작한다.
  • 이 할당의 왼쪽 x는 변수 x에 대한 참조로 평가된다.
  • 함수 호출 g()는 "G!"를 출력하고 3으로 평가된다.
  • g()의 그 3 결과가 x에 할당된다.
  • 할당 표현식 x = g()는 이제 평가를 마쳤고, 그 결과는 x의 새 값인 3이다. 그 3 결과는 내부 배열 리터럴의 다음 요소가 된다(f()의 2 다음).
  • 내부 배열 리터럴 [ f(), x = g() ]는 이제 평가를 마쳤고, 결과는 두 값의 배열 [ 2, 3 ]다.
  • [ 2, 3 ] 배열이 y에 할당된다.
  • 할당 표현식 y = [ f(), x = g() ]는 이제 평가를 마쳤고, 결과는 y의 새 값인 [ 2, 3 ]다. x는 3으로, y[ 2, 3 ]으로 할당되고 콘솔에는 "F!" 다음 "G!"가 출력됐다.

평가 예 3: x[f()] = g()도 왼쪽에서 오른쪽으로 평가된다. (이 예는 x가 이미 어떤 객체로 할당되어 있다고 가정한다.)

  • 할당 표현식 x[f()] = g()가 평가를 시작한다.
  • 이 할당의 왼쪽 속성 접근 x[f()]가 평가를 시작한다.
  • 이 속성 접근의 x는 변수 x에 대한 참조로 평가된다.
  • 그다음 함수 호출 f()는 "F!"를 출력하고 2로 평가된다.
  • 이 할당의 속성 접근 x[f()]는 이제 평가를 마쳤고, 결과는 변수 속성 참조 x[2]다.
  • 그다음 함수 호출 g()는 "G!"를 출력하고 3으로 평가된다.
  • 그 3이 x[2]에 할당된다. (이 단계는 x가 객체에 할당된 경우에만 성공한다.)
  • 할당 표현식 x[f()] = g()는 이제 평가를 마쳤고, 결과는 x[2]의 새 값인 3이다. x[2]는 3으로 할당되고 콘솔에는 "F!" 다음 "G!"가 출력됐다.

할당 체인 피하기 (Avoid assignment chains)

할당을 체이닝하거나 다른 표현식에 중첩하면 놀라운 동작이 나올 수 있다. 이런 이유로 같은 문장에서 할당 체이닝은 권장되지 않는다.

특히 const, let, var 문장에 변수 체인을 넣으면 흔히 동작하지 않는다. 가장 바깥/왼쪽 변수만 선언되고, 할당 체인 안의 다른 변수는 const/let/var 문장으로 선언되지 않는다. 예:

const z = y = x = f();

이 문장은 변수 x, y, z를 선언하는 것처럼 보인다. 그러나 실제로 z만 선언한다. yx는 (strict mode에서) 존재하지 않는 변수에 대한 잘못된 참조이거나, 더 나쁘게는 sloppy mode에서 xy에 대한 전역 변수를 암묵적으로 만들 수도 있다.

비교 연산자 (Comparison operators)

비교 연산자는 피연산자를 비교하고 비교가 참인지에 따라 논리 값을 반환한다. 피연산자는 숫자·문자열·논리·객체 값일 수 있다. 문자열은 Unicode 값을 사용해 표준 사전식(lexicographical) 순서로 비교된다. 대부분의 경우 두 피연산자가 같은 타입이 아니면 JavaScript는 비교에 적합한 타입으로 변환을 시도한다. 이 동작은 일반적으로 피연산자를 숫자로 비교하게 만든다.

비교에서 타입 변환의 유일한 예외는 ===!== 연산자다. 이들은 엄격한 동등/부등 비교를 수행하며, 동등을 검사하기 전에 피연산자를 호환 타입으로 변환하려 하지 않는다.

다음 표는 이 샘플 코드 관점에서 비교 연산자를 설명한다.

const var1 = 3;
const var2 = 4;
연산자 설명 true를 반환하는 예
동등 (==) 피연산자가 같으면 true 반환 3 == var1, "3" == var1, 3 == '3'
부등 (!=) 피연산자가 같지 않으면 true 반환 var1 != 4, var2 != "3"
엄격 동등 (===) 피연산자가 같고 같은 타입이면 true 반환. Object.is와 JS에서의 sameness도 참조 3 === var1
엄격 부등 (!==) 피연산자가 같은 타입이지만 같지 않거나, 다른 타입이면 true 반환 var1 !== "3", 3 !== '3'
큼 (>) 왼쪽 피연산자가 오른쪽보다 크면 true 반환 var2 > var1, "12" > 2
크거나 같음 (>=) 왼쪽이 오른쪽보다 크거나 같으면 true 반환 var2 >= var1, var1 >= 3
작음 (<) 왼쪽이 오른쪽보다 작으면 true 반환 var1 < var2, "2" < 12
작거나 같음 (<=) 왼쪽이 오른쪽보다 작거나 같으면 true 반환 var1 <= var2, var2 <= 5

참고: =>는 비교 연산자가 아니라 **화살표 함수(Arrow functions)**의 표기법이다.

산술 연산자 (Arithmetic operators)

산술 연산자는 숫자 값(리터럴 또는 변수)을 피연산자로 받아 단일 숫자 값을 반환한다. 표준 산술 연산자는 덧셈(+), 뺄셈(-), 곱셈(*), 나눗셈(/)이다. 부동소수점 숫자와 함께 사용할 때 이들은 대부분의 다른 프로그래밍 언어에서처럼 동작한다(특히 0으로 나누면 Infinity가 생성된다는 점에 주목). 예:

1 / 2; // 0.5
1 / 2 === 1.0 / 2.0; // true

표준 산술 연산(+, -, *, /) 외에 JavaScript는 다음 표의 산술 연산자도 제공한다.

연산자 설명
나머지 (%) 이항 연산자. 두 피연산자를 나눈 정수 나머지를 반환 12 % 5는 2 반환
증가 (++) 단항 연산자. 피연산자에 1을 더함. 전위(++x)로 쓰면 더한 후의 값을, 후위(x++)로 쓰면 더하기 전의 값을 반환 x가 3이면 ++xx를 4로 만들고 4를 반환, x++는 3을 반환하고 그제서야 x를 4로 만듦
감소 (--) 단항 연산자. 피연산자에서 1을 뺌. 반환 값은 증가 연산자와 유사 x가 3이면 --xx를 2로 만들고 2를 반환, x--는 3을 반환하고 그제서야 x를 2로 만듦
단항 부정 (-) 단항 연산자. 피연산자의 부정을 반환 x가 3이면 -x는 -3 반환
단항 플러스 (+) 단항 연산자. 피연산자가 이미 숫자가 아니면 숫자로 변환 시도 +"3"은 3, +true는 1 반환
거듭제곱 (**) 밑을 지수만큼 거듭제곱, 즉 base^exponent 2 ** 3은 8, 10 ** -1은 0.1 반환

비트 연산자 (Bitwise operators)

비트 연산자는 피연산자를 10진·16진·8진 숫자가 아니라 **32비트 집합(0과 1)**으로 취급한다. 예를 들어 10진수 9는 이진 표현 1001을 가진다. 비트 연산자는 이런 이진 표현에 연산을 수행하지만 표준 JavaScript 숫자 값을 반환한다.

연산자 사용법 설명
비트 AND a & b 두 피연산자의 해당 비트가 모두 1인 각 비트 위치에 1을 반환
비트 OR a | b 두 피연산자의 해당 비트가 모두 0인 각 위치에 0을 반환
비트 XOR a ^ b 해당 비트가 같은 각 위치에 0을, 다른 각 위치에 1을 반환
비트 NOT ~ a 피연산자의 비트를 반전
왼쪽 시프트 a << b a를 이진 표현에서 b 비트만큼 왼쪽으로 이동, 오른쪽에서 0을 채움
부호 전파 오른쪽 시프트 a >> b a를 이진 표현에서 b 비트만큼 오른쪽으로 이동, 밀려난 비트를 버림
0 채움 오른쪽 시프트 a >>> b a를 이진 표현에서 b 비트만큼 오른쪽으로 이동, 밀려난 비트를 버리고 왼쪽에서 0을 채움

비트 논리 연산자 (Bitwise logical operators)

개념적으로 비트 논리 연산자는 다음과 같이 동작한다.

  • 피연산자는 32비트 정수로 변환되고 일련의 비트(0과 1)로 표현된다.
  • 32비트보다 많은 숫자는 가장 중요한 비트들이 버려진다. 예를 들어 32비트보다 많은 다음 정수는 32비트 정수로 변환된다: Before: 1110 0110 1111 1010 0000 0000 0000 0110 0000 0000 0001After: 1010 0000 0000 0000 0110 0000 0000 0001.
  • 첫 피연산자의 각 비트는 둘째 피연산자의 해당 비트와 짝지어진다(첫 비트끼리, 둘째 비트끼리 식으로).
  • 연산자가 각 비트 쌍에 적용되고 결과가 비트 단위로 구성된다.

예를 들어 9의 이진 표현은 1001, 15의 이진 표현은 1111이다. 이 값들에 비트 연산자를 적용하면 결과는 다음과 같다.

표현식 결과 이진 설명
15 & 9 9 1111 & 1001 = 1001
15 | 9 15 1111 | 1001 = 1111
15 ^ 9 6 1111 ^ 1001 = 0110
~15 -16 ~ 0000 0000 … 0000 1111 = 1111 1111 … 1111 0000
~9 -10 ~ 0000 0000 … 0000 1001 = 1111 1111 … 1111 0110

비트 NOT 연산자로 모든 32비트가 반전되고, 가장 중요한(가장 왼쪽) 비트가 1로 설정된 값은 음수(2의 보수 표현)를 나타낸다는 점에 주목하라. ~x-x - 1과 같은 값으로 평가된다.

비트 시프트 연산자 (Bitwise shift operators)

비트 시프트 연산자는 두 피연산자를 받는다: 첫 번째는 시프트될 양, 두 번째는 첫 피연산자가 시프트될 비트 위치 수를 지정한다. 시프트 방향은 사용된 연산자가 제어한다.

시프트 연산자는 피연산자를 32비트 정수로 변환하고 Number 또는 BigInt 타입의 결과를 반환한다. 구체적으로 왼쪽 피연산자의 타입이 BigIntBigInt를, 그렇지 않으면 Number를 반환한다.

연산자 설명
왼쪽 시프트 (<<) 첫 피연산자를 지정된 비트 수만큼 왼쪽으로 이동. 왼쪽으로 밀려난 초과 비트는 버려지고 오른쪽에서 0비트가 채워짐 9<<2는 36. 1001을 2비트 왼쪽으로 이동하면 100100, 즉 36
부호 전파 오른쪽 시프트 (>>) 첫 피연산자를 지정된 비트 수만큼 오른쪽으로 이동. 오른쪽으로 밀려난 초과 비트는 버려지고 왼쪽에서 가장 왼쪽 비트의 복사본이 채워짐 9>>2는 2. 1001을 2비트 오른쪽으로 이동하면 10, 즉 2. 마찬가지로 -9>>2는 부호가 보존되므로 -3
0 채움 오른쪽 시프트 (>>>) 첫 피연산자를 지정된 비트 수만큼 오른쪽으로 이동. 오른쪽으로 밀려난 초과 비트는 버려지고 왼쪽에서 0비트가 채워짐 19>>>2는 4. 10011을 2비트 오른쪽으로 이동하면 100, 즉 4. 음이 아닌 숫자에서는 0 채움 오른쪽 시프트와 부호 전파 오른쪽 시프트가 같은 결과를 냄

논리 연산자 (Logical operators)

논리 연산자는 전형적으로 Boolean(논리) 값과 함께 사용된다. 이때 Boolean 값을 반환한다. 그러나 &&, ||, ?? 연산자는 실제로 지정된 피연산자 중 하나의 값을 반환하므로, 이 연산자들을 비Boolean 값과 함께 사용하면 비Boolean 값을 반환할 수 있다. 따라서 이들은 더 적절히 "값 선택 연산자(value selection operators)"라고 할 수 있다.

연산자 사용법 설명
논리 AND (&&) expr1 && expr2 expr1이 false로 변환될 수 있으면 expr1을, 그렇지 않으면 expr2를 반환. Boolean 값과 함께 사용하면 두 피연산자가 모두 true일 때 true, 그 외에는 false 반환
논리 OR (||) expr1 || expr2 expr1이 true로 변환될 수 있으면 expr1을, 그렇지 않으면 expr2를 반환. Boolean 값과 함께 사용하면 어느 한 피연산자가 true면 true, 둘 다 false면 false 반환
nullish 병합 (??) expr1 ?? expr2 expr1nullundefined도 아니면 expr1을, 그렇지 않으면 expr2를 반환
논리 NOT (!) !expr 단일 피연산자가 true로 변환될 수 있으면 false를, 그렇지 않으면 true를 반환

false로 변환될 수 있는 표현식의 예는 null, 0, 0n, NaN, 빈 문자열(""), undefined로 평가되는 것들이다.

다음 코드는 &&(논리 AND) 연산자의 예를 보여준다.

const a1 = true && true; // t && t는 true
const a2 = true && false; // t && f는 false
const a3 = false && true; // f && t는 false
const a4 = false && 3 === 4; // f && f는 false
const a5 = "Cat" && "Dog"; // t && t는 Dog
const a6 = false && "Cat"; // f && t는 false
const a7 = "Cat" && false; // t && f는 false

다음 코드는 ||(논리 OR) 연산자의 예를 보여준다.

const o1 = true || true; // t || t는 true
const o2 = false || true; // f || t는 true
const o3 = true || false; // t || f는 true
const o4 = false || 3 === 4; // f || f는 false
const o5 = "Cat" || "Dog"; // t || t는 Cat
const o6 = false || "Cat"; // f || t는 Cat
const o7 = "Cat" || false; // t || f는 Cat

다음 코드는 ??(nullish 병합) 연산자의 예를 보여준다.

const n1 = null ?? 1; // 1
const n2 = undefined ?? 2; // 2
const n3 = false ?? 3; // false
const n4 = 0 ?? 4; // 0

??||처럼 동작하지만 첫 번째 표현식이 "nullish"(즉 null 또는 undefined)일 때만 두 번째 표현식을 반환한다는 점에 주목하라. ??null이나 undefined일 수 있는 값에 기본값을 설정할 때, 특히 ''0 같은 값이 유효한 값이고 기본값이 적용되지 않아야 할 때 ||보다 더 나은 대안이다.

다음 코드는 !(논리 NOT) 연산자의 예를 보여준다.

const n1 = !true; // !t는 false
const n2 = !false; // !f는 true
const n3 = !"Cat"; // !t는 false

단락 평가 (Short-circuit evaluation)

논리 표현식은 왼쪽에서 오른쪽으로 평가될 때 다음 규칙으로 가능한 "단락(short-circuit)" 평가를 검사한다.

  • falsy && anything은 falsy 값으로 단락 평가된다.
  • truthy || anything은 truthy 값으로 단락 평가된다.
  • nonNullish ?? anything은 non-nullish 값으로 단락 평가된다.

논리 규칙은 이 평가들이 항상 올바르다는 것을 보장한다. 위 표현식의 anything 부분은 평가되지 않으므로, 그렇게 함으로써 발생하는 어떤 부작용도 일어나지 않는다는 점에 주목하라.

BigInt 연산자 (BigInt operators)

숫자 사이에 사용할 수 있는 대부분의 연산자는 BigInt 값 사이에도 사용할 수 있다.

// BigInt 덧셈
const a = 1n + 2n; // 3n
// BigInt 나눗셈은 0 방향으로 반올림
const b = 1n / 2n; // 0n
// BigInt 비트 연산은 어느 쪽도 자르지 않는다
const c = 40000000000000000n >> 2n; // 10000000000000000n

한 가지 예외는 **부호 없는 오른쪽 시프트(>>>)**로, BigInt 값에 대해 정의되지 않았다. BigInt는 고정 너비가 없어 기술적으로 "가장 높은 비트"가 없기 때문이다.

const d = 8n >>> 2n; // TypeError: BigInts have no unsigned right shift, use >> instead

BigInt와 숫자는 상호 교체할 수 없다 — 계산에서 섞을 수 없다.

const a = 1n + 2; // TypeError: Cannot mix BigInt and other types

BigInt는 숫자의 부분집합도 상위집합도 아니기 때문이다. BigInt는 큰 정수를 나타낼 때 숫자보다 높은 정밀도를 가지지만 소수를 나타낼 수 없으므로, 어느 쪽에서든 암묵적 변환은 정밀도를 잃을 수 있다. 명시적 변환을 사용해 연산이 숫자 연산인지 BigInt 연산인지 신호를 보내라.

const a = Number(1n) + 2; // 3
const b = 1n + BigInt(2); // 3n

BigInt를 숫자와 비교할 수 있다.

const a = 1n > 2; // false
const b = 3 > 2n; // true

문자열 연산자 (String operators)

문자열 값에 사용할 수 있는 비교 연산자 외에, **연결 연산자(+)**는 두 문자열 값을 연결해 두 피연산자 문자열의 결합인 또 다른 문자열을 반환한다. 예:

console.log("my " + "string"); // 콘솔에 문자열 "my string"을 기록

축약 할당 연산자 +=도 문자열을 연결하는 데 사용할 수 있다. 예:

let myString = "alpha";
myString += "bet"; // "alphabet"으로 평가되고 이 값을 myString에 할당

조건(삼항) 연산자 (Conditional (ternary) operator)

조건 연산자는 세 개의 피연산자를 받는 JavaScript의 유일한 연산자다. 조건에 따라 두 값 중 하나를 가질 수 있다. 문법은:

condition ? val1 : val2

condition이 true면 연산자는 val1의 값을 가진다. 그렇지 않으면 val2의 값을 가진다. 표준 연산자를 사용할 수 있는 아무 곳에서나 조건 연산자를 사용할 수 있다. 예:

const status = age >= 18 ? "adult" : "minor";

이 문장은 age가 18 이상이면 변수 status에 "adult"를 할당하고, 그렇지 않으면 "minor"를 할당한다.

쉼표 연산자 (Comma operator)

**쉼표 연산자(,)**는 두 피연산자를 모두 평가하고 마지막 피연산자의 값을 반환한다. 이 연산자는 주로 for 루프 안에서 루프를 통과할 때마다 여러 변수를 갱신할 수 있게 하기 위해 사용된다. 필요하지 않은 다른 곳에 사용하는 것은 나쁜 스타일로 여겨진다. 종종 두 개의 분리된 문장을 대신 사용할 수 있고 사용해야 한다.

예를 들어 a가 한 변에 10개 요소를 가진 2차원 배열이면, 다음 코드는 쉼표 연산자를 사용해 두 변수를 동시에 갱신한다. 이 코드는 배열의 대각선 요소 값을 출력한다.

const x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const a = [x, x, x, x, x];

for (let i = 0, j = 9; i <= j; i++, j--) {
  //     ^
  console.log(`a[${i}][${j}]= ${a[i][j]}`);
}

단항 연산자 (Unary operators)

단항 연산은 하나의 피연산자만 가지는 연산이다.

delete

delete 연산자는 객체의 속성을 삭제한다. 문법은:

delete object.property;
delete object[propertyKey];
delete objectName[index];

여기서 object는 객체의 이름, property는 기존 속성, propertyKey는 기존 속성을 가리키는 문자열 또는 symbol이다.

delete 연산자가 성공하면 객체에서 그 속성을 제거한다. 이후에 접근하려 하면 undefined가 나온다. delete 연산자는 연산이 가능하면 true를, 불가능하면 false를 반환한다.

delete Math.PI; // false 반환 (non-configurable 속성은 삭제 불가)

const myObj = { h: 4 };
delete myObj.h; // true 반환 (사용자 정의 속성은 삭제 가능)

배열 요소 삭제하기 (Deleting array elements)

배열은 단지 객체이므로 기술적으로 요소를 삭제할 수 있다. 그러나 이것은 나쁜 관행으로 여겨지며 피하는 것이 좋다. 배열 속성을 삭제하면 배열 길이는 영향을 받지 않고 다른 요소는 다시 인덱싱되지 않는다. 그런 동작을 원한다면 요소를 undefined 값으로 덮어쓰는 것이 훨씬 낫다. 배열을 실제로 조작하려면 splice 같은 다양한 배열 메서드를 사용하라.

typeof

typeof 연산자는 평가되지 않은 피연산자의 타입을 나타내는 문자열을 반환한다. operand는 타입을 반환할 문자열·변수·키워드·객체다. 괄호는 선택 사항이다.

다음 변수들을 정의했다고 가정하자.

const myFun = () => 5 + 2;
const shape = "round";
const size = 1;
const foo = ["Apple", "Mango", "Orange"];
const today = new Date();

typeof는 이 변수들에 대해 다음 결과를 반환한다.

typeof myFun; // "function" 반환
typeof shape; // "string" 반환
typeof size; // "number" 반환
typeof foo; // "object" 반환
typeof today; // "object" 반환
typeof doesntExist; // "undefined" 반환

키워드 truenull에 대해 typeof는 다음 결과를 반환한다.

typeof true; // "boolean" 반환
typeof null; // "object" 반환

숫자나 문자열에 대해 typeof는 다음 결과를 반환한다.

typeof 62; // "number" 반환
typeof "Hello world"; // "string" 반환

속성 값에 대해 typeof는 속성이 포함한 값의 타입을 반환한다.

typeof document.lastModified; // "string" 반환
typeof window.length; // "number" 반환
typeof Math.LN2; // "number" 반환

메서드와 함수에 대해 typeof는 다음 결과를 반환한다.

typeof blur; // "function" 반환
typeof parseInt; // "function" 반환
typeof shape.split; // "function" 반환

미리 정의된 객체에 대해 typeof는 다음 결과를 반환한다.

typeof Date; // "function" 반환
typeof Function; // "function" 반환
typeof Math; // "object" 반환
typeof Option; // "function" 반환
typeof String; // "function" 반환

void

void 연산자는 값을 반환하지 않고 평가될 표현식을 지정한다. expression은 평가할 JavaScript 표현식이다. 표현식을 감싸는 괄호는 선택 사항이지만 우선순위 문제를 피하기 위해 사용하는 것이 좋은 스타일이다.

관계 연산자 (Relational operators)

관계 연산자는 피연산자를 비교하고 비교가 참인지에 따라 Boolean 값을 반환한다.

in

in 연산자는 지정된 속성이 지정된 객체 안에 있으면 true를 반환한다. 문법은:

propNameOrNumber in objectName

여기서 propNameOrNumber는 속성 이름이나 배열 인덱스를 나타내는 문자열·숫자·symbol 표현식이고, objectName은 객체의 이름이다.

다음 예는 in 연산자의 몇 가지 사용을 보여준다.

// 배열
const trees = ["redwood", "bay", "cedar", "oak", "maple"];
0 in trees; // true 반환
3 in trees; // true 반환
6 in trees; // false 반환
"bay" in trees; // false 반환
// (인덱스 번호를 지정해야 하며 그 인덱스의 값을 지정하면 안 된다)
"length" in trees; // true 반환 (length는 Array 속성)

// 내장 객체
"PI" in Math; // true 반환
const myString = new String("coral");
"length" in myString; // true 반환

// 사용자 정의 객체
const myCar = { make: "Honda", model: "Accord", year: 1998 };
"make" in myCar; // true 반환
"model" in myCar; // true 반환

instanceof

instanceof 연산자는 지정된 객체가 지정된 객체 타입이면 true를 반환한다. 문법은:

object instanceof objectType

여기서 objectobjectType에 대해 테스트할 객체이고, objectTypeMap이나 Array 같은 타입을 나타내는 생성자다.

실행 시점에 객체의 타입을 확인해야 할 때 instanceof를 사용하라. 예를 들어 예외를 잡을 때 던져진 예외의 타입에 따라 다른 예외 처리 코드로 분기할 수 있다.

예를 들어 다음 코드는 objMap 객체인지 알아내기 위해 instanceof를 사용한다. objMap 객체이므로 if 블록 안의 문장이 실행된다.

const obj = new Map();
if (obj instanceof Map) {
  // 실행할 문장
}

기본 표현식 (Basic expressions)

모든 연산자는 결국 하나 이상의 기본 표현식에 작동한다. 기본 표현식에는 식별자와 리터럴이 포함되고, 몇 가지 다른 종류도 있다. 아래에 간략히 소개하며, 그 의미는 각각의 해당 참조 절에서 자세히 설명된다.

this

this 키워드는 보통 함수 안에서 사용된다. 일반적으로 함수가 객체에 메서드로 붙어 있으면 this는 메서드가 호출된 객체를 가리킨다. 함수에 전달되는 숨은 매개변수처럼 동작한다. this는 객체로 평가되는 표현식이므로 앞서 소개한 모든 객체 연산을 사용할 수 있다.

this["propertyName"];
this.propertyName;
doSomething(this);

예를 들어 함수가 다음과 같이 정의되었다고 하자.

function getFullName() {
  return `${this.firstName} ${this.lastName}`;
}

이제 이 함수를 객체에 붙이면 호출될 때 그 객체의 속성을 사용한다.

const person1 = {
  firstName: "Chris",
  lastName: "Martin",
};

const person2 = {
  firstName: "Chester",
  lastName: "Bennington",
};

// 같은 함수를 붙인다
person1.getFullName = getFullName;
person2.getFullName = getFullName;

console.log(person1.getFullName()); // "Chris Martin"
console.log(person2.getFullName()); // "Chester Bennington"

그룹화 연산자 (Grouping operator)

그룹화 연산자(())는 표현식에서 평가의 우선순위를 제어한다. 예를 들어 곱셈·나눗셈을 먼저 하는 기본 순서를 재정의하고 덧셈·뺄셈을 먼저 평가하게 할 수 있다.

const a = 1;
const b = 2;
const c = 3;

// 기본 우선순위
a + b * c; // 7
// 기본적으로 이렇게 평가됨
a + (b * c); // 7

// 이제 우선순위 재정의
// 덧셈을 곱셈보다 먼저
(a + b) * c; // 9

// 이와 동등
a * c + b * c; // 9

속성 접근자 (Property accessor)

속성 접근자 문법은 점 표기법 또는 대괄호 표기법을 사용해 객체의 속성 값을 얻는다.

object.property;
object["property"];

working with objects 가이드가 객체 속성에 대해 더 자세히 다룬다.

옵셔널 체이닝 (Optional chaining)

옵셔널 체이닝 문법(?.)은 정의되어 있고 null이 아닌 경우 객체에 대해 체이닝 연산을 수행하고, 그렇지 않으면 연산을 단락시키고 undefined를 반환한다. 이를 통해 null 또는 undefined일 수 있는 값에 대해 TypeError 없이 연산할 수 있다.

maybeObject?.property;
maybeObject?.[property];
maybeFunction?.();

new

new 연산자를 사용해 사용자 정의 객체 타입 또는 내장 객체 타입 중 하나의 인스턴스를 만들 수 있다. 다음과 같이 사용한다.

const objectName = new ObjectType(param1, param2, /* …, */ paramN);

super

super 키워드는 객체의 부모에 있는 함수를 호출하는 데 사용된다. 예를 들어 클래스에서 부모 생성자를 호출하는 데 유용하다.

super(args); // 부모 생성자 호출
super.functionOnParent(args);

더 알아보기