들어가기 전에

자바스크립트 개발자라면 알아야 할 33가지 개념 #7 표현식(Expression)과 문장(Statement) (번역)

이 글을 거의 다 읽었을 때쯤에는 아래의 이미지의 코드가 어떻게 동작하는지 왜 동작하는지에 대해서 자세히 설명할 수 있게 될 것입니다.

ExpressionAndStatement1.PNG

자바스크립트에는 대표적인 2가지 문법적 카테고리가 있습니다.

  1. Statements (문장)
  2. Expressions (표현식)

표현식(Expression)은 문장(Statement)처럼 동작할 수 있기 때문에 이 둘을 구분하는 것은 중요합니다. 그리고 이것이 표현문(Expression statement)이 존재하는 이유기도 하죠. 하지만 반대로 봤을 때 문장(Statement)은 표현식(Expression)처럼 동작할 수 없습니다.

표현식(Expressions)

표현식은 값을 만들어낸다

표현식은 값 하나로 귀결되는 자바스크립트 코드 조각(snippet)입니다. 표현식은 우리가 원하는만큼 길어질 수 있지만 언제나 동일한 값이 나오진 않죠.

2 + 2 * 3 / 2

(Math.random() * (100-20)) + 20

functionCall()

window.history ? useHistory() : noHistoryFallback()

1+1, 2+2, 3+3

declaredVariable

true && functionCall()

true && declaredVariable

위의 모든 코드는 표현식입니다. 그리고 표현식은 자바스크립트 코드 중 값이 들어가는 곳이면 어디에나 넣을 수 있습니다. 그래서 아래 console.log의 인자는 콘솔이 로깅될 때 하나의 값으로 변합니다.

console.log(true && 2 * 9) // 18

표현식은 반드시 상태(State)를 바꿀 필요는 없다.

예를 들면,

const assignedVariable = 2; // 이건 문장(Statement)입니다. assignedVariable은 상태입니다.

assignedVariable * 4 // 표현식(Expression)입니다.

assignedVariable * 10 // 표현식(Expression)입니다.

assignedVariable - 10 // 표현식(Expression)입니다.

console.log(assignedVariable) // 2

위의 짤막한 코드의 모든 표현식에도 불구하고 assignedVariable의 값은 여전히 2입니다. 그래서 왜 이 섹션의 제목에 왜 반드시라는 단어를 등장시켰냐면 함수 호출은 표현식이기 때문입니다. 하지만 함수는 값을 변화시키는 문장(Statement)을 포함할 수 있습니다. foo 내부의 foo() 함수는 undefined나 어떤 다른 값을 반환할 수 있는 표현식입니다. 하지만 만일 foo가 다음과 같이 작성됐다면,

const foo = foo () => {
  assignedValue = 14  
}

그 땐, foo를 호출하는 것은 표현식일지라도, 함수를 호출하면 결국 상태(state)가 바뀌게 됩니다. 그래서 foo 함수를 더 나은 방법으로 재작성하려면 문장(Statement)는 다음과 같을 것입니다.

const foo = foo () => {
  return 14; // 가독성을 위한 명시적 반환  
}

assignedVariable = foo()

아니면 더 나은 방법으로

const foo = foo (n) => {
  return n // 가독성을 위한 명시적 반환  
}

assignedVariable = foo(14)

이 편이 더욱 가독성이 좋고, 어딘가에 끼워넣기 좋으며 표현식(Expression)과 문장(Statement) 사이에서 확연히 구분이 됩니다. 이것이 선언적이고 함수적인 자바스크립트의 기반입니다.

문장(Statements)

함수형 프로그래밍의 관점에서 문장은 골치덩어리(headache)입니다. 기본적으로 문장은 무언가 수행합니다.

자바스크립트에서 문장은 값이 들어와야 할 곳에 들어갈 수 없습니다. 그래서 그들은 함수의 인자로도, 대입 연산의 값으로도, 연산자의 피연산자로도 사용될 수 없습니다.

foo(if () {return 2}) // js engine mind = blown

자바스크립트의 문장(Statement)은 다음과 같습니다.

  1. if
  2. if-else
  3. while
  4. do-while
  5. for
  6. switch
  7. for-in
  8. with (deprecated)
  9. debugger
  10. variable declaration

브라우저의 콘솔에 다음과 같은 코드를 치고 엔터를 치면 어떻게 될까요?

