덧셈 +, 곱셈 *, 뺄셈 -과 같은 연산자는 학교에서 배워서 이미 알고 계실 겁니다.

이 챕터에서는 이런 기본 산술 연산자 이외의 연산자에 대해 다루도록 하겠습니다.

용어: “단항”, “이항”, “피연산자”

연산자에 대해 학습하기 전에, 공통으로 사용할 용어 몇 가지를 정리해 보도록 합시다.

  • 피연산자(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, 이항 마이너스 연산자는 뺄셈을 해줍니다.

    위와 같이 부호를 반전해주는 단항 마이너스 연산자와 뺄셈에 쓰이는 이항 마이너스 연산자(뺄셈 연산자)는 부호가 같지만 수행하는 연산이 다릅니다. 두 연산을 구분하는 기준은 피연산자의 개수입니다.

이항 연산자 +와 문자열 연결

이제 학교에서 배운 기초 산술 연산자를 넘어, 자바스크립트가 제공하는 특별한 연산자 기능에 대해 살펴봅시다.

덧셈 연산자 +는 대개 숫자를 더한 결과를 반환합니다.

그러나, 이항 연산자 +의 피연산자로 문자열이 전달되면, 덧셈이 아닌 문자열 병합(연결)이 일어납니다.

let s = "my" + "string";
alert(s); // mystring

이항 연산자 +를 사용할 땐, 피연산자 중 하나가 문자열이면 다른 하나도 문자열로 변환된다는 점을 꼭 기억하시기 바랍니다.

예시:

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"

첫 번째 피연산자가 문자열인지, 두 번째 피연산자가 문자열인지는 중요하지 않습니다. 피연산자 중 어느 하나가 문자열이면 다른 하나도 문자열로 변환됩니다.

연산은 왼쪽에서 오른쪽으로 진행된다는 점에 주의하시기 바랍니다. 두 개의 숫자 뒤에 문자열이 오는 경우, 숫자가 먼저 더해지고, 그 후 더해진 숫자와 문자열과의 병합이 이뤄집니다.

alert(2 + 2 + '1' ); // "221"이 아니라 "41"이 출력됩니다.

이처럼 이항 덧셈 연산자 +는 문자열 연결과 변환이라는 특별한 기능을 제공합니다. 다른 산술 연산자가 오직 숫자형의 피연산자만 다루고, 피연산자가 숫자형이 아닌 경우에 그 형을 숫자형으로 바꾸는 것과는 대조적이죠.

아래는 뺄셈 -과 나눗셈 / 연산자가 어떻게 문자형 피연산자를 다루는지를 보여줍니다.

alert( 2 - '1' ); // 1
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 단항 부정 -
15 곱셈 *
15 나눗셈 /
13 덧셈 +
13 뺄셈 -
3 할당 =

"단항 덧셈 연산자"는 우선순위 17으로, "(이항) 덧셈 연산자"의 우선순위 13보다 높습니다. 표현식 "+apples + +oranges"에서 단항 덧셈 연산자가 덧셈보다 먼저 수행되는 이유가 바로 이 때문입니다.

할당 연산자

무언가를 할당할 때 쓰이는 =도 연산자입니다. 이 연산자는 할당(assignment) 연산자라고 불리는데, 우선순위는 3으로 아주 낮습니다.

x = 2 * 2 + 1과 같은 표현식에서 계산이 먼저 이뤄지고, 그 결과가 x에 할당되는 이유가 바로 이 때문입니다.

let x = 2 * 2 + 1;

alert( x ); // 5

할당 연산자는 아래와 같이 여러 개를 연결할 수도 있습니다(체이닝).

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

위처럼 할당 연산자를 여러 개 연결한 경우, 평가는 우측부터 진행됩니다. 먼저 가장 우측의 2 + 2가 평가되고, 그 결과가 좌측의 c, b, a에 순차적으로 할당됩니다. 모든 변수가 단일 값을 공유하게 되죠.

할당 연산자 "="는 값을 반환합니다.

연산자는 항상 값을 반환합니다. 덧셈 + 또는 곱셈 * 연산자에선 이런 특징이 명확하게 드러납니다. 할당 연산자도 당연히 이 규칙을 따르죠.

x = value를 호출하면 valuex에 할당되고, value를 반환합니다.

다음 예시는 할당 연산자의 이런 특징을 이용한 표현식입니다.

let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

alert( a ); // 3
alert( c ); // 0

위 예제에서 표현식 (a = b + 1)a에 값을 할당하고, 그 값인 3을 반환합니다. 반환 값은 이어지는 표현식에 사용됩니다.

괴상한 코드라고 느껴지겠지만, 여러 자바스크립트 라이브러리에서 이런 식으로 할당 연산자를 사용하고 있기 때문에 동작 원리를 이해할 수 있어야 합니다. 다만, 본인이 직접 코드를 작성할 땐 이런 방식을 사용하지 않기를 바랍니다. 이런 트릭을 사용하면 코드가 명확하지 않을 뿐만 아니라 가독성도 떨어지기 때문입니다.

나머지 연산자 %

나머지 연산자는 % 기호로 나타내지만, 비율을 나타내는 퍼센트와 관련이 없습니다.

나머지 연산자를 사용한 표현식 a % bab로 나눈 후 그 나머지를 정수로 반환해줍니다.

예시:

alert( 5 % 2 ); // 5를 2로 나눈 후의 나머지인 1을 출력
alert( 8 % 3 ); // 8을 3으로 나눈 후의 나머지인 2를 출력
alert( 6 % 3 ); // 6을 3으로 나눈 후의 나머지인 0을 출력

거듭제곱 연산자 **

거듭제곱 연산자 **는 표준으로 채택된 지 얼마 안 된 연산자입니다.

자연수 b가 있을 때, a ** bab번 곱한 값이 됩니다.

예시:

alert( 2 ** 2 ); // 4  (2 * 2)
alert( 2 ** 3 ); // 8  (2 * 2 * 2)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2)

거듭제곱 연산자는 정수가 아닌 숫자에 대해서도 동작합니다.

예시:

alert( 4 ** (1/2) ); // 2 (1/2 거듭제곱은 제곱근)
alert( 8 ** (1/3) ); // 2 (1/3 거듭제곱은 세제곱근)

증가/감소 연산자

숫자를 하나 늘리거나 줄이는 것은 자주 사용되는 연산입니다.

자바스크립트에서는 이런 연산을 해주는 연산자를 제공합니다.

  • 증가(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)"이라고 부릅니다.

후위형과 전위형은 피연산자인 counter1만큼 증가시켜 준다는 점에서 동일한 일을 합니다.

두 형의 차이는 ++/--의 반환 값을 사용할 때 드러납니다.

자, 다시 상기해 보도록 합시다. 이미 배운 바와 같이 모든 연산자는 값을 반환합니다. 증가/감소 연산자도 마찬가지입니다. 전위형은 증가/감소 후의 새로운 값을 반환하는 반면, 후위형은 증가/반환 전의 기존 값을 반환합니다.

아래 예시를 통해 차이점을 직접 살펴보도록 합시다.

let counter = 1;
let a = ++counter; // (*)

alert(a); // 2

(*)로 표시한 줄의 전위형 ++countercounter를 증가시키고 새로운 값 2를 반환합니다. 따라서 alert2를 표시합니다.

이제 후위형을 살펴봅시다.

let counter = 1;
let a = counter++; // (*) ++counter를 counter++로 바꿈

alert(a); // 1

(*)로 표시한 줄의 후위형 counter++counter를 증가시키긴 하지만, 증가 전의 기존 값을 반환합니다. 따라서 alert1을 표시합니다.

증가, 감소 연산자에 대한 내용을 정리하면 아래와 같습니다.

  • 반환 값을 사용하지 않는 경우라면, 전위형과 후위형엔 차이가 없습니다.

    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의 비트 연산자 문서를 추천합니다. 진짜 이 연산자가 필요할 때 문서를 보는 게 더 실용적이지만 말이죠.

복합 할당 연산자

프로그램을 짜다 보면, 변수에 연산자를 적용하고 그 결과를 같은 변수에 저장해야 하는 경우가 종종 생깁니다.

아래와 같이 말이죠.

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과 동일합니다.)

