본 시리즈는 모던 자바스크립트 Deep Dive 책을 참고하여 작성하고 있습니다.
표현식과 문
내용을 시작하기 전에, 몇 가지 용어에 대한 정의를 내리려고 한다.
- 값: 표현식이 평가되어 생성된 결과
- 리터럴: 사람이 이해 가능한 문자나 기호를 사용해 값을 생성하는 표기법
- 표현식: 값으로 평가될 수 있는 문
- 평가(evaluation): 식을 해석해서 값을 생성하거나 참조하는 것
- 문(statement)
- 프로그램을 구성하는 기본 단위이자 최소 실행 단위
- 명령문이라고도 부름 (컴퓨터에 내리는 명령)
- 문이 실행되면 명령이 실행됨
- 선언문, 할당문, 조건문, 반복문 등
값은 어떤 식이 평가되어 생성된 결과이다. 예를 들어, 10 + 20
이라는 식은 평가되어 숫자 값 30을 생성한다. 모든 값은 데이터 타입을 가지고, 메모리에 2진수의 나열로 저장된다.
값을 생성하는 방법에는 여러가지가 있다.
- 식으로 생성하기 (ex.
10 + 20
) - 표현식으로 생성하기 (ex.
sum !== 10
) - 리터럴로 생성하기 (ex.
'hello'
) - ...
리터럴
리터럴은 사람이 이해 가능한 문자나 약속된 기호를 사용해 값을 생성하는 표기법이다. 사람이 이해할 수 있는 문자나 기호에는 아라비아 숫자, 알파벳, 한글, ‘’
, “”
, .
, []
, \{}
등이 있다. 자바스크립트 엔진은 코드가 실행되는 시점인 런타임에 리터럴을 평가해 값을 생성한다.
정수 리터럴 | 100 |
부동소수점 리터럴 | 10.5 |
2진수 리터럴 | 0b010001 |
문자열 리터럴 | "hello” |
불리언 리터럴 | true |
null 리터럴 | null |
객체 리터럴 | {name: ‘Lee’, address: ‘Seoul’} |
배열 리터럴 | [ 1, 2, 3 ] |
함수 리터럴 | function() {} |
표현식
표현식은 값으로 평가될 수 있는 문이다. 다시 말해, 값으로 평가될 수 있는 문이면 모두 표현식이다. 표현식이 평가되면 새로운 값이 생성되거나 기존의 값을 참조한다. 리터럴도 값으로 평가되는 문이므로, 표현식이다. 이때, 표현식과 표현식이 평가된 값은 동치이다. 따라서 값이 위치할 수 있는 자리에는 표현식도 위치할 수 있고, 표현식이 다른 표현식의 일부가 되어 새로운 값을 만들 수도 있다.
1 100; // 리터럴 표현식2 50 + 50; // 연산자 표현식34 var score; // *-- 변수 선언문은 값으로 평가될 수 없으므로 표현식이 아니다56 score = 50 + 50; // 할당문 (표현식)7 score // 식별자 표현식89 result = score * 500;10 // 식별자 표현식 score는 100으로 평가됨11 // result -> 50000
세미콜론과 세미콜론 자동 삽입 기능(ASI)
세미콜론(;)은 문의 종료를 의미하고, 자바스크립트 엔진은 세미콜론을 기준으로 문이 종료되는 위치를 파악한다. 예외로 코드 블록 \{ ... }
은 그 자체로 종결성을 가지기 때문에 세미콜론을 붙이지 않는다. 그런데 자바스크립트에서 세미콜론을 붙이는 것은 옵션이고, 생략 가능하다. 자바스크립트 엔진은 소스코드를 해석하면서 문의 끝이라고 예측되는 곳에 세미콜론을 자동으로 붙여준다. 이 기능을 세미콜론 자동 삽입 기능(Automatic Semicolon Insertion)이라고 한다. 그러나 ASI가 개발자가 의도한 대로 동작하는 경우가 있다.
1 var bar = function () \{}2 (function() \{})();3 // ASI 동작 결과 => var bar = function () \{}(function() \{})();4 // 예측 => var bar = function() \{}; (function() \{})();
이러한 문제를 방지하기 위해 세미콜론을 붙여야 한다는 주장이 다수이다.
표현식인 문과 표현식이 아닌 문
표현식은 문 그 자체가 될 수도 있고, 문의 일부가 될 수도 있다.
1 // var x; 처럼 변수 선언문은 표현식이 아니다2 // x = 2; 처럼 할당문은 값으로 평가될 수 있으므로 표현식이다3 // 따라서 아래 문의 일부만 표현식이다4 var x = 2;56 // 할당문은 표현식이다. 아래 문은 문이면서 표현식이다7 x = 3 + 5;
표현식인 문과 표현식이 아닌 문을 구분하는 방법은 변수에 할당해 보는 것이다. 표현식은 값으로 평가될 수 있는 문이고, 변수는 하나의 값을 저장하기 위해 확보한 메모리 공간이기 때문에, 어떤 문을 변수에 할당할 수 있다는 것은 그 문의 평가 결과가 값이라는 것이고, 즉 그 문은 표현식이다.
1 var foo = var x; // Uncaught SyntaxError: Unexpected token 'var'2 var bar = (x = 3 + 5);3 console.log(var) // 8
크롬 개발자 도구에서는 표현식이 아닌 문을 실행하면 언제나 undefined
를 출력하고, 이를 완료 값이라고 한다. 완료 값은 평가 결과가 아니므로 이 값을 변수에 할당하거나 참조할 수 없다. 표현식인 문을 실행하면 그 식이 평가된 결과를 반환한다.
연산자
- 연산자(operator): 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입, 지수 연산 등을 수행해 하나의 값을 만듦
- 피연산자(operand): 연산의 대상, 값으로 평가될 수 있는 표현식이어야 함
- 연산자 표현식: 피연산자와 연산자의 조합으로 이루어진 식. 평가될 수 있는 표현식이다.
산술 연산자
- 피연산자를 대상으로 수학적 계산 수행 후 새로운 숫자 값 생성
- 산술 연산 불가능할 경우
NaN
값을 반환한다.
1 // 이항 산술 연산자 - 피연산자가 2개 이상2 5 + 23 5 - 24 5 * 25 5 / 26 5 % 278 // 단항 산술 연산자 - 피연산자가 1개9 var x = 1;1011 // -- 부수 효과가 있는 단항 산술 연산자12 x++; // 선할당 후증가13 x--; // 선할당 후감소14 ++x; // 선증가 후할당15 --x; // 선감소 후할당1617 // -- 부수 효과가 없는 단항 산술 연산자18 +x; // 피연산자를 숫자 타입으로 변환19 -x; // 양수를 음수로, 음수를 양수로 반전하여 반환
문자열 연결 연산자: 피연산자 중 하나 이상이 문자열인 경우, 문자열로 연결되어 반환된다.
1 '1' + 2; // '12'2 1 + '2'; // '12'34 // 암묵적 타입 변환 - 타입 강제 변환5 1 + true // 26 1 + false // 17 1 + null // 18 +undefined // NaN9 1 + undefined // NaN
할당 연산자
- 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당
- 할당문은 할당된 값으로 평가된다.
1 var x;23 x = 10;4 x += 5;5 x -= 5;6 x *= 5;7 x /= 5;8 x %= 5;9 x += 'hi'; // '0hi'
비교 연산자
- 좌항, 우항 비교 후 그 결과를 불리언 값으로 반환
동등/일치 비교
1 var x = 2;2 var y = 3;34 x == y; // x와 y의 값이 같은가?5 x === y; // x와 y의 값과 타입이 같은가?6 x != y; // x와 y의 값이 다른가?7 x !== y; // x와 y의 값과 타입이 다른가?
값만 비교할 때는 암묵적으로 타입이 자동 변환으로 타입을 일치시킨 후에 같은 값인지 비교한다. 이를 **동등 비교(==)**라고 한다. 따라서 5 == '5'
는 참이고, 5 === '5'
는 거짓이다. 그런데 동등 비교는 결과를 예측하기 어렵기 때문에 사용하지 않는 편이 좋다.
1 '0' == '' // false2 0 == '' // true3 0 == '0' // true4 0 == '0' // true5 false == 'false' // false6 false == '0' // true7 false == null // false8 false == undefined // false
일치 비교(===) 연산자는 좌항과 우항이 타입도 같고 값도 같아야 true를 반환한다. 그리고 암묵적 타입 변환을 하지 않는다.
1 5 === 5; // true2 5 === '5'; // false
동등 비교 연산자 주의
1 NaN === NaN; // false2 // --- NaN 검사를 위해서는 isNaN()을 사용해야 한다34 ****0 === -0 // true5 0 == -0 // true67 -0 === +0; // true8 Object.is(-0, +0); // false910 NaN === NaN;11 Object.is(NaN, NaN); // true
대소 관계 비교
1 5 > 0 // true2 5 > 5 // false3 5 >= 5 // true4 5 <= 5 // true
삼항 조건 연산자
조건식 ? 조건식이 true일 때 반환할 값 : 조건식이 false일 때 반환할 값
- 삼한 조건 연산자가 사용된 문은 평가되어 두 번째 피연산자나 세 번째 피연산자를 반환 ⇒ 값처럼 사용
- 삼항 조건 연산자 표현식은 값으로 평가할 수 있는 표현식인 문
1 var x = 2;23 console.log(x % 2 ? '홀수' : '짝수')
논리 연산자
우항과 좌항의 피연산자를 논리 연산
||
: 논리합 (OR)&&:
논리곱 (AND)!
: 부정 (NOT) ⇒ 우항의 피연산자를 논리 연산
1 true || true // true2 true || false // true3 false || true // true4 false || false // false56 true && true // true7 true && false // false8 false && true // false9 false && false // false1011 'cat' && 'dog' // 'dog' -- 모두 참일 때, 마지막 피연산자를 반환12 'cat' || 'dog' // 'cat' -- 좌항부터 확인해서 처음으로 참인 피연산자를 반환
!
연산자는 언제나 불리언 값을 반환한다. 피연산자는 불리언 값일 필요는 없다. 암묵적 타입 변환을 수행한다.||
,&&
연산자의 평가 결과는 불리언이 아닐 수 있다.
쉼표 연산자
- 왼쪽 피연산자부터 차례대로 피연산자를 평가
- 마지막 피연산자의 평가 결과를 반환
1 var x, y, z;2 x = 1, y = 2, z = 3; // 3
그룹 연산자
- 소괄호로 피연산자를 묶는다. 연산자 우선순위가 가장 높기 때문에, 감싸고 있는 피연산자들을 가장 먼저 평가한다.
1 10 * (2 + 3) // 502 10 * 2 + 3 // 23
typeof 연산자
- 피연산자 데이터 타입을 문자열로 반환
string, number, boolean, undefined, symbol, object, function
중 하나를 반환typeof null // => object
지수 연산자
(ES7 도입)
1 2 ** 3 // 82 2 ** 2.5 // 2^(2.5) => 5.6568...3 2 ** 0 // 14 2 ** -2 // 2^(-2) => 1/4 => 0.2556 Math.pow(2, 3); // 지수 연산자 도입되기 전에 사용, 8 반환
그 외 연산자
1 var object = \{ name: "yj", number: 33, dog: \{ name: "jj" } }2 var object_2 = \{ number: 34 }34 // 옵셔널 체이닝 연산자5 console.log(object_2.dog?.name) // "yj"6 console.log(object_2.dog.name) // Uncaught TypeError: Cannot read properties of undefined (reading 'name')78 // null 병합 연산자9 console.log(object_2.dog); // undefined10 console.log(object_2.dog ?? \{ name: "kk" }); // \{ name: "kk" }1112 // 프로퍼티 삭제 연산자13 console.log(object); // \{ name: "yj", number: 33, dog: \{ name: "jj" } }\14 delete object.number;15 console.log(object); // \{name: 'yj', dog: \{…}}1617 // new 연산자18 var fruits = new Array('사과', '바나나')19 console.log(fruits); // ['사과', '바나나']2021 // 좌변 객체가 우변 생성자 함수와 연결된 인스턴스인지 판별22 console.log(fruits instanceof Array) // true2324 // 프로퍼티 존재 확인25 console.log('name' in object) // true26 console.log('name' in object_2) // false
제어문
블록문
- == 코드 블록 == 블록
- 0개 이상의 문을 중괄호로 묶은 것
1 \{2 var foo = 1;3 foo++;4 }56 if (true) \{7 console.log("hi");8 }
조건문
- 조건식의 평가 결과에 따라 코드 블록의 실행을 결정
- 조건식: 불리언 값으로 평가될 수 있는 표현식
if ... else
1 if (조건식1) \{2 // 조건식 1이 true3 } else if (조건식2) \{4 // 조건식 2가 true5 } else \{6 // 모두 false7 }
삼항연산자로 바꿔보기
1 var num = 2;23 if (num > 0) \{4 console.log("양수");5 } else \{6 console.log("양수가 아님");7 }89 num > 0 ? console.log("양수") : console.log("양수가 아님");
switch
1 switch (표현식) \{2 case 표현식1:3 // 표현식 == 표현식14 break;5 case 표현식2:6 // 표현식 == 표현식27 break;8 default:9 // 해당되는 case 없을 경우 실행10 }
- 폴스루(fall through): 표현식 평가 결과와 일치하는 case 문으로 이동해 문을 실행했지만, switch 문을 탈출하지 않고 이후 모든 case와 default 문 실행 → break 문을 사용해야 함
12 var year = 2022;3 var month = 2;4 var days;56 switch (month) \{7 case 1: case 3: case 5: case 7: case 8: case 10: case 12:8 days = 31;9 break;10 case 4: case 6: case 9: case 11:11 days = 30;12 break;13 case 2:14 days = getFebDays(year);15 break;16 default:17 console.log('Invalid month');18 }1920 console.log("days");
반복문
- 조건식 평가 결과가 참인 경우 코드 블록 실행
- 실행 후 조건식 다시 평가 후 참이면 다시 코드 블록 실행
for 문
1 for (변수 선언문 혹은 할당문; 조건식; 증감식) \{2 ...3 }45 for (var i = 1; i >= 0; i--) \{6 console.log(i);7 }89 for (;;) \{10 // 무한루프11 }
while 문
1 var count = 0;23 while (count < 3) \{4 console.log(count);5 count++;6 }78 while (true) \{9 // 무한루프10 }
do...while 문
1 var count = 0;23 // 코드 블록 한 번 실행 후, count가 3보다 작을 때까지 코드 블록 계속 반복 실행4 do \{5 console.log(count);6 count++;7 } while (count < 3);
break 문
- 레이블 문, 반복문, switch 문의 코드 블록을 탈출한다.
- 그 외의 블록에서
break
문을 사용하면 SyntaxError
레이블 문
1foo: \{2 console.log(1);3 break foo;4 console.log(2);5}67console.log('hi');89// 실행 결과: 1, hi
continue 문
- 반복문 코드 블록 실행을 현 시점에서 중단
- 반복문 증감식으로 실행 흐름 이동
1for (var i = 0; i < 10; i++) \{2 if (i < 5) continue;3 console.log(i);4}5// 5 6 7 8 9