if (true) {9+9}

아마 18이 리턴된 것이 보일 것입니다. 하지만 우리는 이 결과를 표현식 처럼 사용하거나 자바스크립트 코드 내에 값이 들어갈 어딘가에 넣을 수 없습니다. 이 결과는 이상합니다. 우리는 문장(statement)이 아무것도 반환하지 않을 것이라 예상했기 때문일 겁니다. 우리가 반환된 값을 이용할 수 없으면 문장(statement)이 값을 반환하는 것은 아무런 의미가 없습니다. 이게 바로 자바스크립트입니다. 이상합니다.

함수 선언, 함수 표현식 그리고 네임드(Named) 함수 표현식

함수 선언은 문장(Statement)입니다.

function foo (func) {
  return func.name;  
}

함수 표현식은 표현식입니다. 바로 우리가 익명 함수라 부르는 것들입니다.

console.log(foo(function () {} )); // ""

네임드 함수 표현식은 표현식입니다. 익명 함수처럼요. 하지만 이 함수는 이름이 붙었습니다.

console.log(foo(function myName () {} )); // "myName"

표현식으로서의 함수와 선언으로서의 함수의 구분은 아래의 내용을 이해하는 것으로 요약됩니다.
우리가 자바스크립트에서 값이 들어올 곳에 함수를 선언할 때마다, 자바스크립트는 그것을 값으로 다루려 할 것입니다. 만일 그 함수가 값으로 사용될 수 없다면, 에러가 발생할 것입니다.
반면에 스크립트, 모듈, 블록 문장(자바스크립트에서 값이 들어가는 곳이 아닌 위치에 있는)의 전역 단계(Global level)에 함수를 선언하는 것은 결과적으로 함수선언입니다.

예제:

if () {
  function foo () {} // 블록의 가장 상위 레벨, 함수 선언
}

function foo () {} // 전역 레벨, 함수 선언

function foo () {
  function bar () {} // 블록의 가장 상위 레벨, 함수 선언
}

function foo () {
  return function bar () {} // 네임드 함수 표현식  
}

foo(function () {}) // 익명 함수 표현식

function foo () {
  return function bar () {
    function baz () {} // 블록의 가장 상위 레벨, 함수 선언  
  }
}

function () {} // 문법 에러: 함수 문장(statement)은 이름이 필요합니다.

표현식을 문장으로 바꾸기: 표현식 문장(Expression Statements)

여태까지 자바스크립트에 대한 것중 어느것 하나라도 간단하고 직관적인 것이 있었나요?

2+2; // expression statement
foo(); // expression statement

우리는 표현식(Expressions)을 표현식 문장(Expression Statements)으로 바꿀 수 있습니다. 단지 뒤에 세미콜론만 추가하면 됩니다. 2+2자체는 표현식입니다. 하지만 그 줄(line) 자체는 표현식 문장(Expression Statements)입니다.

2+2 // 그 자체로는 표현식입니다.

foo(2+2) // 그래서 어디든 값이 들어가야 할 곳에서 사용할 수 있죠.

true ? 2+2 : 1 + 1

function foo () {return 2+2}

2+2; // 표현식 문장(Expression Statements)
foo(2+2;) // 문법 에러(Syntax Error)

세미콜론 vs 콤마 연산자

세미콜론을 붙이면, 여러 줄의 문장(Statements)을 하나의 줄에 넣을 수 있습니다.

const a; function foo () {}; const b = 2;

콤마 연산자는 우리가 여러 개의 표현식을 연결할 수 있도록 도와줍니다. 반환은 마지막 표현식만 반환합니다.

console.log( (1+2, 3, 4) ) // 4

console.log( (2, 9/3, function () {}) ) //function () {}

console.log( (3, true ? 2+2 : 1+1) ) // 4

알아두면 좋은 것 : 자바스크립트 엔진에게 값을 전달할 때, 값이 들어가야 할 곳에 괄호'()'를 통해 값을 전달하세요. 괄호가 없으면 각각을 console.log의 인자로 보냅니다.

function foo () {return 1, 2, 3, 4}
foo() // 4

모든 표현식은 왼쪽에서 오른쪽으로 계산됩니다. 그리고 마지막 것이 리턴이 되죠.

IIFEs (Immediately Invoked Function Expression(즉시 호출되는 함수 표현식))

