덧셈 +
, 곱셈 *
, 뺄셈 -
과 같은 연산은 학교에서 배워서 이미 알고 계실 겁니다.
이번 챕터에서는 이런 기본 연산자를 시작으로 학교에선 다루지 않았던 자바스크립트에서만 제공하는 연산자에 대해 배워보겠습니다.
용어: ‘단항’, ‘이항’, ‘피연산자’
연산자에 대해 학습하기 전에, 앞으로 자주 등장하게 될 용어 몇 가지를 정리해 보겠습니다.
-
피연산자(operand) 는 연산자가 연산을 수행하는 대상입니다.
5 * 2
에는 왼쪽 피연산자5
와 오른쪽 피연산자2
, 총 두 개의 피연산자가 있습니다. '피연산자’는 '인수(argument)'라는 용어로 불리기도 합니다. -
피연산자를 하나만 받는 연산자는 단항(unary) 연산자 라고 부릅니다. 피연산자의 부호를 뒤집는 단항 마이너스 연산자
-
는 단항 연산자의 대표적인 예입니다.let x = 1; x = -x; alert( x ); // -1, 단항 마이너스 연산자는 부호를 뒤집습니다.
-
두 개의 피연산자를 받는 연산자는 이항(binary) 연산자 라고 부릅니다. 마이너스 연산자는 아래와 같이 이항 연산자로 쓸 수도 있습니다.
let x = 1, y = 3; alert( y - x ); // 2, 이항 마이너스 연산자는 뺄셈을 해줍니다.
위와 같이 부호를 반전해주는 단항 마이너스 연산자와 뺄셈에 쓰이는 이항 마이너스 연산자(뺄셈 연산자)는 기호는 같지만 수행하는 연산이 다릅니다. 두 연산을 구분하는 기준은 피연산자의 개수입니다.
수학
자바스크립트에서 지원하는 수학 연산자는 다음과 같습니다.
- 덧셈 연산자
+
, - 뺄셈 연산자
-
, - 곱셈 연산자
*
, - 나눗셈 연산자
/
, - 나머지 연산자
%
, - 거듭제곱 연산자
**
앞쪽 네 연산자는 설명이 필요 없겠지만, %
와 **
는 약간의 설명이 필요할 것 같네요.
나머지 연산자 %
나머지 연산자(remainder operator)는 %
기호로 나타내지만, 비율을 나타내는 퍼센트와 관련이 없습니다.
나머지 연산자를 사용한 표현식 a % b
는 a
를 b
로 나눈 후 그 나머지(remainder)를 정수로 반환해줍니다.
예시:
alert( 5 % 2 ); // 5를 2로 나눈 후의 나머지인 1을 출력
alert( 8 % 3 ); // 8을 3으로 나눈 후의 나머지인 2를 출력
거듭제곱 연산자 **
거듭제곱 연산자(exponentiation operator)를 사용한 a ** b
를 평가하면 a
를 b
번 곱한 값이 반환됩니다.
예시:
alert( 2 ** 2 ); // 4 (2 * 2)
alert( 2 ** 3 ); // 8 (2 * 2 * 2)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2)
거듭제곱 연산자는 정수가 아닌 숫자에 대해서도 동작합니다. 1/2
을 사용하면 제곱근을 구할 수 있죠.
alert( 4 ** (1/2) ); // 2 (1/2 거듭제곱은 제곱근)
alert( 8 ** (1/3) ); // 2 (1/3 거듭제곱은 세제곱근)
이항 연산자 '+'와 문자열 연결
이제 학교에서 배운 기본 산술 연산자를 넘어, 자바스크립트가 제공하는 특별한 연산자 기능에 대해 살펴봅시다.
덧셈 연산자 +
는 대개 숫자를 더한 결과를 반환합니다.
그런데 이항 연산자 +
의 피연산자로 문자열이 전달되면 덧셈 연산자는 덧셈이 아닌 문자열을 병합(연결)합니다.
let s = "my" + "string";
alert(s); // mystring
따라서 이항 연산자 +
를 사용할 때는 피연산자 중 하나가 문자열이면 다른 하나도 문자열로 변환된다는 점에 주의해야 합니다.
예시:
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
첫 번째 피연산자가 문자열인지, 두 번째 피연산자가 문자열인지는 중요하지 않습니다. 피연산자 중 어느 하나가 문자열이면 다른 하나도 문자열로 변환됩니다.
좀 더 복잡한 예시를 살펴봅시다.
alert(2 + 2 + '1' ); // '221'이 아니라 '41'이 출력됩니다.
연산은 왼쪽에서 오른쪽으로 순차적으로 진행되기 때문에 이런 결과가 나왔습니다. 두 개의 숫자 뒤에 문자열이 오는 경우, 숫자가 먼저 더해지고, 그 후 더해진 숫자와 문자열과의 병합이 일어납니다.
이처럼 이항 덧셈 연산자 +
는 문자열 연결과 변환이라는 특별한 기능을 제공합니다. 다른 산술 연산자가 오직 숫자형의 피연산자만 다루고, 피연산자가 숫자형이 아닌 경우에 그 형을 숫자형으로 바꾸는 것과는 대조적입니다.
아래는 뺄셈 -
과 나눗셈 /
연산자가 어떻게 문자형 피연산자를 다루는지를 보여줍니다.
alert( 6 - '2' ); // 4, '2'를 숫자로 바꾼 후 연산이 진행됩니다.
alert( '6' / '2' ); // 3, 두 피연산자가 숫자로 바뀐 후 연산이 진행됩니다.
단항 연산자 +와 숫자형으로의 변환
덧셈 연산자 +
는 이항 연산자뿐만 아니라 단항 연산자로도 사용할 수 있습니다.
숫자에 단항 덧셈 연산자를 붙이면 이 연산자는 아무런 동작도 하지 않습니다. 그러나 피연산자가 숫자가 아닌 경우엔 숫자형으로의 변환이 일어납니다.
예시:
// 숫자에는 아무런 영향을 미치지 않습니다.
let x = 1;
alert( +x ); // 1
let y = -2;
alert( +y ); // -2
// 숫자형이 아닌 피연산자는 숫자형으로 변화합니다.
alert( +true ); // 1
alert( +"" ); // 0
단항 덧셈 연산자는 짧은 문법으로도 Number(...)
와 동일한 일을 할 수 있게 해줍니다.
개발을 하다 보면 문자열을 숫자로 변환해야 하는 경우가 자주 생깁니다. HTML 폼(form) 필드에서 값을 가져왔는데 그 값이 문자형일 때 같이 말이죠. 실제로 폼에서 가지고 온 값은 대개 문자열 형태입니다.
이항 덧셈 연산자를 사용하면 아래와 같이 값이 문자열로 변해서 연결될 겁니다.
let apples = "2";
let oranges = "3";
alert( apples + oranges ); // 23, 이항 덧셈 연산자는 문자열을 연결합니다.
원하는 대로 값을 더해주려면, 단항 덧셈 연산자를 사용해 피연산자를 숫자형으로 변화시키면 됩니다.
let apples = "2";
let oranges = "3";
// 이항 덧셈 연산자가 적용되기 전에, 두 피연산자는 숫자형으로 변화합니다.
alert( +apples + +oranges ); // 5
// `Number(...)`를 사용해서 같은 동작을 하는 코드를 작성할 수 있지만, 더 기네요.
// alert( Number(apples) + Number(oranges) ); // 5
위 식을 수학자가 본다면 불필요한 덧셈 기호에 대해 언급하며 식이 이상하다고 지적할 겁니다. 프로그래머라면 아니겠지만 말이죠. 위 식은 우리가 의도한 대로 단항 덧셈 연산자가 먼저 문자열을 숫자로 변환시키고, 이항 덧셈 연산자가 그 결과들을 더해주고 있습니다.
그런데 왜 이항 덧셈 연산자가 적용되기 전에 단항 덧셈 연산자가 먼저 적용될까요? 그 이유는 이제 학습하게 될 연산자 우선순위 때문입니다.
연산자 우선순위
하나의 표현식에 둘 이상의 연산자가 있는 경우, 실행 순서는 연산자의 우선순위(precedence) 에 의해 결정됩니다.
1 + 2 * 2
라는 식이 있을 때 곱셈이 먼저, 그 후에 덧셈이 일어난다는 것을 알고 계실 겁니다. 이런 개념이 연산자 우선순위입니다. 여기서 곱셈은 덧셈보다 더 높은 우선순위를 가지죠.
자바스크립트에서 정의한 연산자 우선순위가 마음에 들지 않는다면, 괄호를 사용하면 됩니다. 괄호는 모든 연산자보다 우선순위가 높기 때문에 자바스크립트에서 정의한 연산자 우선순위를 무력화시킵니다. 표현식 (1 + 2) * 2
에서 괄호로 둘러싼 덧셈 연산자가 먼저 수행되는 것 같이 말이죠.
자바스크립트는 다양한 연산자를 제공하는데, 이 모든 연산자엔 우선순위가 매겨져 있습니다. 우선순위 숫자가 클수록 먼저 실행됩니다. 순위가 같으면 왼쪽부터 시작해서 오른쪽으로 연산이 수행됩니다.
아래는 우선순위 테이블(precedence table)의 일부를 발췌한 표입니다. 순서를 기억할 필요는 없지만, 동일한 기호의 단항 연산자는 이항 연산자보다 우선순위가 더 높다는 것에 주목해 주시기 바랍니다.
순위 | 연산자 이름 | 기호 |
---|---|---|
… | … | … |
17 | 단항 덧셈 | + |
17 | 단항 부정 | - |
16 | 지수 | ** |
15 | 곱셈 | * |
15 | 나눗셈 | / |
13 | 덧셈 | + |
13 | 뺄셈 | - |
… | … | … |
3 | 할당 | = |
… | … | … |
'단항 덧셈 연산자’는 우선순위 17
로, '(이항) 덧셈 연산자’의 우선순위 13
보다 높습니다. 표현식 "+apples + +oranges"
에서 단항 덧셈 연산자가 덧셈보다 먼저 수행되는 이유가 바로 이 때문입니다.
할당 연산자
무언가를 할당할 때 쓰이는 =
도 연산자입니다. 이 연산자는 할당(assignment) 연산자라고 불리는데, 우선순위는 3
으로 아주 낮습니다.
x = 2 * 2 + 1
과 같은 표현식에서 계산이 먼저 이뤄지고, 그 결과가 x
에 할당되는 이유가 바로 이 때문입니다.
let x = 2 * 2 + 1;
alert( x ); // 5
값을 반환하는 할당 연산자
=
는 연산자이기 때문에 흥미로운 함축성을 내포하고 있습니다.
자바스크립트에서 대부분의 연산자들은 값을 반환합니다. +
와 -
뿐만 아니라 =
역시 값을 반환하죠.
x = value
을 호출하면 value
가 x
에 쓰여지고, 이에 더하여 value
가 반환됩니다.
할당 연산자의 이런 특징을 이용한 복잡한 표현식을 살펴봅시다.
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
위 예제에서 표현식 (a = b + 1)
은 a
에 값을 할당하고, 그 값인 3
을 반환합니다. 그리고 반환 값은 이어지는 표현식에 사용됩니다.
괴상한 코드라고 느껴지겠지만, 여러 자바스크립트 라이브러리에서 이런 식으로 할당 연산자를 사용하고 있기 때문에 동작 원리를 이해할 수 있어야 합니다.
다만, 직접 코드를 작성할 땐 이런 방식을 사용하지 않기를 바랍니다. 이런 트릭을 사용하면 코드가 명확하지 않을 뿐만 아니라 가독성도 떨어지기 때문입니다.
할당 연산자 체이닝
할당 연산자는 아래와 같이 여러 개를 연결할 수도 있습니다(체이닝).
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
이렇게 할당 연산자를 여러 개 연결한 경우, 평가는 우측부터 진행됩니다. 먼저 가장 우측의 2 + 2
가 평가되고, 그 결과가 좌측의 c
, b
, a
에 순차적으로 할당됩니다. 모든 변수가 단일 값을 공유하게 되죠.
그런데 되도록이면 연산자를 체이닝 하는것 보다 가독성을 위해 아래와 같이 줄을 나눠 코드를 작성하길 권유드립니다.
c = 2 + 2;
b = c;
a = c;
이렇게 작성하면 읽기도 쉽고, 눈을 빠르게 움직이며 코드를 읽을 수 있습니다.
복합 할당 연산자
프로그램을 짜다 보면, 변수에 연산자를 적용하고 그 결과를 같은 변수에 저장해야 하는 경우가 종종 생깁니다.
아래와 같이 말이죠.
let n = 2;
n = n + 5;
n = n * 2;
이때, +=
와 *=
연산자를 사용하면 짧은 문법으로 동일한 연산을 수행할 수 있습니다.
let n = 2;
n += 5; // n은 7이 됩니다(n = n + 5와 동일).
n *= 2; // n은 14가 됩니다(n = n * 2와 동일).
alert( n ); // 14
이런 ‘복합 할당’ 연산자는 산술 연산자와 비트 연산자에도 적용할 수 있습니다. /=
, -=
등의 연산자를 만들 수 있죠.
복합 할당 연산자의 우선순위는 할당 연산자와 동일합니다. 따라서 대부분 다른 연산자가 실행된 후에 복합 할당 연산자가 실행됩니다.
let n = 2;
n *= 3 + 5;
alert( n ); // 16 (*=의 우측이 먼저 평가되므로, 위 식은 n *= 8과 동일합니다.)
증가·감소 연산자
숫자를 하나 늘리거나 줄이는 것은 자주 사용되는 연산입니다.
자바스크립트에서는 이런 연산을 해주는 연산자를 제공합니다.
-
증가(increment) 연산자
++
는 변수를 1 증가시킵니다.let counter = 2; counter++; // counter = counter + 1과 동일하게 동작합니다. 하지만 식은 더 짧습니다. alert( counter ); // 3
-
감소(decrement) 연산자
--
는 변수를 1 감소시킵니다.let counter = 2; counter--; // counter = counter - 1과 동일하게 동작합니다. 하지만 식은 더 짧습니다. alert( counter ); // 1
증가·감소 연산자는 변수에만 쓸 수 있습니다. 5++
와 같이 값에 사용하려고 하면 에러가 발생합니다.
++
와--
연산자는 변수 앞이나 뒤에 올 수 있습니다.
counter++
와 같이 피연산자 뒤에 올 때는, '후위형(postfix form)'이라고 부릅니다.++counter
와 같이 피연산자 앞에 올 때는, '전위형(prefix form)'이라고 부릅니다.
후위형과 전위형은 피연산자인 counter
를 1
만큼 증가시켜 준다는 점에서 동일한 일을 합니다.
두 형의 차이는 ++/--
의 반환 값을 사용할 때 드러납니다.
자, 다시 상기해 보도록 합시다. 이미 배운 바와 같이 모든 연산자는 값을 반환합니다. 증가·감소 연산자도 마찬가지입니다. 전위형은 증가·감소 후의 새로운 값을 반환하는 반면, 후위형은 증가·감소 전의 기존 값을 반환합니다.
아래 예시를 통해 차이점을 직접 살펴보도록 합시다.
let counter = 1;
let a = ++counter; // (*)
alert(a); // 2
(*)
로 표시한 줄의 전위형 ++counter
는 counter
를 증가시키고 새로운 값 2
를 반환합니다. 따라서 alert
는 2
를 표시합니다.
이제 후위형을 살펴봅시다.
let counter = 1;
let a = counter++; // (*) ++counter를 counter++로 바꿈
alert(a); // 1
(*)
로 표시한 줄의 후위형 counter++
는 counter
를 증가시키긴 하지만, 증가 전의 기존 값을 반환합니다. 따라서 alert
는 1
을 표시합니다.
증가, 감소 연산자에 대한 내용을 정리하면 아래와 같습니다.
-
반환 값을 사용하지 않는 경우라면, 전위형과 후위형엔 차이가 없습니다.
let counter = 0; counter++; ++counter; alert( counter ); // 2, 위 두 라인은 동일한 연산을 수행합니다.
-
값을 증가시키고 난 후, 증가한 값을 바로 사용하려면 전위형 증가 연산자를 사용하면 됩니다.
let counter = 0; alert( ++counter ); // 1
-
값을 증가시키지만, 증가 전의 기존값을 사용하려면 후위형 증가 연산자를 사용하면 됩니다.
let counter = 0; alert( counter++ ); // 0
++/--
연산자를 표현식 중간에 사용하는 것도 가능합니다. 이때, 증가·감소 연산자의 우선순위는 다른 대부분의 산술 연산자보다 높기 때문에, 평가가 먼저 이뤄집니다.
예시:
let counter = 1;
alert( 2 * ++counter ); // 4
위 예시를 아래와 비교해 봅시다.
let counter = 1;
alert( 2 * counter++ ); // counter++는 '기존'값을 반환하기 때문에 2가 출력됩니다.
이렇게 코드를 작성하는 게 기술적으로 문제가 있는 것은 아니지만, 한 줄에서 여러 가지 일을 동시에 하고 있기 때문에 코드의 가독성이 떨어집니다.
코드를 읽을 때 눈을 ‘수직으로’ 빠르게 움직이다 보면 counter++
와 같은 것을 놓치기 쉽습니다. 변수가 증가했다는 것을 놓칠 수 있죠.
'코드 한 줄엔, 특정 동작 하나’에 관련된 내용만 작성하는 게 좋습니다. 아래와 같이 말이죠.
let counter = 1;
alert( 2 * counter );
counter++;
비트 연산자
비트 연산자(bitwise operator)는 인수를 32비트 정수로 변환하여 이진 연산을 수행합니다.
이런 비트 조작 관련 연산자는 자바스크립트뿐만 아니라 대부분의 프로그래밍 언어에서 지원합니다.
아래는 비트 연산 시 쓰이는 연산자 목록입니다.
- 비트 AND (
&
) - 비트 OR (
|
) - 비트 XOR (
^
) - 비트 NOT (
~
) - 왼쪽 시프트(LEFT SHIFT) (
<<
) - 오른쪽 시프트(RIGHT SHIFT) (
>>
) - 부호 없는 오른쪽 시프트(ZERO-FILL RIGHT SHIFT) (
>>>
)
비트 연산자는 저수준(2진 표현)에서 숫자를 다뤄야 할 때 쓰이므로 흔하게 쓰이진 않습니다. 웹 개발 시엔 이런 일이 자주 일어나지 않기 때문에 비트 연산자를 만날 일은 거의 없죠. 그렇긴 해도 암호를 다뤄야 할 땐 비트 연산자가 유용하기 때문에 때가 되면 MDN의 비트 연산자 문서를 보시는 걸 추천합니다.
쉼표 연산자
쉼표 연산자(comma operator) ,
는 좀처럼 보기 힘들고, 특이한 연산자 중 하나입니다. 코드를 짧게 쓰려는 의도로 가끔 사용됩니다. 이런 코드를 만났을 때, 어떤 연산 결과가 도출되는지 알아야 하므로 쉼표 연산자에 대해 알아보도록 합시다.
쉼표 연산자 ,
는 여러 표현식을 코드 한 줄에서 평가할 수 있게 해줍니다. 이때 표현식 각각이 모두 평가되지만, 마지막 표현식의 평가 결과만 반환되는 점에 유의해야 합니다.
예시:
let a = (1 + 2, 3 + 4);
alert( a ); // 7 (3 + 4의 결과)
위 예시에서 첫 번째 표현식 1 + 2
은 평가가 되지만 그 결과는 버려집니다. 3 + 4
만 평가되어 a
에 할당되죠.
쉼표 연산자의 연산자 우선순위는 매우 낮습니다. 할당 연산자 =
보다 더 낮죠. 따라서 위 예시에선 괄호가 중요한 역할을 합니다.
괄호가 없으면 a = 1 + 2, 3 + 4
에서 +
가 먼저 수행되어 a = 3, 7
이 됩니다. 할당 연산자 =
는 쉼표 연산자보다 우선순위가 높기 때문에 a = 3
이 먼저 실행되고, 나머지(7
)는 무시되죠. (a = 1 + 2), 3 + 4
를 연산한 것처럼 될 겁니다.
이렇게 마지막 표현식을 제외한 모든 것을 버리는 연산자는 어디서 사용되는 걸까요?
여러 동작을 하나의 줄에서 처리하려는 복잡한 구조에서 이를 사용합니다.
아래와 같이 말이죠.
// 한 줄에서 세 개의 연산이 수행됨
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
}
쉼표 연산자를 사용한 트릭은 여러 자바스크립트 프레임워크에서 볼 수 있습니다. 이 연산자의 사용 빈도가 높지 않지만, 언급하고 넘어가는 이유이죠. 쉼표 연산자는 코드 가독성에 도움이 되지 않습니다. 따라서 곰곰이 생각해 본 후, 진짜 필요한 경우에만 사용하시길 바랍니다.