자바스크립트에는 대표적인 2가지 문법적 카테고리가 있다.
표현식은 문장처럼 동작할 수 있다 -> 때문에 둘은 구분하는 것은 중요하다.
그리고 이것이 표현문이 존재하는 이유다.
하지만 반대로 문자은 표현식처럼 동작할 수 없다.
표현식은 값 하나로 귀결되는 자바스크립트 코드 조각이다.
표현식은 우리가 원하는 만큼 길어질 수 있지만 언제나 동일한 값이 나오진 않는다.
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다.
그래서 이 섹션의 제목에 왜 반드시라는 단어를 등장시켰냐면 함수 호출은 표현식이기 때문이다.
하지만 함수는 값을 변화시키는 문장을 포함할 수 있다.
foo내부의 foo()
함수는 undefined 나 어떤 다른 값을 반환할 수 있는 표현식이다.
하지만 만일 foo가 다음과 같이 작성됐다면,
const foo = foo () => {
assignedValue = 14
}
그땐, foo를 호출하는 것은 표현식일지라도, 함수를 호출하면 결국 상태가 바뀌게 된다.
그래서 foo 함수를 더 나은 방법으로 재작성하려면 문장(statement)는 다음과 같을 것이다.
함수는 표현식 , 표현식은 상태를 반드시 바꿀 수 없다. - > 함내 내부는 값을 변화시키는 문장을 포함할 수 있다???
const foo = foo () => {
return 14 // 가독성을 위한 명시적 반환
}
assignedVariable = foo()
아니면 더 나은 방법으로
const foo = foo (n) => {
return n // 가독성을 위한 명시적 반환
}
assignedVariable = foo(14)
이편이 더욱 가독성이 좋고, 어딘가에 끼워넣기 좋으며 표현식과 문장 사이에서 확연히 구분된다. 이것이 선언적이고 함수적인 자바스크립트 기반이다.
함수형 프로그래밍의 관점에서 문장은 골치덩어리
기본적으로 문장은 무언가 수행한다.
자바스크립트에서 문장은 값이 들어와야 할 곳에 들어갈 수 없다.
그래서 그들은 함수의 인자로도, 대입연산의 값으로도, 연산자의 피연산자로도 사용될 수 없다.
foo(if () {return 2})
자바스크립트의 문장(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
브라우저 콘솔에 다음과 같은 코드를 치고 엔터를 치면??
i( true ) {9+9}
아마 18이 리턴된 것이 보일 것이다.
하지만 우리는 이 결과를 표현식처럼 사용하거나 자바스크립트 코드 내에 값이 들어갈 어딘가에 넣을 수 없다.
이 결과는 이상하다.
우리는 문장이 아무것도 반환하지 않을 것이라 예상했기 떄문이다.
우리가 반환된 값을 이용할 수 없으면 문장이 값을 반환하는 것은 아무런 의미가 없다. 이게 바로 자바스크립트다.
함수 선언은 문장(statement)이다.
function foo (func) {
return func.name;
}
함수표현식은 표현식이다. 바로 우리가 익명함수라 부르는 것들이다.
console.log(foo(function(){})); //""
네임드 함수 표현식은 표현식이다. 익명함수처럼
하지만 이함수는 이름이 붙었다.
console.log(foo(function myName () {} )); // "myName"
표현식으로서의 함수와 선언으로서의 함수의 구분은 아래의 내용을 이해하는 것으로 요약된다.
우리가 자바스크립트에서 값이 들어올 곳에 함수를 선언할때마다, 자바스크립트는 그것을 값으로 다루려고 할 것이다. 만일 그 함수가 값으로 사용될 수 없다면, 에러가 발생할 것이다.
반면에 스크립트, 모듈, 블록 문장( 자바스크립트에서 값이 들어가는 곳이 아닌 위치에 있는) 전역 단계에 함수를 선언하는 것은 결과적으로 함수선언이다.
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
우리는 표현식을 표현식 문장으로 바꿀수 있다.
단지 뒤에 세미콜론만 추가하면 된다.
2+2자체는 표현식이다.
하지만 줄 자체는 표현식 문장이다.
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는 문장을 인자로 받아들일 수 없는 함수
하지만 오브젝트 리터럴은 인자로 받아들일 수 있다.
{} + 1 // 1
{2} + 2 // 2
{2+2} + 3 // 3
{2+2} - 3 // -3
아마 위의 코드들이 문번에러나 1,4,7과 같은 숫자를 각각 리턴한다고 예측했을 수도 있다.
문장은 어느것도 반환하도록 되어있지 않는다.
왜냐하면 값으로 쓰일 수 없기 때문이다.
그래서 자바스크립트는 error를 내보내지 않는 대신에 + 연산자의 피연산자를 숫자나 문자열로 바꾼다.
만일 바꿀 수 없는 값이라면 에러를 내보낸다.
블록문장에서 무엇이 반환되던디 그것은 암묵적으로 0
으로 강제 형변환되어 피연산자로 사용된다.
Javascript 내에서 Expressions, Statements, Expression Statements에 대해서는 이정도만 알면 된다.
🔍 출처 블로그
자바스크립트 개발자라면 알아야 할 33가지 개념 #7 표현식(Expression)과 문장(Statement) (번역)