이 포스팅은 https://github.com/leonardomso/33-js-concepts 에 있는 포스팅들을 번역한 것입니다. 오역이나 의역이 있을 수 있습니다. 지적해주시면 확인 후 바로 정정하겠습니다.
original source of this posting is from https://dev.to/promhize/javascript-in-depth-all-you-need-to-know-about-expressions-statements-and-expression-statements-5k2 If the original author requests deletion, it will be deleted immediately.
Translated by Jake Seo (서진규)
- https://velog.io/@jakeseo_me
- https://github.com/n00nietzsche
자바스크립트 개발자라면 알아야 할 33가지 개념 #7 표현식(Expression)과 문장(Statement) (번역)
이 글을 거의 다 읽었을 때쯤에는 아래의 이미지의 코드가 어떻게 동작하는지 왜 동작하는지에 대해서 자세히 설명할 수 있게 될 것입니다.
자바스크립트에는 대표적인 2가지 문법적 카테고리가 있습니다.
표현식(Expression)은 문장(Statement)처럼 동작할 수 있기 때문에 이 둘을 구분하는 것은 중요합니다. 그리고 이것이 표현문(Expression statement)이 존재하는 이유기도 하죠. 하지만 반대로 봤을 때 문장(Statement)은 표현식(Expression)처럼 동작할 수 없습니다.
표현식은 값 하나로 귀결되는 자바스크립트 코드 조각(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
예를 들면,
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) 사이에서 확연히 구분이 됩니다. 이것이 선언적이고 함수적인 자바스크립트의 기반입니다.
함수형 프로그래밍의 관점에서 문장은 골치덩어리(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)이 값을 반환하는 것은 아무런 의미가 없습니다. 이게 바로 자바스크립트입니다. 이상합니다.
함수 선언은 문장(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)은 이름이 필요합니다.
여태까지 자바스크립트에 대한 것중 어느것 하나라도 간단하고 직관적인 것이 있었나요?
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)
세미콜론을 붙이면, 여러 줄의 문장(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
모든 표현식은 왼쪽에서 오른쪽으로 계산됩니다. 그리고 마지막 것이 리턴이 되죠.
익명 함수는 표현식으로 쓰일 수 있습니다. 자바스크립트에서 값이 들어갈 곳에 쓰일 수 있다면, 우리가 만약 자바스크립트에서 값이 들어갈 곳에 괄호를 쓸 수 있다면 우리는 익명 함수를 값으로 넘길 수 있다는 것을 의미합니다.
function () {}
위의 코드는 유효하지 않은 반면에 아래의 코드는 유효합니다.
(function () {}) // function () {}를 리턴합니다.
만일 익명 함수를 괄호 속에 넣는다면 즉시 같은 익명 함수를 리턴합니다. 이 말은 우리가 바로 이 함수를 불러올 수 있다는 것입니다. 다음과 같이요.
(function () {
// do something
})()
그래서 다음과 같은 것도 가능합니다.
(function () {
console.log("익명함수 즉시 호출");
})() // "익명함수 즉시 호출"
(function () {
return 3;
})() // 3
console.log((function () {
return 3;
})()) // 3
// 인자를 넘길 수도 있습니다.
(function (a) {
return a;
})("저는 인자입니다."); // 저는 인자입니다.
주: 리터럴이란 값 그 자체를 의미합니다.
사이드 노트 : 다음에 나오는 코드들은 유효한 자바스크립트입니다.
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에 대해서는 이정도만 알면 될 것 같습니다.
식과 문 개념에 대해 잘 몰랐는데, 글을 일목요연하게 잘 써주셔서 이해하는데에 큰 도움이 되었습니다. 감사합니다.
그런데 조금 의문이 남는게 마지막 부분에서
블록 문장(block statements)에서 무엇이 반환되던지 그것은 암묵적으로 0로 강제 형변환되어 피연산자로 사용됩니다.
라고 하셨는데요.
{} + 1
, {2+2} + 3
코드를 예로 들때, 좌측 피연산자({2+2}
문)가 0
으로 변환된다는것이 잘 이해가 안 되어서요. 혹시 0
으로 변환되는게 아니라 다음과 같이 별개의 식으로 평가되어 실행되는 것일 가능성은 없을까요?
// {} + 1
{
/* 빈 블록 */
}
+1;
// {2+2} + 3
{
2 + 2;
}
+3;
공부하는데 도움이 되었습니다. 잘보고갑니다