이 글이 끝나면 아래의 이미지 코드가 어떻게, 왜 동작하는지 알 수 있을 것이다.
자바스크립트에는 대표적인 2가지 문법적 카테고리가 있다.
표현식(Expression)은 문장(Statement)처럼 동작할 수 있기 때문에 이 둘을 구분하는 것은 중요하다. 그리고 이것이 표현문(Expression statement)이 존재하는 이유기도 하다. 하지만 반대로 봤을 때 문장(Statement)은 표현식(Expresstion)처럼 동작할 수 없다.
표현식은 값 하나로 귀결되는 자바스크립트 코드 조각(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)은 다음과 같다.
브라우저의 콘솔에 다음과 같은 코드를 치고 엔터를 치면 어떻게 될까?
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
으로 강제 형변환되어 피연산자로 사용된다.