자바스크립트 코드는 결국 문(statement)의 나열이다
자바스크립트 프로그램은 결국 문(statement) 들이 줄지어 있는 것이다. 엔진은 이 문들을 위에서 아래로 차례대로 실행한다. 변수를 선언하는 것도 문이고, 함수를 호출하는 것도 문이며, if나 for 블록도 하나의 문이다. 그리고 이 문들은 세미콜론으로 구분된다.
문장 세 개, 세미콜론 세 개. 엔진은 첫 번째를 실행하고, 이어서 두 번째, 그리고 세 번째를 실행합니다. 이 정도면 충분히 단순하죠.
파서 입장에서는 공백이나 들여쓰기가 아무런 의미가 없습니다. 세 줄을 한 줄에 몰아넣고 사이사이 세미콜론만 찍어도 똑같이 동작해요. 들여쓰기는 엔진이 아니라 사람이 읽기 편하라고 하는 겁니다.
블록으로 문장 묶기
중괄호 { }는 _블록_을 만듭니다. 여러 문장을 하나의 단위로 묶어서 엔진이 함께 처리하도록 하는 거죠. 함수 본문, if문의 분기, 반복문 본문 등 블록은 어디서나 만나게 됩니다.
중괄호 안에 있는 두 개의 console.log 호출이 if의 본문입니다. 닫는 } 뒤에 세미콜론이 없다는 점에 주목하세요. 블록은 종결자가 필요한 statement가 아니기 때문이죠. if 문 전체는 블록이 끝나는 지점에서 함께 끝납니다.
이 부분은 모든 } 뒤에 ;를 붙여야 하는 언어에서 넘어온 분들이 자주 헷갈려하는 지점입니다. 자바스크립트에서는 단독 블록이나 제어 흐름 블록 뒤에 세미콜론을 붙이지 않습니다.
표현식(expression)과 문(statement)
자바스크립트 문법 기초에서 일찌감치 짚고 넘어가면 좋은 구분이 하나 있습니다. 표현식(expression) 은 값을 만들어 내고, 문(statement) 은 어떤 동작을 수행합니다.
2 + 3은 표현식입니다. 평가되면5가 됩니다.let x = 2 + 3;은 문입니다. 변수를 선언하고 표현식의 결과를 할당하죠.console.log("hi")는 표현식입니다(함수 호출은undefined를 반환합니다). 하지만 한 줄에 세미콜론을 붙여 쓰면 표현식 문(expression statement) 이 됩니다.
대부분의 경우엔 이걸 딱히 신경 쓸 일이 없습니다. 다만 화살표 함수나 삼항 연산자를 쓸 때, 또는 자바스크립트가 표현식(expression)을 기대하는 자리에 문(statement)을 넣어버렸거나 반대의 상황이 생겼을 때 비로소 문제가 됩니다.
자바스크립트 세미콜론, 꼭 써야 할까?
솔직하게 답하자면 이렇습니다. 자바스크립트에는 자동 세미콜론 삽입(Automatic Semicolon Insertion, 줄여서 ASI)이라는 기능이 있어서, 대부분의 줄바꿈 지점에서 빠진 세미콜론을 엔진이 알아서 넣어줍니다. 그래서 아래 같은 코드도 문제없이 돌아가죠:
세미콜론 없이 써도 에러가 나지 않는다. ASI는 줄바꿈마다 "다음 토큰이 지금 문장을 이어갈 수 있는가?"를 따져보고, 그럴 수 없다고 판단되면 세미콜론을 자동으로 끼워 넣는다.
이 자바스크립트 ASI(automatic semicolon insertion) 덕분에 현업에서는 두 가지 스타일이 공존한다.
- 세미콜론을 항상 쓰는 스타일. 대부분의 코드베이스, 튜토리얼, 스타일 가이드가 이쪽이다.
- 세미콜론을 쓰지 않는 스타일. 일부 모던 프로젝트(Standard 스타일, 일부 React 코드베이스)에서 쓴다. ASI에 의존하면서 몇 가지 방어적 트릭을 곁들인다.
둘 다 잘 돌아간다. 어느 쪽도 "틀린" 건 아니다. 진짜 문제는 일관성이 없는 경우다. 한 파일 안에서 두 스타일을 섞어 쓰면 버그를 찾기가 훨씬 까다로워진다.
세미콜론 자동 삽입이 물어버리는 순간
ASI는 99% 정도는 우리가 기대한 대로 동작한다. 문제는 나머지 1%, 바로 앞 줄에 이어질 수 있는 토큰으로 시작하는 줄이다. 대표적인 말썽쟁이는 [, (, `, +, -, /이다.
아래 코드를 보자.
x와 y가 서로 바뀌길 기대하겠지만, 실제로는 그렇게 동작하지 않습니다. ASI가 [x, y] 앞에 세미콜론을 넣어주지 않는데, 이유는 10[x, y]가 문법적으로 유효한 표현(숫자 10에 대한 인덱싱)이기 때문입니다. 파서는 2번째 줄과 3번째 줄을 하나의 커다란 표현식으로 읽어버리고, 결국 런타임 에러가 나거나 이상한 결과가 출력됩니다.
해결 방법은 2번째 줄 끝에 세미콜론을 붙이는 것입니다:
혹은 세미콜론을 생략하는 스타일로 작성한다면, 문제가 될 만한 줄의 맨 앞에 세미콜론을 붙여주면 됩니다:
맨 앞에 붙은 ;는 세미콜론을 생략하는 코드베이스에서 쓰는 방어 패턴이다. 왜 붙어 있는지 모르면 어색해 보이지만, 이유를 알면 납득이 된다.
return 함정
ASI 케이스 중에서도 꼭 기억해둬야 할 게 하나 있는데, 조용히 버그를 만들어내기 때문이다. return 바로 뒤에서 줄바꿈을 하면 ASI가 거기에 세미콜론을 끼워 넣어 버려서, 함수는 undefined를 반환하게 된다:
작성자 의도는 객체를 반환하는 것이었습니다. 그런데 ASI(자동 세미콜론 삽입)가 줄 끝의 return을 보고 그 자리에서 문장을 끝내버렸죠. 결과적으로 객체 리터럴은 실행될 일이 없는 죽은 코드가 되어버립니다.
해결책은 간단합니다. 값을 return과 같은 줄에서 시작하면 됩니다:
throw, break, continue, yield도 마찬가지예요. 키워드와 값 사이에 줄바꿈을 넣으면 안 됩니다.
간단하지만 확실한 규칙
자바스크립트 문법 기초를 다지는 중이라면, 가장 편한 방법은 다음과 같습니다.
- 모든 문장 끝에 세미콜론을 명시적으로 붙이세요.
if,for,while, 함수 선언, 클래스 본문처럼 블록을 닫는}뒤에는 붙이지 마세요.- 객체 리터럴이나 변수에 할당한 함수 _표현식_처럼 값으로 끝나는
}뒤에는 붙이세요:const f = function() {};. - 포매터(Prettier, 스타일 룰을 적용한 ESLint)를 사용하고 더는 신경 쓰지 마세요. 팀에서 정한 규칙을 포매터가 알아서 지켜줍니다.
이 규칙이 절대적인 법칙은 아니지만, 여러분이 앞으로 읽게 될 대부분의 자바스크립트 코드에서 통용되는 관례입니다. 특별한 이유가 없다면 이 관례를 따르는 편이 좋습니다.
대소문자 구분과 식별자 규칙
여기서 함께 짚고 넘어갈 작은 규칙이 두 가지 있습니다.
- 자바스크립트는 대소문자를 구분합니다. 즉,
userName,username,UserName은 서로 전혀 다른 식별자입니다. - 식별자에는 문자, 숫자,
_,$를 쓸 수 있지만 숫자로 시작할 수는 없습니다. 또한class,return,function같은 예약어는 식별자로 쓸 수 없습니다.
$는 문법적으로 문제없지만, 관례상 라이브러리에서 예약어처럼 쓰는 경우가 많습니다. 과거에는 jQuery가 대표적이었고, 지금도 일부 템플릿 엔진이 이걸 사용합니다. 그래서 개발자가 직접 $를 변수명으로 쓰는 일은 거의 없죠.
다음: strict 모드
요즘 자바스크립트는 _strict 모드_라고 부르는 조금 더 엄격한 방식으로 동작합니다. 예전에는 그냥 넘어가던 허술한 동작들을 실제 에러로 바꿔주는 모드죠. ES 모듈이나 클래스 내부는 기본적으로 strict 모드로 실행되는데, 구체적으로 뭐가 달라지는지는 알아둘 가치가 있습니다. 자세한 내용은 다음 페이지에서 다룹니다.
자주 묻는 질문
자바스크립트에서 세미콜론은 꼭 써야 하나요?
엄밀히 말하면 안 써도 됩니다. 자바스크립트에는 ASI(자동 세미콜론 삽입) 기능이 있어서 줄바꿈 대부분에서 알아서 세미콜론을 채워 넣거든요. 다만 실무에서는 여전히 명시적으로 찍는 팀이 많습니다. [, (, `, +, -, / 로 시작하는 줄처럼 ASI가 엉뚱하게 동작하는 케이스가 몇 군데 있기 때문이죠. 스타일을 하나 정하고 Prettier 같은 포매터에 맡기는 게 깔끔합니다.
자동 세미콜론 삽입(ASI)이 뭔가요?
ASI는 다음 토큰이 현재 문장을 이어갈 수 없을 때 자바스크립트가 줄 끝에 세미콜론을 자동으로 넣어주는 규칙입니다. let x = 1 뒤에 세미콜론이 없어도 잘 돌아가는 이유가 이것이죠. 문제는 어떤 줄이 앞 줄을 이어갈 수 있는 문자([나 ( 같은 것)로 시작할 때입니다. 이때는 자바스크립트가 조용히 두 줄을 하나로 붙여버려서 의도와 전혀 다른 코드가 됩니다.
자바스크립트 문장(statement)의 기본 문법은 어떻게 되나요?
문장은 하나의 명령 단위입니다. 변수 선언, 함수 호출, if 블록, 반복문 같은 것들이죠. 문장과 문장 사이는 세미콜론(직접 찍든, ASI가 넣어주든)으로 구분하고, 여러 문장을 묶을 때는 { } 블록 안에 넣습니다. 공백과 들여쓰기는 파서가 전혀 신경 쓰지 않기 때문에, 포매팅은 엔진이 아니라 사람을 위한 장치라고 보면 됩니다.