프로그래밍-학습기록/Javascript

javascript 논리연산자(&&, ||) 우선순위 vs. 단락 평가(short circuit)

leesche 2021. 1. 29. 23:54

자바스크립트 논리연산자를 공부하다가 논리연산자 우선순위단락 평가를 알게 됐다. 배우면서 알게된 것을 기록해두려고 한다.

자바스크립트에서 논리연산자는 표현식을 반환한다.

자바스크립트에서 논리연산자 AND와 OR는 참과 거짓만 반환하는 게 아니라, 표현식 자체를 반환한다. 규칙은 다음과 같다.

논리연산자 설명

  • AND의 경우 왼쪽이 참 같은 값(Truthy)이면 오른쪽을 반환하고, 참 같은 값이 아니면 왼쪽을 반환한다.
  • OR의 경우 왼쪽이 참 같은 값이면 왼쪽을 반환하고, 참 같은 값이 아니면 오른쪽을 반환한다.

논리 연산자 우선순위는 에 의하면 AND는 6, OR는 5로 AND가 OR 보다 우선순위가 더 높다. 다음은 MDN 웹 문서에 나와 있는 예제다.

true || false && false      // returns true, because && is executed first
(true || false) && false    // returns false, because operator precedence cannot apply

하지만 (적어도 지금까지 내가 판단하기에) 위 예제 첫번째는 오류가 있다. 분명 우선순위에서 앞서는 논리 AND은 먼저 실행되지 않는 것처럼 보인다. 확인할 수 있도록 콘솔에서 예제를 입력해보자.

console.log()의 반환 값은 undefined이므로 거짓 같은 값(falsey)이다. &&연산자가 정상적으로 먼저 실행됐다면, 좌측 표현식을 평가하고(즉, "am I executed?"를 콘솔에 출력하고) 참 같은 값이 아니기 때문에 undefined가 반환되고, 이후 좌측에 있는 true||연산을 해서 true가 최종 반환되어야 한다. 하지만 콘솔에 출력된 것은 없다. 즉, 뒤의 &&은 실행되지 않았다(혹은 실행됐는데 확인할 수 없다..?).

뒤의 논리연산자가 실행되지 않은 이유는 단락 평가 때문이다.

놀랍게도 논리연산자의 우선순위는 단락 평가에 무시당한다.

단락 평가는 다음과 같은 규칙을 따른다.

  • AND 연산일 때 좌측 표현식이 거짓이면 우측 표현식이 뭐든 평가하지 않는다.
  • OR 연산일 때 좌측 표현식이 참이면 우측 표현식이 뭐든 평가하지 않는다.

논리연산이고 뭐고, 우선순위가 높아도, 단락 평가가 일어난 순간 그 뒤의 연산은 모두 무시된다.

어차피 논리연산해봤자 결과가 정해져있는데, 뒤에 건 봐서 뭣해?

라고 하고 자바스크립트 컴파일러(?)가 에너지를 아끼는 것이다.

단락 평가로 인해 주의해야 할 점

위와 같이 원래라면 참조 오류가 났어야 할 표현식도 평가되지 않고 그저 false만 반환된다. 단락 평가되었기 때문이다.

따라서 AND와 OR을 사용해서 프로그래밍을 할 경우 단락 평가가 일어났을 때 단락 평가 이후의 표현식이 오류가 있는지 없는지 판단할 길이 없으므로 주의가 필요하다는 것을 알 수 있다.

아래는 AND 연산과 OR 연산의 조합에 따라 표현식이 어떤 순서로 평가되는지 쉽게 알기 위해 (급하게, 대충) 만든 예제이다. 논리연산자 우선순위, 단락평가의 개념이 헷갈린다면 참고해서 보면 좋겠다.

function log(logical, order) {
  console.log(order);
  return logical;
}

// ------------------ A || B && C ------------------
console.log(
  log(true, "첫번째") || (log(true, "두번째") && log(true, "세번째"))
);
// 첫번째
// true

console.log(
  log(true, "첫번째") || (log(true, "두번째") && log(false, "세번째"))
);
// 첫번째
// true

console.log(
  log(true, "첫번째") || (log(false, "두번째") && log(false, "세번째"))
);
// 첫번째
// true

console.log(
  log(true, "첫번째") || (log(false, "두번째") && log(true, "세번째"))
);
// 첫번째
// true

console.log(
  log(false, "첫번째") || (log(true, "두번째") && log(true, "세번째"))
);
// 첫번째
// 두번째
// 세번째
// true

console.log(
  log(false, "첫번째") || (log(true, "두번째") && log(false, "세번째"))
); 
// 첫번째
// 두번째
// 세번째
// false

console.log(
  log(false, "첫번째") || (log(false, "두번째") && log(true, "세번째"))
);
// 첫번째
// 두번째
// false

console.log(
  log(false, "첫번째") || (log(false, "두번째") && log(false, "세번째"))
);
// 첫번째
// 두번째
// false

// ------------------ A && B || C --------------------
console.log(
  log(true, "첫번째") && (log(true, "두번째") || log(true, "세번째"))
);
// 첫번째
// 두번째
// true

console.log(
  log(true, "첫번째") && (log(true, "두번째") || log(false, "세번째"))
);
// 첫번째
// 두번째
// true

console.log(
  log(true, "첫번째") && (log(false, "두번째") || log(false, "세번째"))
);
// 첫번째
// 두번째
// 세번째
// false

console.log(
  log(true, "첫번째") && (log(false, "두번째") || log(true, "세번째"))
);
// 첫번째
// 두번째
// 세번째
// true

console.log(
  log(false, "첫번째") && (log(true, "두번째") || log(true, "세번째"))
);
// 첫번째
// false

console.log(
  log(false, "첫번째") && (log(true, "두번째") || log(false, "세번째"))
);
// false

console.log(
  log(false, "첫번째") && (log(false, "두번째") || log(true, "세번째"))
);
// 첫번째
// false

console.log(
  log(false, "첫번째") && (log(false, "두번째") || log(false, "세번째"))
);
// 첫번째
// false

참고 문헌