쉼표 연산자

쉼표(comma) 연산자 ,는 좀처럼 보기 힘들고, 특이한 연산자 중 하나입니다. 코드를 짧게 쓰려는 의도로 가끔 사용됩니다. 이런 코드를 만났을 때, 어떤 연산 결과가 도출되는지 알아야 하므로 쉼표 연산자에 대해 알아보도록 합시다.

쉼표 연산자 ,는 여러 표현식을 코드 한 줄에서 평가할 수 있게 해줍니다. 이때 표현식 각각이 모두 평가되지만, 마지막 표현식의 평가 결과만 반환되는 점에 유의해야 합니다.

예시:

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++) {
 ...
}

쉼표 연산자를 사용한 트릭은 여러 자바스크립트 프레임워크에서 볼 수 있습니다. 이 연산자의 사용 빈도가 높지 않지만, 언급하고 넘어가는 이유이죠. 쉼표 연산자는 코드 가독성에 도움이 되지 않습니다. 따라서 곰곰이 생각해 본 후, 진짜 필요한 경우에만 사용하시길 바랍니다.

과제

중요도: 5

아래 코드가 실행된 후, 변수 a, b, c, d엔 각각 어떤 값들이 저장될까요?

let a = 1, b = 1;

let c = ++a; // ?
let d = b++; // ?

