문(Statement)과 표현식(Expression)

uchanlee.dev·2020년 10월 28일
5

JavaScript

목록 보기
1/1
post-thumbnail

문(Statement)과 표현식(Expression)

  • 문과 표현식을 대충 같은 의미라고 넘겨버리는 개발자가 많다.
  • 그러나 자바스크립트에서 두 용어는 아주 중요한 차이가 있으므로 명확하게 구분해야 한다.

문(Statement)

  • 실행 가능한 최소의 독립적인 코드 조각
  • statement는 흔히 한 개 이상의 expression이나 프로그래밍 키워드를 포함하는 경우가 많다.
for (let i = 0; i < 10; i++) {} // for statment

while (true) {} // while statment

if (true) {} // if statment

let a; // declaration statment

function b() {} // function declaration statement

표현식(Expression)

  • 특정한 결괏값으로 계산되는 것
let a = 3 * 6; // 1.
let b = a; // 2.
b; // 3.
    • 3 * 6은(18이라는 값으로 평가되는) 표현식이다.
    • let a = 3 * 6; 문은 변수를 선언(그리고 선택적으로 동시에 어떤 값을 할당)하므로 '선언문(Declaration Statement)'이라 한다.
    • (앞에 let이 빠진) a = 3 * 6;는 '할당 표현식(Assignment Expression)'이라고 한다.
    • let b = a; 문도 변수를 선언(그리고 선택적으로 동시에 어떤 값을 할당)하므로 '선언문(Declaration Statement)'이라 한다.
    • a라는 표현식은 당시 변수들에 저장된 값으로 평가되므로 18이라는 값으로 평가되고 역시 b는 18이 된다.
    • b가 표현식의 전부지만 이것 만으로도 완전한 문이다.
    • 일반적으로 이런 문은 '표현식 문(Expression Statement)'이라고 부른다.

문의 완료 값

  • 모든 문은 (그 값이 undefined라고 해도) 완료 값(Completion Value)을 가진다는 사실을 의외로 모르는 사람들이 많다. 나 또한 이번에 처음 알았다.

    공식 문서중 일부

📑 출처

문의 완료 값을 확인하는 방법은 없을까?

  • 가장 확실하고 빠른 방법은 브라우저 개발자 콘솔 창에서 문을 타이핑해보는 것이다.
  • 콘솔 창은 가장 최근에 실행된 문의 완료 값을 기본적으로 출력하게 되어 있다.

let c = 10; 같은 문은 완료 값이 뭘까?

  • 할당 표현식 c = 10;은 할당 이후의 값(여기서는 10)이 완료 값이지만, let 문 자체의 완료 값은 undefined다.

콘솔 창이 반환해준 완료 값은 개발자가 내부 프로그램에서 사용할 수 있는 값은 아니다. 그럼 완료 값을 사용할 방법은 없을까?

  • 방법은 있지만 꽤 복잡하다.
  • 다른 종류의 문 완료 값을 보자.
  • 예를 들어, 보통의 { } 블록은 내부의 가장 마지막 문/표현식의 완료 값을 자신의 완료 값으로 반환한다.
let b;

if (true) {
   b = 5 + 10;
}
  • 콘솔 창에서 실행하면 15가 나온다.
  • 블록 내의 마지막 문 b = 5 + 10;의 완료 값이 15이므로 if 블록의 완료 값도 15를 반환한 것이다.
  • 즉, 블록의 완료 값은 내부에 있는 마지막 문의 값을 암시적으로 반환한 값이다.

하지만 다음과 같은 코드가 작동하지 않는 건 분명히 문제가 있다.

let a, b;

a = if (true) {
   b = 5 + 10;
}
  • 문의 완료 값을 캐치하여 다른 변수에 할당한다는 건 쉬운 구문/문법으로는 불가능하다.
  • 뭔가 방법이 없을까? 🤔
  • 완료 값을 캐치하려면 어쩔 수 없이 유해함의 대명사 eval()함수를 사용할 수 밖에 없다.
let a, b;

a = eval('if (true) { b = 5 + 10; }');

a; // 15
  • 꼴 보기 싫은 코드지만 일단 잘 돌아간다!
  • 콘솔 창 외에 자바스크립트 프로그램에서도 문 완료 값을 확인할 방법이 있음을 알 수 있다.

ES7 명세에는 'do 표현식'이 제안된 상태다.

let a, b;

a = do {
   if (true) {
      b = 5 + 15;
   }
};

a; // 15
  • do { } 표현식은 (하나 이상의 문을 포함한) 블록 실행 후 블록 내 마지막 문의 완료 값을 do 표현식 전체의 완료 값으로 반환하며 결국 이 값이 변수 a 에 할당된다.
  • 인라인 함수 표현식 안에 감싸서 명시적으로 반환할 필요 없이 문을 (다른 문 안에 들어갈 수 있는) 표현식처럼 다루자는게 기본적인 아이디어다.
  • 아직까지는 문 완료 값을 대수롭지 않게 여기고 있지만 자바스크립트 언어가 진화할수록 그 중요성은 점점 더 부각될 것 같다.
  • 어서 do { } 표현식이 도입되어 eval() 같은 나쁜 것들을 사용하고픈 욕구를 영원히 잠재울 수 있길 고대한다.