익명 함수는 표현식으로 쓰일 수 있습니다. 자바스크립트에서 값이 들어갈 곳에 쓰일 수 있다면, 우리가 만약 자바스크립트에서 값이 들어갈 곳에 괄호를 쓸 수 있다면 우리는 익명 함수를 값으로 넘길 수 있다는 것을 의미합니다.

function () {}

위의 코드는 유효하지 않은 반면에 아래의 코드는 유효합니다.

(function () {}) // function () {}를 리턴합니다.

만일 익명 함수를 괄호 속에 넣는다면 즉시 같은 익명 함수를 리턴합니다. 이 말은 우리가 바로 이 함수를 불러올 수 있다는 것입니다. 다음과 같이요.

(function () {
  // do something  
})()

그래서 다음과 같은 것도 가능합니다.

(function () {
    console.log("익명함수 즉시 호출");
})() // "익명함수 즉시 호출"

(function () {
  return 3;
})() // 3

console.log((function () {
  return 3;
})()) // 3


// 인자를 넘길 수도 있습니다.
(function (a) {
  return a;
})("저는 인자입니다."); // 저는 인자입니다.

오브젝트 리터럴 vs 블록 문장(Block Statement)

주: 리터럴이란 값 그 자체를 의미합니다.
사이드 노트 : 다음에 나오는 코드들은 유효한 자바스크립트입니다.

r: 2+2 // 유효함

foo()

const foo = () => {}

글로벌 스코프에 위치한 위의 문장(Statement)들은 유효한 자바스크립트로 변경되어 실행됩니다. r은 label이라 불리는 것입니다. breaking loops를 구성할 때 유용합니다. 예제는 다음과 같습니다.

loop: {
  for (const i=0; i<2; i++){
    for (const n=0; n<2; n++){
      break loop; // 바깥 루프를 중단하여 전체 루프를 중단합니다.  
    }
  }
}

라벨을 어떤 표현식이나 표현식 문장에 붙일 수도 있습니다.
다음의 예제를 보고, 라벨을 만드는 것이 변수를 만드는 건 아니라는 것을 알 수 있습니다.

lab: function a () {}
console.log(lab) // ReferenceError: lab is not defined

{}와 같은 괄호는 문장과 표현식 문장들을 그룹화하는데 도움을 줍니다.
다음과 같이 작성할 수 있습니다.

{var a = "b"; func(); 2+2} // 4

위의 내용을 브라우저 콘솔에 붙여넣기 하면, 4를 반환할 것입니다. 그리고 console.log(a); 쳐보면 b를 반환합니다. 이것을 블록 문장이라고 불러도 됩니다. 우리에게 익숙한 오브젝트 리터럴과는 다른 것입니다.

console.log({a: 'b'}); // {a: 'b'} 오브젝트 리터럴

console.log({var a = "b", func(), 2+2}) // SyntaxError 블록 문장

const obj = {var a = "b", func(), 2+2} // SyntaxError 블록 문장

블록 문장을 값이나 표현식으로 사용할 수는 없습니다. console.log는 문장(statement)을 인자로 받아들일 수 없는 함수이니까요. 하지만 오브젝트 리터럴은 인자로 받아들일 수 있습니다. 위에 설명했던 모든 것들을 이해하셨길 바랍니다. 아래의 코드는 당신을 미궁에 빠트릴 수도 있으니까요.

{} + 1 // 1

{2} + 2 // 2

{2+2} + 3 // 3

{2+2} - 3 // -3

아마 위의 코드들이 문법 에러나 1, 4, 7과 같은 숫자를 각각 리턴한다고 예측했을 수도 있습니다. 기억하세요. 문장(statements)은 어느것도 반환하도록 되어있지 않습니다. 왜냐하면 값으로 쓰일 수 없으니까요. 그래서 자바스크립트는 error을 내보내지 않는 대신에 + 연산자의 피연산자를 숫자나 문자열로 바꿉니다. 만일 바꿀 수 없는 값이라면 에러를 내보냅니다. 블록 문장(block statements)에서 무엇이 반환되던지 그것은 암묵적으로 0로 강제 형변환되어 피연산자로 사용됩니다.

만일 여기까지 읽으셨다면, 당신이 진정한 MVP입니다. Javascript 내에서 Expressions, Statements, Expression Statements에 대해서는 이정도만 알면 될 것 같습니다.