답은 다음과 같습니다.

  • a = 2
  • b = 2
  • c = 2
  • d = 1
let a = 1, b = 1;

alert( ++a ); // 2, 전위형은 증가 후의 값을 반환합니다.
alert( b++ ); // 1, 후위형은 증가 전의 값을 반환합니다.

alert( a ); // 2, 값이 1만큼 증가합니다.
alert( b ); // 2, 값이 1만큼 증가합니다.
중요도: 3

아래 코드가 실행되고 난 후, ax엔 각각 어떤 값이 저장될까요?

let a = 2;

let x = 1 + (a *= 2);

답은 다음과 같습니다.

  • a = 4 (기존 값(2)에 2를 곱한 4)
  • x = 5 (1 + 4의 결과)
중요도: 5

아래 표현식들의 결과를 예측해 보세요.

"" + 1 + 0
"" - 1 + 0
true + false
6 / "3"
"2" * "3"
4 + 5 + "px"
"$" + 4 + 5
"4" - 2
"4px" - 2
7 / 0
"  -9  " + 5
"  -9  " - 5
null + 1
undefined + 1
" \t \n" - 2

예측한 결과를 적어본 후, 해답과 비교해 보시기 바랍니다.

"" + 1 + 0 = "10" // (1)
"" - 1 + 0 = -1 // (2)
true + false = 1
6 / "3" = 2
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
7 / 0 = Infinity
" -9  " + 5 = " -9  5" // (3)
" -9  " - 5 = -14 // (4)
null + 1 = 1 // (5)
undefined + 1 = NaN // (6)
" \t \n" - 2 = -2 // (7)
  1. 피 연산자 중 하나가 문자열인 "" + 1에서 1은 문자형으로 변환됩니다. 따라서 공백과 문자열 1을 더한, "" + 1 = "1"과 같은 효과를 발휘하죠. 그다음 연산 "1" + 0에도 같은 규칙이 적용됩니다.
  2. 뺄셈 연산자 -는 기타 수학 연산자처럼 숫자형만을 인수로 받습니다. 빈 문자열 ""는 숫자 0으로 변환되기 때문에 결과는 -1이 됩니다.
  3. 피 연산자 중 하나가 문자열이므로 숫자 5가 문자열로 변환됩니다.
  4. 뺄셈 연산자는 인수를 숫자형으로 변화시키므로 " -9 "는 숫자 -9로 변합니다. 앞, 뒤 공백은 제거되죠.
  5. 숫자형으로 변환 시 null0이 됩니다.
  6. undefined는 숫자형으로 변환시 NaN이 됩니다.
  7. 문자열이 숫자형으로 변할 땐 문자열 앞뒤의 공백이 삭제됩니다. 뺄셈 연산자 앞의 피연산자는 공백을 만드는 문자 \t\n, 그 사이의 “일반적인” 공백으로 구성됩니다. 따라서 " \t \n"는 숫자형으로 변환 시 길이가 0인 문자열로 취급되어 숫자 0이 됩니다.
튜토리얼 지도

댓글

댓글을 달기 전에 마우스를 올렸을 때 나타나는 글을 먼저 읽어주세요.
  • 추가 코멘트, 질문 및 답변을 자유롭게 남겨주세요. 개선해야 할 것이 있다면 댓글 대신 이슈를 만들어주세요.
  • 잘 이해되지 않는 부분은 구체적으로 언급해주세요.
  • 댓글에 한 줄짜리 코드를 삽입하고 싶다면 <code> 태그를, 여러 줄로 구성된 코드를 삽입하고 싶다면 <pre> 태그를 이용하세요. 10줄 이상의 코드는 plnkr, JSBin, codepen 등의 샌드박스를 사용하세요.