표현식의 부수 효과

  • 대부분의 표현식에는 부수 효과가 없다. 예를 들면,
let a = 2;
let b = a + 3;
  • 표현식 a + 3 자체는 가령 a 값을 바꾸는 등의 부수 효과가 전혀 없다.
  • 단지 b = a + 3 문에서 결괏값 5가 b에 할당될 뿐이다.

다음의 함수 호출 표현식은 부수 효과를 가진(가졌을지 모를) 표현식의 전형적인 예이다.

let a = 1;

function foo() {
   a += 1;
}

foo(); // 결괏값: 'undefined', 부수 효과: 'a'가 변경됨.

다른 부수 효과를 지닌 표현식을 보자.

let a = 7;
let b = a++;

a; // 8
b; // 7
  • 표현식 a++이 하는 일은 두 가지다.
  • a의 현재 값 7를 반환(그리고 b에 할당하는 것까지)하고 a 값을 1만큼 증가시킨다.

++a++은 문법에 맞는 구문일까?

  • 실행하면 ReferenceError 에러가 난다.
  • 부수 효과를 유발하는 연산자는 부수 효과를 일으킬 변수 레퍼런스가 꼭 필요하기 때문이다.
  • ++a++에서는 a++시 ++ 연산자는 42 같은 원시 값에 직접 부수 효과를 일으킬 수는 없으므로 ReferenceError를 던진다.

delete 역시 부수 효과를 일으키는 연산자다.

  • delete는 객체의 프로퍼티를 없애거나 배열에서 슬롯을 제거할 때 쓴다.
  • 하지만 단독 문(Standalone Statement)으로 더 많이 쓴다.
const obj = {
   a: 30,
};

obj.a; // 30
delete obj.a; // true
obj.a; // undefined
  • delete 연산자의 결괏값은 유효한/허용된 연산일 경우 true, 그 외에는 false다.
  • 이 연산자의 부수 효과는 바로 프로퍼티(또는 배열 슬롯)를 제거하는 것이다.
  • '유효한/허용된'이란 무슨 의미일까?
    • 존재하지 않는 프로퍼티 또는 존재하면서 설정 가능한(Configurable)한 프로퍼티일 경우 delete 연산자는 true를 반환한다. 그 외의 경우는 false를 반환하거나 에러를 낸다.

마지막으로 예시할 부수 효과 유발 연산자는 언뜻 보기에 분명한 것 같으면서도 분명하지 않은 = 할당 연산자다.

let a;

a = 42; // 42
a; // 42
  • a = 42에서 = 연산자는 아무리 봐도 부수 효과와는 무관해 보인다.
  • 하지만 a = 42 문의 실행 결과는 이제 막 할당된 값(42)이므로 42를 a에 할당하는 자체가 본질적으로 부수 효과다.

이렇게 할당 표현식문 실행 시 할당된 값이 완료 값이 되는 작동 원리는 다음과 같은 연쇄 할당문(Chained Assignment)에서 특히 유용하다.

let a, b, c;

a = b = c = 20;
  • c = 20 평가 결과는 (20을 c에 할당하는 부수 효과를 일으키며) 20이 되고, b = 20 평가 결과는 (20을 b에 할당하는 부수 효과를 일으키며) 20이 된다. 결국, a = 20으로 (20을 a에 할당하는 부수 효과를 일으키며) 평가된다.

또 다른 예를 보자.

function vowels(str) {
   let matches;

   if (str) {
      // 모든 모음을 추출한다.
      matches = str.match(/[aeiou]/g);

      if (matches) {
         return matches;
      }
   }
}

vowels("Hello World"); // ["e", "o", "o"]
  • 잘 작동하는 코드다.
  • 할당 연산자의 부수 효과를 잘 활용하면 다음과 같이 2개의 if 문을 하나로 간단히 합칠 수 있다.
function vowels(str) {
   let matches;

   // 모든 모음을 추출한다.
   if (str && (matches = str.match(/[aeiou]/g))) {
      return matches;
   }
}

vowels("Hello World"); // ["e", "o", "o"]
  • matches = str.match를 감싸는 ( )를 빠뜨리면 안 된다.
  • 두 조건이 서로 분명히 연관되어 있음을 잘 보여주기 때문에 나는 후자를 더 선호하는 편이다. 물론 어떤 스타일을 선호할지는 개인 취향 차이다.

출처

profile
늦게 시작하는 것을 두려워 하기보단 하다가 중단 하는 것을 두려워 하자!

1개의 댓글

comment-user-thumbnail
2021년 6월 17일

어렵네요

답글 달기