5장. 문
5.1 표현문
표현식: 평가를 통해 값으로 바뀌는 식
문: 실행을 통해 특정 동작을 수행
할당이나 함수 호출처럼 부수 효과가 있는 표현식은 그 자체로 '문'이 될 수 있음.
5.2 복합문과 빈 문
{
x = Math.PI;
cx = Math.cos(x);
console.log("cos(π) = " +cx);
}
1. 블록은 세미콜론으로 끝나지 않는다. 블록 안에 있는 기본 문은 세미콜론으로 끝나지만 블록 자체는 그렇지 않다.
2. 블록에 들어 있는 행은 자신을 감싼 중괄호를 기준으로 들여 쓴다.
자바스크립트 문법은 공식적으로 단일 하위 문을 허용한다. 예를 들어 while 루프 문법은 루프 바디로 기능하는 단일한 문을 포함한다.
문 블록을 사용하면 허용되는 하위 문 하나에 문 여러개를 넣을 수 있다.
복합문은 자바스크립트가 문 하나를 예상하는 곳에 문 여러 개를 넣을 때 사용한다. 빈 문은 반대다. 문이 있을 것으로 예상되는 곳에 문을 쓰지 않을 수도 있다. 빈 문은 빈 바디를 갖는 루프를 만들고자 할 때 유용하다.
// 배열 a를 초기화한다.
const a = ["one", "two", "three"]
for(let i = 0; i < a.length; a[i++] = 0) ; // [0, 0, 0]
for(let i = 0; i < a.length; a[i++] = "t") ; // ['t', 't', 't']
의도적으로 빈 문을 사용할 때는 목적을 가지고 그렇게 했다는 것이 명확하게 드러나도록 주석을 쓰는 것이 좋다.
for(let i = 0; i < a.length; a[i++] = 0) /* 의도적으로 비움. */ ;
5.3 조건문
조건문은 코드가 두 개 이상의 경로로 갈라지는 분기점(branch)이며 인터프리터는 반드시 그 경로 중 하나를 선택해야 한다.
5.3.3 switch
if 문은 프로그램 실행 흐름에 분기점을 만들고, else if를 써서 분기점을 여럿 만들 수 있다.
하지만 모든 분기점이 같은 표현식의 값에 좌우된다면 else if가 최선의 선택은 아니다.
똑같은 표현식을 여러 if 문이 반복해 평가하는 것은 낭비다.
switch 문으로 똑같은 상황에 대응할 수 있다.
switch 문은 switch 키워드 뒤에 괄호로 둘러싼 표현식을 쓰고 그 뒤에 중괄호로 감싼 코드 블록을 쓰는 형태다.
switch {
statements
}
코드 블록 곳곳에 case 키워드를 쓰고 그 뒤에 표현식과 콜론을 붙일 수 있다.
1. switch 문이 실행되면 먼저 expression의 값을 계산한 후,
case 라벨의 표현식 중에서 expression과 같은 값으로 평가되는 것을 찾는다.
(이 때 '같은' 같은 === 연산자를 기준으로 판단한다.)
2. 일치하는 것을 찾으면 해당 case의 코드 블록을 실행한다.
3. 일치하는 case를 찾지 못하면 'default:' 라벨을 찾는다.
4. 'default:' 라벨조차 없으면 switch 문은 코드 블록 전체를 건너뛴다.
switch(n) {
case 1: // n === 1 이면 여기서 시작
// 코드 블록 #1을 실행한다.
break; // 여기서 멈춘다.
case 2: // n === 2 이면 여기서 시작
// 코드 블록 #2를 실행한다.
break; // 여기서 멈춘다.
case 3: // n === 3 이면 여기서 시작
// 코드 블록 #3을 실행한다.
break; // 여기서 멈춘다.
default: // 전부 실패하면
// 코드 블록 #4를 실행한다.
break; // 여기서 멈춘다.
}
만약 break 문이 없다면 switch 문은 expression의 값과 일치하는 case 라벨을 찾아 코드를 실행한 다음,
블록의 끝에 다다를 때까지 실행을 계속한다.
함수 안에서 switch를 쓸 때는 break 문 대신 return 문을 사용해도 된다.
아래 예제는 입력값을 그 타입에 따라 문자열로 변환한다.
function convert(x) {
switch(typeof x) {
case "number":
return x.toString(16); // 숫자를 16진수로 변환
case "string":
return '"' + x + '"'; // 문자열을 따옴표로 감싸 반환
default:
return String(x); // 다른 타입은 일반적인 방법으로 변환
}
}
switch 문은 switch 키워드 다음에 있는 표현식을 가장 먼저 평가하고,
그 다음에는 일치하는 값을 찾을 때까지 case 표현식을 순서대로 평가한다.
일치하는 case를 찾을 때는 동등 연산자 ==가 아니라 일치 연산자 ===을 사용하므로,
표현식은 반드시 타입 변환을 거치지 않고 일치해야 한다.
switch 문을 실행할 때마다 case 표현식 전체가 평가되는 것은 아니므로,
case 표현식에는 함수 호출이나 할당처럼 부수 효과가 있는 것은 피해야 한다.
가장 안전한 방법은 case 표현식에 일정한 표현식만 쓰는 것이다.
5.4 반복문
반복문: 경로를 자기 자신 쪽으로 구부려, 코드 일부를 반복하는 문. 루프라고도 한다.
루프 변수는 대부분 숫자를 사용하지만 꼭 그래야 하는 것은 아니다.
다음 코드는 for 루프를 써서 연결된 리스트 데이터 구조를 이동하며,
리스트의 마지막 객체(next property가 없는 첫 번째 객체)를 반환한다.
function tail(o) {
for(; o.next; o = o.next) /* 비움 */ ; // 연결된 리스트 o의 마지막을 반환한다.
return o; // o.next가 truthy하면 반환.
}
o에는 어떤 것을 입력해야 한다는 거지??
for/of
이터러블 객체에서 동작한다.
이터러블 객체: 배열, 문자열, 세트, 맵. for/of 루프로 순회할 수 있는 일종의 연속체(sequence) 또는 일련의 요소.
let data = [1, 2, 3, 4, 5, 6, 7, 8, 9], sum = 0;
for(let element of data) {
sum += element;
}
sum // => 45
괄호 안에는 변수 선언이 있고, of 키워드가 있고, 그 뒤에는 data 배열처럼 이터러블 객체로 평가되는 표현식이 있다.
이 코드에서 루프 바디는 data 배열의 각 요소에 대해 한 번씩 실행된다.
루프 바디를 실행하기 전, 배열의 다음 요소가 element 변수에 할당된다. 배열 요소는 첫 번째에서 시작해 마지막으로 순회한다.
배열은 '동적으로' 순회한다. 즉 반복 중간에 배열 자체에 변화가 발생한다면 반복 결과가 바뀌기도 한다.
만약 위 코드의 루프 바디에 data.push(sum) 이라는 행을 추가한다면,
절대 배열의 마지막 요소에 도달할 수 없으므로 무한 루프가 만들어진다.
객체는 (기본적으로) 이터러블이 아니다. 일반적인 객체에 for/of 를 사용하려 하면 런타임 TypeError 가 발생한다.
let o = { x: 1, y: 2, z: 3 };
let keys = "";
for(let k of Object.keys(o)) {
keys += k;
}
keys // => "xyz"
Object.keys()는 객체의 프로퍼티 이름으로 이루어진 배열을 반환하며 배열은 for/of 를 사용할 수 있는 이터러블이다.
이렇게 객체의 키를 순회하는 것은 동적이지 않다. 루프 바디 안에서 객체 o를 변경해도 결과가 달라지지 않는다.
객체의 키가 필요하지 않다면, 다음과 같이 값을 순회할 수도 있다.
let sum = 0;
for(let v of Object.values(o)) {
sum += v;
}
sum // => 6
객체 프로퍼티의 키와 값이 모두 필요하다면 다음과 같이 Object.entries()와 분해 할당을 통해 for/of 를 사용할 수 있다.
let pairs = "";
for(let [k, v] of Object.entries(o)) {
pairs += k + v;
}
pairs // => "x1y2z3"
ES6 이후에는 문자열을 다음과 같이 문자 단위로 순회할 수 있다.
let frequency = {};
for(let letter of "mississippi") {
if (frequency[letter]) { // frequency 에 m 이라는 프로퍼티가 있냐?
frequency[letter]++; // 있으면 그 값에 1을 더해라
} else {
frequency[letter] = 1; // 없으면 1을 할당해라
}
}
frequency // => {m: 1, i: 4, s: 4, p: 2}
문자열은 UTF-16 문자가 아니라 유니코드 코드 포인트로 순회한다.
문자열 "I♥︎🐥"의 length 는 5다. 이모지 문자 두 개가 UTF-16 문자 두 개로 이루어져 있기 때문이다.
하지만 이 문자열을 for/of 로 순회하면 루프 바디는 코드 포인트 "I", "♥︎", "🐥" 에 대해 한 번씩, 총 세 번 실행된다.
for/of 와 세트, 맵
ES6에 내장된 Set와 Map 클래스는 이터러블이다. 세트를 for/of 로 순회하면 루프 바디는 세트의 각 요소에 대해 한 번씩 실행된다. 이를 사용해 문자열에서 각 단어를 중복 없이 출력할 수 있다.
let text = "Na na na na na na na Batman!";
let wordSet = new Set(text.split(" "));
let unique = [];
for(let word of wordSet) {
unique.push(word);
}
unique // => ["Na", "na", "Batman!"]
Map 객체의 이터레이터는 키나 값이 아닌 키-값 쌍을 순회한다. 반복할 때마다 이터레이터는 첫 번째 요소가 '키', 두 번째 요소가 '키에 대응하는 값'인 배열을 반환한다. m이라는 맵에 대해 다음과 같이 키-값 쌍을 순회할 수 있다.
let m = new Map([[1], "one"]);
for(let [key, value] of m) {
key // => 1
value // => "one"
}
for/await를 사용한 비동기 순회
ES2018은 비동기 이터레이터라는 새로운 이터레이터를 도입하면서 비동기 이터레이터와 함께 동작하도록 for/of 루프를 변형한 for/await 루프를 도입했다.
// 스트림에서 비동기적으로 덩어리를 읽고 출력한다.
async function printStream(stream) {
for await (let chunk of stream) {
console.log(chunk);
}
}
이건 아예 이해 안 됨..
5.4.5 for/in
for/of 루프는 of 다음에 이터러블 객체가 와야 하지만 for/in 루프는 in 다음에 어떤 객체든 쓸 수 있다.
for/in 문은 지정된 객체의 프로퍼티 이름을 순회한다.
for (let p in o) {
console.log(o[p]);
}
자바스크립트 인터프리터는 for/in 문을 실행할 때
1. object 표현식을 평가
2. 그 표현식이 null 이나 undefined 로 평가되면 인터프리터는 루프를 건너뛰고 다음 문으로 이동
3. 그렇지 않으면 인터프리터는 객체의 열거 가능한(enumerable) 프로퍼티 각각에 한 번씩 루프 바디를 실행
4. 하지만 각 반복에 앞서 인터프리터는 variable 표현식을 평가하고 그 변수에 프로퍼티 이름(문자열 값)을 할당
for/in 루프의 variable은 왼쪽 값으로 평가될 수만 있다면 어떤 표현식을 써도 된다.
이 표현식은 루프를 반복할 때마다 평가되므로 매번 다른 값이 될 수도 있다.
예를 들어 다음과 같은 코드를 써서 객체 프로퍼티 이름을 배열에 복사할 수 있다.
let o = { x: 1, y: 2, z: 3 }
let a = [], i = 0;
for(a[i++] in o) {} // 빈 것이 맞다.
a // => ['x', 'y', 'z']
배열을 대상으로 작업할 때는 거의 항상 for/in이 아니라 for/of가 맞는 루프다.
for/in 루프는 실제로 객체의 프로퍼티 전체를 열거하지는 않는다.
1. 심벌인 프로퍼티는 열거하지 않는다.
2. 이름이 문자열인 프로퍼티 중에서도 열거 가능한 프로퍼티만 순회한다.
3. 자바스크립트에서 정의하는 내장 메서드는 열거가 불가능하다. 모든 객체에 toString() 메서드가 있지만 for/in 루프는 toString 프로퍼티를 열거 대상으로 간주하지 않는다.
4. 사용자가 직접 정의한 프로퍼티와 메서드는 기본적으로 열거 가능하다.
열거 가능한 '상속된 프로퍼티' 역시 for/in 루프의 순회 대상에 속한다.
따라서 모든 객체에서, 상속하는 프로퍼티를 정의하고 for/in 루프를 사용한다면 결과가 예상과 다를 수 있다.
이 때문에 많은 프로그래머가 for/in 루프 대신 Object.keys()와 for/of 루프를 조합하는 걸 선호한다.
for/in 루프의 바디에서 아직 열거되지 않은 프로퍼티를 삭제한다면 그 프로퍼티는 순회 대상에서 빠진다.
루프 바디에서 객체에 새 프로퍼티를 정의한다면 그 프로퍼티는 순회 대상에 있을 수도 있고 없을 수도 있다.
5.5 점프 문
자바스크립트 인터프리터가 소스 코드의 다른 위치로 이동하게 한다.
break 문은 인터프리터를, 루프를 비롯한 다른 문의 끝으로 이동시킨다.
continue 문은 루프 바디의 나머지를 생략하고 루프 맨 위로 돌아가 새 반복을 시작하게 한다.
return 문은 인터프리터가 함수에서 빠져나와 호출자에게 함수 호출 값을 전달하게 한다.
yield 문은 제너레이터 함수에서 사용하는 return 비슷한 문이다.
throw 문은 예외를 발생시키는(던지는) 문이며 예외 처리를 담당하는 try/catch/finally 문과 함께 사용하도록 설계됐다.
예외가 발생하면 인터프리터는 가장 가까운 예외 핸들러로 점프하는데, 핸들러는 동일한 함수 안에 있을 수도 있고 콜 스택을 거슬러 올라가 호출자에 있을 수도 있다.
5.1.1 라벨 붙은 문
어떤 문이든 그 앞에 다음과 같이 식별자와 콜론을 붙여서 라벨을 만들 수 있다.
identifier: statement
문에 라벨을 붙이는 것은 프로그램 다른 곳에서 참조할 수 있는 이름을 짓는 것과 같다. 어떤 문에든 라벨을 붙일 수 있지만, 루프나 조건문처럼 바디가 있는 문에 라벨을 붙여야 유용하게 사용할 수 있다. 루프에 이름을 붙이면 루프 바디 안에서 break와 continue 문을 사용해 루프를 빠져나가거나 루프 맨 위로 점프해 다음 반복으로 넘어갈 수 있다. 자바스크립트에서 문 라벨을 이용하는 문은 break와 continue 뿐이다. 다음은 while 루프에 라벨을 붙이고 continue 문에서 그 라벨을 사용하는 예제다.
mainloop: while(token !== null) {
// 코드는 생략
continue mainloop; // 이 루프의 다음 반복으로 건너뛴다.
// 코드는 생략
}
라벨의 네임스페이스는 변수나 함수의 그것과 다르므로, 같은 식별자를 문 라벨에 쓰고 다시 변수나 함수의 이름에 쓸 수 있다.
문 라벨이 적용되는 영역은 해당 라벨이 적용되는 문(물론 그 하위 문을 포함) 뿐이다. 문에는 자신을 포함한 문과 같은 라벨을 붙일 수 없지만, 중첩되지 않은 두 문에는 같은 라벨을 붙일 수 있다. 라벨 붙은 문을 모아서 다시 라벨을 붙일 수도 있다. 즉 모든 문은 여러 개의 라벨을 가질 수 있다.
5.5.2 break
break 문을 단독으로 사용하면 자신을 포함하고 있는 가장 가까운 루프 또는 switch 문을 즉시 빠져나간다.
루프를 더 진행할 필요가 없을 때 일찍 끝내는 용도로 사용한다.
다음 코드는 배열에서 특정 값을 검색한다. 이 루프는 배열의 끝에 도달하면 일반적인 방법으로 종료되지만, 원하는 것을 찾으면 break 문으로 종료된다.
for(let i = 0; i < a.length; i++) {
if(a[i] === target) break;
}
break 키워드 다음에 문 라벨(콜론 없이 식별자만)을 붙일 수 있다.
break labelname;
break 문은 자신을 둘러싼 문이면 어떤 형태든 빠져나갈 수 있다.
그저 이름을 붙이기 위해 중괄호를 썼을 뿐인 문 블록에서도 빠져나갈 수 있다.
break 문에 라벨을 사용하는 경우는 탈출하려는 문이 가장 가까운 루프나 switch 문이 아닐 때다.
let matrix = getData(); // 숫자로 구성된 2차원 배열을 만든다.
// 행렬에 포함된 숫자를 모두 더한다.
let sum = 0, success = false;
// 에러가 발생했을 때 빠져나갈 수 있도록 라벨 붙은 문으로 시작한다.
computeSum: if (matrix) {
for(let x = 0; x < matrix.length; x++) {
let row = matrix[x];
if (!row) break computeSum;
for(let y = 0; y < row.length; y++) {
let cell = row[y];
if(isNaN(cell)) break computeSum;
sum += cell;
}
}
success = true;
}
// break 문은 여기로 점프한다. 만약 success의 값이 false인 상태로 여기 도달했다면
// 전달한 행렬에 뭔가 문제가 있는 것이다.
// 문제가 없다면 sum은 행렬에 포함된 모든 셀의 합이다.
이해를 돕기 위해 내가 새로 작성한 코드
let matrix = [[0, 0], [1, 1], [2, 2], [3, 3]]
let sum = 0, success = false;
computeSum: if (matrix) {
for(let x = 0; x < matrix.length; x++) {
let row = matrix[x];
console.log(`${x+1}행 : ${row}`);
if (!row) break computeSum;
for(let y = 0; y < row.length; y++) {
let cell = row[y];
console.log(`${x+1}행 ${y+1}열 : ${cell}`);
if(isNaN(cell)) break computeSum;
sum += cell;
console.log(`${x+1}행 ${y+1}열의 값까지 더했을 때, sum = ` + sum);
}
}
success = true;
}
출력되는 값은 아래와 같다.
// 1행 : 0,0
// 1행 1열 : 0
// 1행 1열의 값까지 더했을 때, sum = 0
// 1행 2열 : 0
// 1행 2열의 값까지 더했을 때, sum = 0
// 2행 : 1,1
// 2행 1열 : 1
// 2행 1열의 값까지 더했을 때, sum = 1
// 2행 2열 : 1
// 2행 2열의 값까지 더했을 때, sum = 2
// 3행 : 2,2
// 3행 1열 : 2
// 3행 1열의 값까지 더했을 때, sum = 4
// 3행 2열 : 2
// 3행 2열의 값까지 더했을 때, sum = 6
// 4행 : 3,3
// 4행 1열 : 3
// 4행 1열의 값까지 더했을 때, sum = 9
// 4행 2열 : 3
// 4행 2열의 값까지 더했을 때, sum = 12
하지만 break 문은 함수 외부에 있는 라벨을 볼 수 없다.
somelabel: function normal() {
somelabel: {
console.log('normal');
break somelabel;
console.log('something wrong');
}
console.log('break cannot break function');
}
normal()
// "normal"과 "break cannot break function" 출력
outlabel: function serror() {
somelabel: {
console.log('normal');
break outlabel; // Syntax Error: undefined label 'outlabel'
console.log('something wrong');
}
console.log('break cannot break function');
}
5.5.3 continue
인터프리터는 continue 문을 만나면 루프의 현재 반복을 멈추고 다음 반복으로 넘어간다. 따라서 루프 타입에 따라 결과가 다를 수 있다.
- while 루프에서는 expression(표현식)을 루프 맨 위에서 다시 평가하고, true면 루프 바디를 맨 위에서부터 실행
- do/while 루프에서는 루프 맨 아래까지 건너뛴 다음, 루프 조건을 다시 평가한 후 맨 위부터 재시작
- for 루프에서는 increment(증가) 표현식을 평가하고 다시 test(테스트) 표현식을 평가해서 반복을 재개할지 결정
- for/of와 for/in 루프에서는 다음 값 또는 프로퍼티 이름이 variable(변수)에 할당
for(let i = 0; i < data.length; i++) {
if (!data[i]) continue; // 정의되지 않은 데이터는 처리할 수 없다.
total += data[i];
}
// continue 를 만나면 i를 현재 값에서 1 증가시키고
// i가 data.length 보다 작은지 테스트한 다음
// 바디 실행
5.5.4 return
함수 호출은 표현식이고, 표현식에는 모두 값이 있다.
return 문은 그 함수 호출의 값을 지정한다.
return 문이 없는 함수 호출은 함수 바디의 각 문을 차례대로 실행한 다음 호출자에게 돌아간다. 이런 경우 호출 표현식은 undefined로 평가된다. return 문은 보통 함수의 마지막에 사용하곤 하지만 꼭 마지막에만 써야하는 것은 아니다. 함수는 return 문을 실행하는 즉시 호출자에게 돌아가며 함수 바디 안에 있는 다른 문은 무시한다. return 문을 expression 없이 사용해도 호출자에게 undefined를 반환한다.
5.5.6 throw
function factorial(x) {
// 인자가 유효하지 않으면 예외를 발생시킨다.
if (x < 0) throw new Error("x must not be negative");
// 그렇지 않다면 정상적으로 값을 계산해 반환한다.
let f;
for(f = 1; x > 1; f *= x, x--) /* 의도적으로 비움 */ ;
return f;
}
factorial(4) // => 24
// if (x < 0) throw new Error("x must not be negative"); 문장이 없었다면
// factorial(음수) 했을 때 무조건 1이 리턴된다.
예외가 일어나면 자바스크립트 인터프리터는 즉시 프로그램 실행을 멈추고 가장 가까운 예외 핸들러로 점프한다. 예외 핸들러로는 catch를 사용한다. 예외를 일으킨 코드 블록에 연결된 catch 절이 없다면 인터프리터는 다음으로 가장 가까운 코드 블록에 예외 핸들러가 있는지 검색한다. 핸들러를 찾을 때까지 이 과정을 반복한다. try/catch/finally 문이 없는 함수에서 예외가 발생되면 예외는 해당 함수를 호출한 코드까지 올라간다. 이런 식으로 예외는 자바스크립트 메서드와 콜 스택을 계속 거슬러 올라간다. 끝까지 예외 핸들러를 찾지 못하면 예외를 에러로 간주하고 사용자에게 보고한다.
5.5.7 try/catch/finally
try: 처리하려 하는 예외가 담긴 코드 블록.
catch: try 블록에서 예외가 발생하면 호출되는 절.
finally: try 블록에서 무슨 일이 일어났든 관계없이 실행되는 일종의 정리 코드.
catch와 finally 모두 선택 사항이지만 try 블록 뒤에 둘 중 하나는 반드시 써야 한다.
try, catch, finally 블록은 모두 중괄호에 둘러싸여 있다. 중괄호는 절대 생략할 수 없다.
try {
// 문제가 없을 경우 일반적으로 이 코드는 블록 위쪽에서 아래쪽으로 실행된다.
// 하지만 이 코드는 때때로 예외를 발생시킬 수 있는데,
// throw 문을 통해 예외를 직접 일으키거나
// 예외를 발생시키는 메서드를 호출해서 간접적으로 발생시킨다.
}
catch(e) {
// 이 블록의 문은 try 블록에서 예외를 발생시켰을 때만 실행된다.
// 이 문은 로컬 변수 e를 사용할 수 있으며 이 변수는
// Error 객체 또는 전달받은 값을 참조한다.
// 이 블록은 예외를 처리할 수도 있고,
// 아무 일도 하지 않고 무시할 수도 있으며,
// throw를 통해 다시 예외를 발생시킬 수도 있다.
}
finally {
// 이 블록은 try 블록에서 무슨 일이 있었든 항상 실행된다.
// 경우의 수는 다음과 같다.
// 1) 정상적으로 try 블록 끝에 도달한 경우
// 2) break, continue, return 문을 통해 try 블록을 빠져나가는 경우
// 3) 위 catch 절에서 처리한 예외 때문에 try 블록이 종료된 경우
// 4) 예외가 캐치되지 않고 계속 전파되는 경우
}
catch 키워드 뒤에는 일반적으로 괄호 안에 식별자를 쓴다. 이 식별자는 함수 매개변수와 비슷하다. 예외를 캐치하면 그 예외와 연결된 값(예를 들어 Error 객체)이 이 매개변수에 할당된다. catch 절이 받은 식별자는 블록 스코프이므로 catch 블록 안에만 존재한다.
function factorial(x) {
// 인자가 유효하지 않으면 예외를 발생시킨다.
if (x < 0) throw new Error("x must not be negative");
// 그렇지 않다면 정상적으로 값을 계산해 반환한다.
let f;
for(f = 1; x > 1; f *= x, x--) /* 의도적으로 비움 */ ;
return f;
}
try {
// 사용자에게 숫자 입력을 요구한다.
let n = Number(prompt("Please enter a positive integer", ""));
// 입력이 유효한 숫자라고 가정하고 그 팩토리얼을 계산한다.
let f = factorial(n);
// 결과를 표시한다.
alert(n + "! = " + f);
}
catch(ex) { // 입력이 유효하지 않다면 실행된다.
alert(ex); // 사용자에게 에러에 대해 알린다.
}
finally는 catch만큼 자주 사용되지는 않지만 특정 상황에서는 유용할 수 있다. finally 절은 try 블록의 코드가 어떻게 끝났든 관계없이, 일부분이라도 실행되면 항상 실행된다. 이 절은 일반적으로 try 절의 코드 이후를 정리하는 목적으로 사용된다.
일반적인 경우라면 자바스크립트 인터프리터가 try 블록의 끝까지 진행한 후 finally 블록으로 넘어가서 필요한 정리 작업을 수행한다.
인터프리터가 return, continue, break 문 때문에 try 블록을 중단하고 벗어난다면 다음 목적지로 이동하기 전에 finally 블록을 먼저 실행한다.
try 블록에서 예외가 발생했고 그에 연결된 catch 블록이 있다면 인터프리터는 먼저 catch 블록을 실행한 다음 finally 블록을 실행한다. 예외를 처리할 catch 블록이 없다면 인터프리터는 먼저 finally 블록을 실행하고, 가장 가까운 catch 절로 점프한다.
finally 블록 자체에 return, continue, break, throw 문이 있거나 예외를 일으키는 함수를 호출한다면 인터프리터는 대기시켜 둔 점프를 취소하고 finally 블록을 따라 점프한다. 예를 들어 finally 절에서 예외를 발생시키면 그 예외는 처리중이던 예외를 모두 무시하고 우선권을 갖는다. finally 절에 return 문이 포함되어 있으면 처리 중이던 예외가 아직 완전히 처리되지 않았더라도 함수 실행을 종료한다.
catch 절 없이 try와 finally만 사용할 수도 있다. 이런 경우 finally 블록은 try 블록에서 무슨 일이 있었든 관계없이 실행되는 정리코드다. continue 문의 동작 방식 차이 때문에 while 루프로는 for 루프를 완전히 흉내낼 수 없다고 했지만, try/finally 문을 사용하면 for 루프처럼 동작하며 continue 문도 정확히 처리하는 while 루프를 만들 수 있다.
// for(initialize; test; increment) 바디를 흉내냈다.
initialize;
while( test ) {
try { body; }
finally { increment; }
}
하지만 body에 break 문이 들어있다면 종료 전에 increment가 한 번 더 실행되므로 조금 다르게 동작한다. 따라서 finally 절을 사용하더라도 while 루프로 for 루프를 완전히 흉내내는 것은 불가능하다.
5.7 선언
5.7.2 함수
function area(radius) {
return Math.PI * radius * radius;
}
함수 선언은 함수 객체를 생성하고 이를 지정된 이름(위 예제에서는 area)에 할당한다. 프로그램의 다른 곳에서 이 이름을 사용해 함수를 참조하고 그 코드를 실행할 수 있다. 함수 선언은 어떤 블록에 있든 해당 블록의 코드보다 먼저 처리되며, 함수 이름은 그 블록을 통틀어 함수 객체에 묶인다. 함수를 선언하는 것을 '호이스팅 된다(끌어올려진다)'라고 표현하는데, 함수 선언은 어떤 스코프에서 정의 됐든 항상 그 맨 위에 있는 것처럼 처리하기 때문이다. 결과적으로 함수를 선언하는 코드보다 앞에 있는 코드에서 그 함수를 호출할 수 있다.
5.7.3 클래스
ES6 이후에는 class 선언으로 새 클래스를 생성하고 이름을 붙인다.
class Circle {
constructor(radius) { this.r = radius; }
area() { return Math.PI * this.r * this.r }
circumference() { return 2 * Math.PI * this.r }
}
함수와 달리 클래스 선언은 끌어올려지지 않으며, 위와 같이 선언한 클래스는 선언하기 전에는 사용할 수 없다.
5.7.4 가져오기와 내보내기
import와 export 선언은 다른 모듈에서 정의한 값을 사용할 수 있게 합니다. 모듈은 자바스크립트 코드로 구성된 파일이며 독자적인 전역 네임스페이스를 갖고, 다른 모듈에는 완전히 독립적이다. 한 모듈에서 정의한 값(함수나 클래스)을 다른 모듈에서 사용하는 방법은, 정의한 모듈에서 export로 값을 내보내고 사용할 모듈에서 import로 가져오는 방법 뿐이다.
import 지시자는 다른 모듈에서 하나 이상의 값을 가져오고, 현재 모듈에서 사용할 이름을 부여한다.
// import를 사용하는 몇가지 방법
import Circle from './geometry/circle.js';
import { PI, TAU } from './geometry/constants.js';
inport { magnitude as hupotenuse } from './vector.utils.js';
자바스크립트 모듈에 들어 있는 값은 비공개이며 명시적으로 내보내지 않는 한 다른 모듈에서 가져올 수 없다. export 지시자가 이를 내보내는 역할을 한다. 이 지시자는 현재 모듈에서 정의한 하나 이상의 값을 내보내며, 따라서 다른 모듈에서 가져올 수 있다고 선언한다. export 지시자는 import 지시자보다 더 다양한 형태를 사용한다. 다음은 그 중 하나이다.
// geometry/constants.js
const PI = Math.PI;
const TAU = 2 * PI;
export { PI, TAU };
export 키워드를 이용하여, 때때로 다른 선언을 변경해 상수, 변수, 함수, 클래스를 정의하는 동시에 내보내는 복합 선언을 할 수도 있다.
그리고 모듈에서 내보내는 값이 단 하나뿐일 때는 일반적으로 export default를 사용한다.
export const TAU = 2 * Math.PI;
export function magnitude(x,y) { return Math.sqrt(x*x + y*y); }
export default class Circle { /* 클래스 선언은 생략 */ }
'부트캠프 > 자바스크립트 완벽 가이드' 카테고리의 다른 글
7장 배열 (0) | 2023.01.26 |
---|---|
6장. 객체 (0) | 2023.01.15 |
4장 표현식과 연산자 - 스터디 정리 및 회고 (0) | 2023.01.03 |
4장. 표현식과 연산자 - 단축 평가, 비트 NOT(~), 2진수 음수 변환 (0) | 2022.12.29 |
3장 타입, 값, 변수 (0) | 2022.12.27 |