제어문이란 코드의 실행 흐름을 인위적으로 제어하는 문이다.
함수형 프로그래밍에서는 forEach, map, filter, reduce와 같은 고차 함수를 활용하여 제어문의 사용을 억제하려고 노력한다.
주어진 조건식의 평가 결과에 따라 코드 블록(블록문)의 실행을 결정하는 문
값처럼 사용할 수 없는 코드 블록이기 때문에 변수에 할당할 수 없다.
if-else문과 똑같은 역할을 하지만, 값처럼 변수에 할당할 수 있다.
var kind = num ? (num > 0 ? '양수' : '음수') : '영';
조건식이 boolean 값으로 평가되어야 한다.
조건식이 boolean 값보다 문자열이나 숫자인 경우가 많다.
하지만 이 경우 자바스크립트의 암묵적 타입 변환에 의해 boolean 값으로 바뀌어 평가된다.
논리적 참/거짓(if-else문)보다 다양한 상황(case)에 따라 실행할 코드 블록을 결정할 때 사용한다.
암묵적 타입 변환
조건식에 boolean이 아닌 값이 들어올 경우, 자바스크립트 엔진에 의해 암묵적으로 boolean 값으로 강제 변환되는 것
폴스루(fall through)란?
switch문의 각 case문에 break 키워드를 넣어주지 않아서 해당 코드 블록을 탈출하지 않고 다음 case문을 연이어 실행하는 현상
=> switch문의 각 case문에는 반드시 break 키워드가 있어야 한다.
(default는 모든 case문을 실행하지 않았을 때 실행되고 switch문을 종료하므로 따로 break문이 필요 없음)
일반적으로는 switch문보다 if-else문이 더 좋지만, 조건이 너무 많아서 if-else문이 오히려 가독성이 떨어진다면 switch문을 사용하는 편이 좋다.
조건문의 평가 결과가 true인 경우 코드 블록을 실행하고, 조건문이 false가 될 때까지 반복하는 문
반복 횟수가 명확할 때 주로 사용
반복 횟수가 명확하지 않을 때 주로 사용
while문은 조건문이 true이면 무한루프가 되는데, 이를 방지하기 위해
if문으로 탈출 조건을 만들고 if문 내의 break를 통해 while문을 탈출한다.
반복문의 실행을 continue 실행 지점에서 중단한 후, 반복문의 증감식으로 이동한다.
for (let i = 0; i < array.length; i++) {
// 조건이 true이면 현 지점에서 실행을 중단하고 반복문의 증감식으로 이동한다.
if (arr[i] !== 'l') continue;
count++; // continue문이 실행되면 이 문은 실행되지 않는다.
}
위의 코드처럼 실행문이 한 줄일 경우에는 continue를 쓰지 않은 다음 코드가 더 간결하다.
for (let i = 0; i < arr.length; i++) {
// 단순히 'l'이면 카운트를 증가시킨다.
if (arr[i] === 'l') count++;
}
하지만 실행문이 여러 줄일 경우(count++ 외에 추가로 더 실행할 코드가 있는 경우), continue문을 사용하면 if문 바깥에 추가 실행 코드를 작성할 수 있다.
// continue 사용 x
for (let i = 0; i < array.length; i++) {
// 'l'이면 카운트를 증가시킨다.
if (arr[i] === 'l') {
count++;
// 추가 실행 코드
}
}
// continue 사용
for (let i = 0; i < array.length; i++) {
// 'l'이 아니면 아래를 실행하지 않고 바로 다음 증감식으로 이동한다.
if (arr[i] !== 'l') continue;
count++; // continue에 걸리면 여기부터는 실행하지 않는다.
// 추가 실행 코드
}
값의 타입을 다른 타입으로 변환하는 것
개발자가 의도적으로 값의 타입을 변환하는 것
let x = 10;
let str = x.toString();
개발자의 의도와 상관없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환되는 것
let x = 10;
let str = x + '';
타입 변환 둘 다 기존 x 변수의 값을 변경하는 것은 아니다.
원시 값은 변경 불가능(immutable)한 값이므로 변경할 수 없다.
타입 변환은 기존의 원시 값을 사용해 다른 타입의 새로운 원시 값을 생성한다.
자바스크립트 엔진은 이렇게 타입 변환된 값은 단 한 번 사용하고 버린다. (= 가비지 콜렉터에 의해 메모리에서 해제)
1 * '10' // -> 10
1 / 'one' // -> NaN
+null // -> 0
+[] // -> 0
+'' // -> 0
+false // -> 0
+undefined // -> NaN
+{} // -> NaN
+[10, 20] // -> NaN
자바스크립트 엔진은 boolean 값이 아닌 값을 불리언으로 평가할 때 Truthy(참으로 평가되는 값)와 Falsy(거짓으로 평가되는 값)으로 구분한다.
Falsy 외의 모든 값은 Truthy이다.
// Falsy 값 종류
false
undefined
null
0, -0
NaN
'' (빈 문자열)
표현식을 평가하는 도중에 평가 결과가 확정된 경우 나머지 평가 결과를 생략하는 것
단축 평가를 하는 방법에는 논리 연산자, 옵셔널 체이닝 연산자, null 병합 연산자를 활용하는 방법이 있다.
종류: 논리곱 연산자(&&)와 논리합 연산자(||)
연산의 반환값으로 논리 연산의 결과를 결정하는 피연산자를 반환
'Cat' && 'Dog' // -> "Dog"
&&은 피연산자가 둘 다 참이어야 true를 반환한다.
즉, 앞이 참이어도 뒤가 거짓이면 false 처리가 되기 때문에,
'논리 연산의 결과를 결정하는 피연산자가 뒤에 있다' 고 보고
반환값으로 뒤의 피연산자를 반환한다.
'Cat' || 'Dog' // -> "Cat"
||은 피연산자 중 하나만 참이어도 true를 반환한다.
즉, 앞이 참이면 뒤는 볼 필요없이 전체 연산 결과는 true 처리가 되기 때문에,
'논리 연산의 결과를 결정하는 피연산자가 앞에 있다' 고 보고
반환값으로 앞의 피연산자를 반환한다.
true || anything // -> true
false || anything // -> anything
if (done) message = '완료';
message = done && '완료';
if (!done) message = '진행중';
message = done || '진행중';
if (done) message = '완료';
else message = '진행중';
message = done ? '완료' : '진행중';
let element = null;
let value = element.value; // TypeError: Cannot read property 'value' of null
위의 기존 코드는 null인 객체에서 property를 참조하려 했기 때문에 에러가 발생했다.
이를 단축 평가를 사용하여 작성하면 다음과 같다.
let element = null;
let value = element && element.value; // null
단축 평가의 논리 연산자 &&을 사용하면, element.value 참조 시 element의 상태인 null을 그대로 출력하고 에러를 발생시키지 않는다.
function getStringLength(str) {
str = str || ''; // 초기값 설정
return str.length;
}
console.log(getStringLength()); // 0
console.log(getStringLength('hi')); // 2
위의 코드는 함수 내부에서 매개변수 str의 기본값을 단축 평가의 논리 연산자 ||를 통해 설정하고 있다.
이를 ES6 문법으로 더 간결하게 표현하면 다음과 같다.
function getStringLength(str = '') {
return str.length;
}
console.log(getStringLength()); // 0
console.log(getStringLength('hi')); // 2
똑같이 에러 없이 결과가 잘 출력되며, 함수 내부의 코드 한 줄이 줄어들었다.
논리 연산자 &&은 좌항의 피연산자가 Falsy 값이면 && 뒤를 더 이상 참조하지 않고 좌항 피연산자를 그대로 반환해버린다.
let str = '';
let length = str && str.length;
console.log(length); // '' (우항의 str.length를 참조하지 못한다)
위의 코드에서 빈 문자열의 길이는 0으로 반환해야 의미적으로 더 맞지만, 논리 연산자 &&은 피연산자의 값이 Falsy로 인식되면 그 피연산자를 그대로 반환해버리고 뒤의 연산은 더 이상 진행하지 않는다.
반면 옵셔널 체이닝 연산자는 좌항의 피연산자가 Falsy 값이더라도 null이나 undefined만 아니면 뒤의 연산을 계속 한다.
let str = '';
let length = str?.length;
console.log(length); // 0 (빈 문자열의 길이 반환)
str이 null이나 undefined이면 undefined를 반환하고,
그 외의 경우(빈 문자열, 빈 배열 등)에는 연산자 뒤의 연산을 계속 수행한다.
따라서 위의 코드의 경우 해당 객체의 길이를 반환하려는 원래의 목적대로 연산이 잘 수행되었다.
let foo = null ?? 'default string';
console.log(foo); // default string (좌항이 null인 경우 우항의 피연산자 반환)
논리 연산자 ||은 좌항의 피연산자가 Falsy 값(빈 문자열, 빈 배열 등)이면 우항의 피연산자를 반환한다. 이때 이 Falsy 값을 기본값으로 사용하려는 의도로 코드를 작성했다면 의도와 다르게 동작하게 된다.
let foo = '' || 'default string';
console.log(foo); // default string
하지만 null 병합 연산자는 좌항의 피연산자가 null이나 undefined만 아니면, Falsy 값이더라도 그대로 좌항의 피연산자를 반환한다. 이렇게 되면 좌항의 피연산자를 변수의 기본값으로 쓸 수 있게 된다.
let foo = '' ?? 'default string';
console.log(foo); // ''