서론
할 말은 없다. 워낙 급하기 때문에 나머지 말은 사족에 다 몰아서 적도록 하겠다.
강의는 산더미이고, 정리할 것 또한 산더미이다. 지금 당장 안 끝내면 두고두고 밀릴 것이 뻔하다.
이번 일자는 자바스크립트 기초 - 기본 및 control flow이다.
연산자
연산자(Operator)는 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입 연산 등을 수행해 하나의 값을 만든다.
이때 연산의 대상을 피연산자(Operand)라 한다.
또한, 다양한 종류의 연산자가 존재한다.
- 단항 연산자, 산술 연산자, 관계 연산자, 이진 논리 연산자, 삼항 연산자, 할당 연산자, 옵셔널 연산자, 쉼표 연산자 등등...
산술 연산자 (Arithmetic operator)
피연산자를 대상으로 수학적 계산을 통해 새로운 숫자 값을 만든다.
피연산자 간의 산술 연산이 불가능한 경우 NaN을 대신 반환한다.
이러한 산술 연산자는 이항 산술 연산자와 단항 산술 연산자로 구분할 수 있다.
단항 산술 연산자 (Unary)
// 선대입 후증가
result = x++;
// 선대입 후감소
result = x--;
// 선증가 후대입
result = ++x;
// 선감소 후대입
result = --x;
단항 산술 연산자는 1개의 피연산자만을 대상으로 산술 연산하여 숫자 값을 반환한다.
증가(++) 및 감소(--) 연산자는 피연산자의 값을 변경하는 부수 효과가 있다. 즉, 증가/감소 연산을 하면 피연산자의 값이 바뀐다. 이는 곧 증가 및 감소 연산 시에 피연산자의 값을 변경하는 암묵적인 할당이 있다는 말이다.
이러한 증가 및 감소 연산자는 위치에 따라 처리 단계가 다르다.
- 연산자의 위치가 피연산자 앞에 위치: 전위 증감 연산자(Prefix increment/decrement operator).
++a, --a와 같은 연산 형태.
피연산자 앞에 위치한 전위 증가 및 감소 연산자는 먼저 피연산자의 값을 증감시킨 후, 다른 연산을 수행한다. - 연산자의 위치가 피연산자 뒤에 위치: 후위 증감 연산자(Postfix increment/decrement operator).
a++, a--와 같은 연산 형태.
피연산자 뒤에 위치한 후위 증가 및 감소 연산자는 먼저 다른 연산을 수행한 후, 피연산자의 값을 증감시킨다.
또한 단순 플러스(+) 연산자는 양수의 표현이며, 단항 상태에서는 아무런 효과가 없다.(단항 상태가 아니면 달라질 수도 있다.) 반면 단순 마이너스(-) 연산자는 단항 상태라도 양수를 음수로, 음수를 양수로 반전시킨 값을 반환할 수 있다.
이항 산술 연산자 (Binary)
이항 산술 연산자는 2개의 피연산자를 대상으로 연산하여 숫자 데이터 타입의 값을 만든다.
a + b
// 더하기
a - b
// 빼기
a * b
// 곱하기
a / b
// 나누기
a % b
// 나머지
모든 이항 산술 연산자는 피연산자의 값을 변경하는 부수 효과(Side effect)가 없다.
즉, 어떤 산술 연산을 해도 피연산자의 값이 바뀌는 경우는 없고 그저 새로운 값을 만들 뿐이라는 것이다.
비교 연산자 (Comparison operator)
좌측 항과 우측 항의 피연산자를 비교 후 결과를 boolean 값으로 반환하는 연산자이다.
동등/일치 비교 연산자
동등 비교 (Loose equality) 연산자와 일치 비교 (Strict equality) 연산자는 좌측 항과 우측 항의 피연산자가 같은 값으로 평가되는지 비교한다.
그리고 boolean 값을 반환한다. 다만 동등과 일치는 그 엄격성의 정도가 다르다.
비교 연산자 | 의미 | 예시 | 설명 |
== | 동등 비교 | x == y | x와 y의 값이 같음. |
=== | 일치 비교 | x === y | x와 y의 값과 타입이 같음. |
!= | 부등 비교 | x != y | x와 y의 값이 다름. |
!== | 불일치 비교 | x !== y | x와 y의 값과 타입이 다름. |
동등 비교 연산자는 좌측 항과 우측 항의 피연산자를 비교할 때, 먼저 암묵적 타입 변환을 수행한다. 그럼으로써 타입을 일치시킨 후 같은 값인지 비교한다.
123 == 123; // true
123 == '123'; // 이게 왜 true?
덕분에 위처럼 좌와 우의 피연산자가 같은 타입이 아니더라도 암묵적 타입 변환 후 같은 값이라면 true일 수 있다. 사실상 타입은 보지 않는 것이 동등 비교 연산자라고 할 수 있다.
반면 일치 비교 연산자는 좌와 우의 피연산자가 타입도 같고 값도 같은 경우에 한해서면 true를 반환한다. 즉, 조금 더 엄격한 비교 연산자라고 할 수 있다.
NaN === NaN
// false
0 === -0
// true
null === undefined
// false
이러한 일치 연산자에서는 주의할 것이 있다. 바로 위와 같은 경우들이다.
특히 NaN 간의 일치 연산자 비교는 분명 되어야 할 것 같은데 되지 않는다. 이 때문에 숫자가 NaN인지 확인하려고 한다면 빌트인 함수 isNaN()을 사용해야 한다.
대소 관계 비교 연산자
대소 관계 비교 연산자는 피연산자의 크기를 비교하여 boolean 값을 반환한다.
비교 연산자 | 예제 | 설명 |
> | x > y | x가 y보다 크다. |
< | x < y | x가 y보다 작다. |
>= | x >= y | x가 y보다 같거나 크다. |
<= | x <= y | x가 y보다 같거나 크다. |
각 설명에 해당하는 조건이 맞으면 true가 반환된다.
삼항 연산자 (Ternary operator)
삼항 조건 연산자는 조건식의 평가 결과에 따라 반환할 값을 결정한다.
//조건식 ? 식이 ture일때 반환할 값 : 식이 false일때 반환할 값
var store = food_count >= 50 ? 'end operation' : 'extended operation'
부수 효과는 없고 위와 같이 사용한다.
논리 연산자 (Logical operator)
논리 연산자는 좌우 항의 피연산자를 논리 연산한다.
논리 연산자 | 의미 |
|| | 논리합(OR) 어떤 하나로 참일 경우 true 반환 |
&& | 논리곱(AND) 모두 참일 경우에만 true 반환 |
! | 부정(NOT) 값을 반대로 뒤집는다. |
다만, 부정 논리 연산자의 경우 우항(->)의 피연산자를 논리 연산한다. 또한, 논리 부정 연산자는 언제나 boolean 값을 반환한다. 만약 피연산자가 boolean 값이 아니면 boolean 타입으로 암묵적 타입 변환을 수행한다.
참고로 논리 연산자 내에서 null, undfined, 빈 문자열은 false로 처리가 된다.
단축 평가
논리합(||) or 논리곱(&&) 연산자 표현식의 평가 결과는 항상 boolean 값이 아닐 수도 있다.
어논리합(||) or 논리곱(&&) 연산자 표현식은 언제나 2개의 피연산자 중 어느 한쪽으로 평가된다.
단축 평가 표현식 | 평가 결과 |
true && anything | anything |
false && anything | false |
true || anything | true |
false || anything | anything |
이를 간단하게 표현하자면, 첫번째 식을 평가한 결과에 따라서, 두번째 식 평가를 실행한다는 것이다.
'doge' && 'coin'
// "coin"
논리곱(&&) 연산자는 두 개의 피연산자가 모두 true로 평가될 때 true를 반환하며, 좌항에서 우항으로 평가가 진행된다.
즉, 첫 번째 피연산자 시점에서는 표현식을 제대로 평가할 수 없기에, 두 번째 피연산자까지 넘어가서 표현식을 평가한다. 그리고 최종적으로 논리 연산의 결과를 결정하는 두 번째 피연산자를 반환한다. 이는 논리곱(&&)이기에 뒷 내용까지 확인해야 표현식을 평가할 수 있기 때문이다.
'doge' || 'coin'
// "doge"
논리합(||) 연산자는 두 개의 피연산자 중 하나만 true로 평가되어도 true를 반환한다. 논리합 연산자도 좌항에서 우항으로 평가가 진행된다.
그러나 첫 번째 피연산자 시점에서 true가 나오면, 그 이상으로 평가하지 않는다. 말 그대로 첫 번째 피연산자까지만 평가하고, 첫 번째 피연산자의 값을 반환해버린다. 이는 논리합(||)이기에, 뒷 내용까지 가지 않아도 표현식을 평가할 수 있기 때문이다.
이러한 단축 평가는 다음과 같은 상황에서 유용하게 사용된다.
1. 객체가 null 또는 undefined인지 확인하고 프로퍼티를 참조할 때.
- 객체는 키과 값으로 구성된 프로퍼티들의 집합이다. 만약 객체가 null 또는 undefined인 경우, 객체의 프로퍼티를 참조하면 타입 에러(TypeError)가 발생하며 강제 종료된다. 이때 단축 평가를 사용하면 에러를 발생시키지 않는다.
2. 함수의 매개변수에 기본값을 설정할 때.
- 함수를 호출할 때 인수를 전달하지 않으면 매개변수는 undefined를 갖는다. 이때 단축 평가를 사용해서 매개변수의 기본값을 설정하면 undefined로 인해 발생할 수 있는 에러를 방지할 수 있다.
번외로 if문을 단축 평가로 대체할 수 있는 상황일 때도 충분히 사용할 법하다. 다만, if문을 마구잡이로 단축시키면 가독성이 엉망이 될 수도 있으니 주의하자.
쉼표 연산자 (Comma operator)
콤마(,) 연산자라고도 부른다.
var a, b, c;
a = b = 5, c = 7; // 콘솔에는 4를 반환
console.log(a); // 5
var x, y, z;
x = (y = 7, z = 9); // 콘솔에는 6을 반환
console.log(x); // 9
각각의 피연산자를 왼쪽에서 오른쪽 순서로 평가하고, 마지막 연산자의 값을 반환한다.
보통 여러 번의 업데이트 작업을 수행할 때 유용하다.
그룹 연산자 (Group operator)
그룹 연산자는 괄호 그룹 내의 표현식을 최우선으로 평가한다.
그룹 연산자를 사용하면 연산자의 우선 순위를 최고 1순위로 높일 수 있다.
5 * 1 + 5 // 10
5 * (1 + 5) // 30
사실 일반적인 수학 내에서의 우선 순위와 비슷하다고 보면 된다.
옵셔널 체이닝 연산자 (Optional chaining operator)
// 옵셔널 체이닝 연산자가 없을 시절의 사용 방법
let FoodName = customer && customer.food && customer.food.name
// 옵셔널 체이닝 연산자 활용 예시
let FoodName = customer?.food?.name
일명 선택적 연결. 옵셔널 체이닝 연산자(?.)는 좌항의 피연산자가 null 또는 undefined인 경우 undefined를 반환하고, 그렇지 않으면 우항의 프로퍼티 참조를 이어간다.
다만, 브라우저 간 호환성을 확인해야 한다. 작동하지 않는 경우도 많기에 폴리필을 써야 할 수도 있다. 그리고 옵셔널 체이닝은 잦을 사용을 할 경우 가독성에 큰 타격을 준다.
Nullish 병합 연산자 (Nullish coalescing operator)
var st = null ?? '기본값';
console.log(st); // "기본값"
Nullish 병합 연산자(??)는 좌항의 피연산자가 null 또는 undefined인 경우 우항의 피연산자를 반환하고, 그렇지 않으면 좌항의 피연산자를 반환한다.
위의 예시에서 알 수 있다시피 변수에 기본값을 설정할 때 유용하다. 이런 것이 없을 시절에는 논리 연산자(||)을 통한 단축 평가를 활용하여 기본값을 설정했었다.
할당 연산자 (Assignment operator)
할당 연산자 | 설명 | 동일 표현 |
= | 왼쪽 변수에 오른쪽 값을 할당 | x = y |
+= | 왼쪽 변수에 오른쪽 값을 더하고 결과를 왼쪽 변수에 할당 | x = x + y |
-= | 왼쪽 변수에서 오른쪽 값을 빼고 결과를 왼쪽 변수에 할당 | x = x - y |
*= | 왼쪽 변수에 오른쪽 값을 곱하고 결과를 왼쪽 변수에 할당 | x = x * y |
/= | 왼쪽 변수에서 오른쪽 값을 나누고 결과를 왼쪽 변수에 할당 | x = x / y |
%= | 왼쪽 변수에서 오른쪽 값을 나눈 나머지의 결과를 왼쪽 변수에 할당 | x = x % y |
**= | 왼쪽 변수에 오른쪽 값만큼 제곱을 하고 결과를 왼쪽 변수에 할당 | x = x ** y |
&&= | 오른쪽 변수만 평가하고 왼쪽 변수가 참이면 왼쪽 변수에 할당 | x && (x = y) |
||= | 오른쪽 변수만 평가하고 왼쪽 변수가 거짓인 경우 왼쪽 변수에 할당 | x || (x = y) |
할당 연산자는 오른쪽에 있는 피연산자의 평가 결과를 왼쪽에 있는 변수에 할당한다.
할당 연산자는 왼쪽의 변수에 값을 할당하므로 부수 효과가 있다고 할 수 있다.
기타 연산자
아래는 한 개의 피연산자만 사용하는 연산이다.
- void : 표현식을 평가할때 값을 반환하지 않도록 지정한다.
- typeof : 평가 전의 피연산자의 데이터 타입을 나타내는 문자열을 반환한다.
- delete : 객체의 속성을 삭제한다.
아래는 피연산자를 비교하고, 결과가 참인지에 따라 boolean 값을 반환하는 연산이다.
- in: 객체 내에 해당 속성이 존재할 경우 true를 반환.
- instanceof: 객체가 특정 클래스에 속하는지 아닌지를 확인하고 특정 클래스에 속하면 true를 반환.
비교 연산자와 비슷한 측면이 있다고 할 수 있다. 다만, 이 둘은 사실 관계 연산자보다는 다른 무언가에 가깝다.
그리고 이러한 연산자 외에도 비트(0, 1) 단위에서 연산하는 비트 연산자(&, |, <<, >>) 등이 있다.
함수 (Function)
함수는 다른 객체처럼 속성 및 메서드를 가질 수 있기에 일급 객체이며 인자를 가질 수 있는 코드 블록이다. 쉽게 말하자면 외부 코드가 호출할 수 있는 하위 프로그램이다.
함수는 자체 범위를 가진다. 자바스크립트에서 함수는 프로그램의 매우 중요한 특징이며, 특히 부모 함수의 지역 변수에 접근할 수 있다. 이러한 접근을 클로저(closure)라고 한다.
그리고 자바스크립트의 함수는 객체처럼 속성과 메서드를 가질 수 있다. 객체와의 차이점은 함수는 외부에서 호출이 가능한 반면, 객체는 외부에서 호출이 불가능하다는 점이다.
함수는 다음과 같이 구성된다.
- input : 로직 형태를 위해 주입받는 데이터
- output : 로직 처리 후 반환되는 결과를 위한 데이터
- 본문 : 명령문의 시퀀스로 구성
또한, 자바스크립트의 함수는 일급 객체(first-class object)이다. 일급 객체란 생성, 대입, 연산, 인자 또는 반환값으로서의 전달 등 프로그래밍 언어의 일반적으로 적용 가능한 연산을 제한없이 사용할 수 있는 대상을 의미한다.
- 무명의 리터럴 생성 가능, 런타임 생성 가능하다.
- 할당명령문의 대상이 될 수도 있다.
- 변수나 자료 구조(객체, 배열 등등)에 저장할 수 있다.
- 함수의 매개변수(파라미터)로 전달할 수 있다.
- 반환값(return value)으로 사용할 수 있다.
이러한 함수는 정의에 따라 총 3가지의 방식으로 선언할 수 있다.
1. 함수 선언문
2. 함수 표현식
3. Function 생성자 함수
함수 선언문 (Function declaration)
function f1(n) {
return n + n;
}
// 매개변수 기본값
function f1_1(a = 2, b = 1) {
return a + b;
}
// 나머지 매개변수
function f1_2(... rest) {
console.log(rest); // [4, 8, 12, 16, 20]
}
f1_2(4, 8, 12, 16, 20);
// arguments 객체
function showName() {
alert(arguments.length);
alert(arguments[0]);
alert(arguments[1]);
}
// 2, cham, hoooo 출력됨
showName("cham", "hoooo");
// 1, cham, undefined가 출력됨(두 번째 인수는 없음)
showName("cham");
함수 선언문 방식으로 정의한 함수는 function 키워드와 이하의 내용으로 구성된다. 우리가 흔히 아는 그 선언문이다.
- 함수명: 함수의 이름
함수 선언문의 경우, 함수명은 생략할 수 없다. 함수명은 함수 몸체에서 자신을 재귀적(recursive) 호출하거나 자바스크립트 디버거가 해당 함수를 구분할 수 있는 식별자이다. - 매개변수: 함수의 전달되는 인수
0개 이상의 목록으로 괄호로 감싸고 콤마로 분리한다. 다른 언어와의 차이점은 매개변수의 타입을 기술하지 않는다는 것이다. 이 때문에 함수 몸체 내에서 매개변수의 타입 체크가 필요할 수 있다.- 기본값 매개변수 (Default Parameter)
함수를 호출할 때 매개변수의 개수만큼 인수를 전달하는 것이 일반적이지만 그렇지 않은 경우에도 에러가 발생하지는 않는다. 함수는 매개변수의 개수와 인수의 개수를 체크하지 않는다. 인수가 부족한 경우, 매개변수의 값은 undefined가 된다. - 나머지 매개변수 (Rest Parameter)
나머지 매개변수는 매개변수 이름 앞에 점 3개(...)를 붙여서 정의한 매개변수를 의미한다. 나머지 매개변수는 함수에 전달된 인수들의 목록을 배열로 전달받는다. 쉽게 말하자면 함수가 정하지 않은 매개변수를 받을 수 있는 것이다. - spread 연산자
spread 연산자(...)를 사용하면 기존 배열이나 객체의 전체 또는 일부를 다른 배열이나 객체로 빠르게 복사할 수 있다. - arguments 객체
함수에 전달되는 인자들을 참조할 수 있는 객체로, 나머지 매개변수가 나오기 이전에 함수의 인수 전체를 얻어내는 방법이었다. arguments 객체는 함수 호출 시 전달된 인수(argument)들의 정보를 담고 있는 순회 가능한(iterable), 배열이 아닌 유사 배열 객체(array-like object)이며 함수 내부에서 지역 변수처럼 사용할 수 있다.
- 기본값 매개변수 (Default Parameter)
- 함수 내용
함수가 호출되었을 때 실행되는 구문들의 집합이다. 중괄호({ })로 구문들을 감싸고 return 문으로 결과값을 반환할 수 있다. 이를 반환값(return value)라 한다.
또한 함수 선언문은 정의되기 전에도 호출이 가능하다. 즉, 호이스팅이 가능하다.
// 호이스팅
console.log(add(5, 1))
function add(a, b){
return a + b;
}
별로 좋은 것은 아니다. 오히려 프로그래머가 원한 것과 상관없이 코드의 맨 위로 함수를 끌어올린다는 것을 뜻하니 말이다. 코드가 엉성해질 수도 있기에, 가급적 아래의 함수 표현식이 권장된다.
함수 표현식 (Function expression)
// 기명 함수 표현식(named function expression)
let f2 = function f2_get(n) {
return n - n;
};
// 익명 함수 표현식(anonymous function expression)
let f2 = function(n) {
return n - n;
};
함수의 일급 객체 특성을 이용하여 함수 리터럴 방식으로 함수를 정의하고 변수에 할당할 수 있는데 이러한 방식을 함수 표현식이라 한다.
이러한 방식으로 정의한 함수는 함수명을 생략할 수 있다. 이러한 함수를 익명 함수(Anonymous function)라고 한다. 해당 함수에서는 예시에 나와있다시피 함수 이름을 생략하는 것이 보편적이다.
다만, 기명이나 익명이나 둘 다 함수를 호출시에는 함수명이 아닌 함수를 가리키는 변수명을 사용하여야 한다. 이는 함수가 일급 객체이이기에 변수에 할당 시 참조값을 저장하기 때문이다.
누군가는 이렇게 말할 것이다. 그래도 기명 함수 표현식에는 f2 변수 말고 f2_get 함수명이 있는데, 왜 그걸 못 쓰냐며 말이다. 이건 함수 표현식에서 사용한 함수명은 외부 코드에서 접근 불가능하기 때문이다.
이는 사실 함수명으로 호출하는 함수 선언문 또한 마찬가지이다. 자바스크립트 엔진이 사실은 안보이는 곳에서 함수 표현식으로 변환한다.
let f1 = function f1(n) {
return n + n;
};
즉, 이 항목 위에 있는 함수 선언문 코드는 실제로는 다음과 같은 코드로 작동하는 것이다. 그리고 여기서 알 수 있다시피 함수명과 변수명이 같다고해서 문제는 딱히 안 생긴다.
화살표 함수 표현식(Arrow function)
// ES5 - 함수 표현식
let x = function(x, y) {
return x * y;
}
// ES6 - 화살표 함수
let x = (x, y) => x * y;
ES6부터 추가된 화살표 함수 표현식은 함수 리터럴의 단축 표현이고 익명 함수이다.
단, 함수 리터럴과 완전히 같은 건 아니다.
또한 익명 함수이기에 함수 표현식을 사용한다.
그리고 함수 표현식은 호이스팅되지 않기에, 화살표 함수 또한 호이스팅되지 않는다.
다만, this의 값이 함수를 정의할 때 결정되어서 상위 스코프의 this 값이 화살표 함수의 this 값이 되거나, arguments 변수가 없다는 차이가 있다. 이는 추후 상세하게 다루도록 하겠다.
Function 생성자 함수
var f3 = new Function('num', 'return num + num');
console.log(f3(7));
함수 표현식으로 함수를 정의할 때 함수 리터럴 방식을 사용한다. 함수 선언문도 내부적으로 자바스크립트 엔진이 함수 표현식으로 반환하므로 결국 함수 리터럴 방식을 사용합니다. (여기서 말하는 리터럴은, 선언과 동시에 값 또는 코드를 지정해주는 것을 뜻한다.)
함수 선언문과 함수 표현식은 모두 함수 리터럴 방식으로 함수를 정의한다. 이것은 결국 내장 함수 Function 생성자 함수로 함수를 생성하는 것을 단순화시킨 축약 형태(short-hand)이다.
위의 사용 예에서 알 수 있다시피 그리 좋은 방법은 아니다. 일반적으로 사용되지도 않는다.
함수 사용 패턴
즉시 실행 함수 (Immediately invoked function expression; IIFE)
(function () {
console.log('IIFE');
})();
(() => {
console.log('IIFE 화살표 함수')
})();
IIFE는 함수가 정의 되자마자 즉시 실행되는 함수 표현식이다.
코드 평가-> 코드 실행 -> 최초 1회 시행이라는 과정을 거치며, 초기화 처리에 주로 사용된다.
한 번의 실행만을 필요로 하는 초기화 코드 부분에 사용되며, 비동기 처리를 바로 사용할 때(다만 최근 들어서는 굳이 안 그래도 된다) 필요하며, 어쨌거나 괄호로 블럭 지정 후 묶는 것이기에 스코프 지정이 되고 전역 변수와 전역 함수 충돌을 막을 수도 있다.
다만, 최근 들어서는 자바스크립트에 모듈 시스템이 발달하고 있다보니 사용빈도가 많이 줄어들었다.
재귀 함수 (Recursion function)
function recurse() {
if(condition) {
recurse();
}
else {
// recurse를 멈추는 코드
}
}
recurse();
함수가 자신을 다시 호출하는 구조로 만들어진 함수이다.
재귀함수는 종료조건이 있어야 하며, 종료조건을 설정해주지 않으면 무한 반복을 하게된다. 당연하지만, 반복하는 구조기에 재귀함수로 작성이 되는 코드는 반복문으로도 작성할 수 있다.
중첩 함수 (Inner function)
function outerFunction(x) {
function innerFunction(y) {
return x + y;
}
return innerFunction;
}
const add_num = outerFunction(10);
console.log(add_num(5)); // 15가 나옴
함수 내부에 정의 된 함수를 중첩 함수(nested function) 또는 내부 함수(inner function)이라 한다. 즉, 함수 안에 함수를 만들 수 있는 것이다. 그리고 중첩 함수를 포함한 함수를 외부 함수라 부른다. 보통 외부 함수의 작동을 돕는 헬퍼 함수의 역할을 한다.
중첩 함수는 외부 함수의 내부에서만 호출할 수 있고, 외부 함수의 밖에서는 호출할 수 없다. 쉽게 말하자면 부모가 되는 함수 안쪽에 만들어놓은 내부 함수는, 부모가 되는 함수 바깥의 공간에서 호출이 불가능하다는 것이다.
또한, 외부 함수의 최상위 레벨에만 중첩 함수 작성이 가능하다.함수 내부의 if문 혹은 while문 혹은 for문 같은 블록 내부에는 중첩 함수가 작성이 불가능하다.
콜백 함수 (Callback function)
// 그냥 함수
function eat(name, callback) {
console.log('고기: ' + name);
callback();
}
// 콜백 함수
function callMe() {
console.log('음식 출력 완료');
}
eat('삼겹살', callMe);
콜백 함수는 매개 변수로 함수를 전달받아, 함수의 내부에서 전달 받은 함수를 실행하는 함수이다.
특정 이벤트가 발생했을 때 시스템에 의해 호출되는 함수가 콜백 함수라고 할 수 있다.
자바스크립트의 구성 요소
- 값 (Value): 값은 식이 평가되어 생성된 결과를 의미한다. 그리고 값과 한 세트라고 할 수 있는 '변수'는 하나의 값을 저장하기위해 확보한 메모리 공간 자체 or 그 메모리 공간을 식별하기 위해 붙인 이름이다.
- 리터럴 (liternal): 리터럴은 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기법을 뜻한다.
- 문 (Statement): 문은 프로그램을 구성하는 기본 단위이자 최소 실행 단위이다.
- 블록문 (Block statemet/Compound statement): 블록문은 0개 이상의 문들을 한 쌍의 중괄호로 묶은 것으로 코드 블록 또는 블록이라고 부른다. 참고로 세미콜론(;)을 끝에 붙이지 않는다.
- 표현식 (Expression): 표현식은 값으로 평가될 수 있는 문이다. 표현식이 평가되면 새로운 값을 생성하거나 기존값을 참조한다.
- 식별자 (Identifier): 식별자는 자바스크립트에서 이름을 붙일 때 사용하는 단어이다. 식별자의 예로는 변수명과 함수명, 클래스명 등이 있다.
제어 흐름 (Control flow)
제어 흐름 혹은 흐름 제어는 명령형 프로그램의 개별 명령문, 명령 또는 함수 호출이 실행되거나 평가되는 순서이다.
이러한 제어 흐름의 종류는 총 5가지이다.
- goto: 다른 구문에서 시작 (비권장되는 방법).
- goto - choice: 일부 조건이 충족되는 경우만 일련의 명령문을 실행.
- if~else, switch - loop: 어떤 조건이 충족될 때까지 일련의 명령문을 0회 이상 실행.
- collection loop, general loop - continue: 현재 실행 구문에서 떨어진 한 구문의 집합을 실행.
- loop continuation - break: 프로그램 실행을 중단.
- loop early exit, 함수 실행 정지
이러한 제어 흐름과 관련된 실행문에는 조건문, 반복문, 분기문 등이 포함되어 있다. 사실상 위의 제어 흐름의 종류 5가지이며, 이는 제어문(Control flow statements)이라고 부른다.
https://softwareengineering.stackexchange.com/questions/414349/control-flow-vs-flow-control
제어 흐름과 흐름 제어?
참고로 Control flow와 Flow control은 외국에서도 어떻게 부르는 게 맞는지 다소 헷갈려 하는 모양이다. 일단 흐름 제어(Flow control)는 네트워크 상의 용어라고 하고, 여기서 말하는 것은 제어 흐름(Control flow)이라고 한다. 근데 둘 다 혼용하는 사람도 많기는 한데, 일단은 제어 흐름이라고 부르도록하자.
조건문 (Conditional statement)
조건문은 특정 조건이 참인 경우에 실행하는 명령의 집합이다.
if ~ else 문
function testNum(a) {
let result;
if (a > 0) {
result = 'if text';
}
else if (a == 0) {
result = 'else if 0 text';
}
else if (a == -1) {
result = 'else if -1 text';
}
else {
result = 'else text';
}
return result;
}
if ~ else 문은 주어진 조건식(boolean 값으로 평가될 수 있는 표현식)의 평가 결과. 즉 참, 거짓에 따라 실행할 코드 블록을 결정한다. 만약 조건식의 평가 결과가 boolean 값이 아니면 boolean 값으로 강제 변환되어 참, 거짓을 구별한다.
if 문은 else if, else와 함께 사용가능하다. 그리고 false, undefined, null, 0, NaN, ""(빈 문자열)과 같은 값들은 if 문 차원에서 거짓으로 판별한다.
switch 문
let a = 1;
switch (a) {
case "1"
a = 'text one';
break;
case 1:
a = 'one';
break;
case 2:
a = 'two';
break;
default:
a = 'X';
break;
}
switch 문은 switch 문의 표현식을 평가하여 그 값과 일치하는 표현식을 갖는 case 문으로 실행 순서를 이동시킨다.
case문은 상황을 의미하는 표현식을 지정하고 콜론으로 마친다. 그리고 그 뒤에 실행할 문들을 위치시킨다. switch 문의 표현식과 일치하는 case 문이 없다면 실행 순서는 default 문으로 이동한다. 참고로 default 문은 옵션이다.
반복문 (Loop statement)
반복문은 주어진 조건이 충족이 될 때까지 반복적으로 문장을 수행 시키는 명령문이다.
while 문
let i = 0, n = 10;
while (i <= n) {
console.log(i);
i += 1;
}
루프 시작에 조건을 검사하고, 조건이 참이면 실행되는 반복문이다.
do ~ while 문
let i = 0, n = 15;
do {
console.log(i);
i++;
} while(i <= n);
일단 먼저 코드 블록을 실행하고 조건식을 평가한다. 따라서 do ~ while은 무조건 한 번 이상 실행된다.
for 문
for ([초기화식]; [조건식]; [증감식]) {
//내용
}
let n = 10;
for (let i = 1; i <= n; i++) {
console.log(`자바스크립트. ${n}회`);
}
기본적인 반복문 중 하나로, '조건식'이 거짓으로 판별될 때까지 코드 블록을 반복 실행한다.
- 초기화식: 실행 전에 한 번 먼저 실행한다. 초기값 겸 초기화값을 결정한다.
- 조건식: 반복문을 작동시킬 조건이 참인지 거짓인지를 판별한다.
- 증감식: 한 번의 순회가 끝날 때마다 실행할 증감 혹은 연산식을 결정한다.
이러한 for문의 조건식은 위와 같이 구성된다.
for in 문
let people = {
name: 'Park',
age: 20,
nation: 'korea',
gender: 'male',
level: 99
}
for (let key_idx in people) {
console.log(`${key_idx} => ${people[key_idx]}`);
}
// <결과>
// name => Park
// age => 20
// nation => korea
// gender => male
// level => 99
상속된 열거 속성(enumerable)들을 포함하여 객체에서 문자열로 키가 지정된 모든 열거 가능한 속성에 대해 반복한다.
쉽게 말하자면 for in문은 object에 사용할 수 있는 반복분이다.
배열 반복에도 쓸 수는 있지만, 추천되지는 않는다. (대신 for of나 forEach를 사용하자.)
참고로 위 코드 예시에서 알 수 있다시피 key_idx 부분의 변수는 객체의 키 인덱스 값을 반환한다.
for of 문
const food = ['Mint Chocolate Cake', 'Coffee', 'Sushi', 'Chili Dog', 'French Fries'];
for (let f_index of food) {
console.log(f_index);
}
배열과 같은 반복할 수 있는 객체(Array, Map, Set, String, TypedArray, arguments 객체 등)를 순회할 수 있도록 해주는 반복문이다.
위의 코드를 실행시키면 배열 내의 요소들이 순차적으로 출력된다. 즉, food 배열을 f_index에 하나씩 넣어가며 순회하되, food 배열의 끝이 나타나면 자동으로 종료하는 것이다.
forEach()
var food = ['Mint Chocolate Cake', 'Coffee', 'Sushi', 'Chili Dog', 'French Fries'];
food.forEach(myFunction);
function myFunction(f_item) {
console.log(f_item);
}
// 일반 사용 1
var food = ['Mint Chocolate Cake', 'Coffee', 'Sushi', 'Chili Dog', 'French Fries'];
food.forEach(function(f_item, f_index, f_arr) {
console.log(`${f_item} item => ${f_index} index`);
});
// 일반 사용 2
// f_item(순회 때마다 택해질 아이템), f_index(택해진 아이템의 인덱스), f_arr(배열 전체)
// 여기서 f_item을 제외한 나머지 2개는 생략해도 됨.
var food = ['Mint Chocolate Cake', 'Coffee', 'Sushi', 'Chili Dog', 'French Fries'];
food.forEach(f_item => {
console.log(f_item);
});
// 화살표 함수 사용 (간략)
var food = ['Mint Chocolate Cake', 'Coffee', 'Sushi', 'Chili Dog', 'French Fries'];
food.forEach((f_item, f_index, f_arr) => {
console.log(`${f_item} item => ${f_index} index`);
});
// 화살표 함수 사용 (상세)
// f_item(순회 때마다 택해질 아이템), f_index(택해진 아이템의 인덱스), f_arr(배열 전체)
// 여기서 f_item을 제외한 나머지 2개는 생략해도 됨.
배열에 활용이 가능한 메서드로, 매개변수로 주어진 함수를 배열 요소 각각에 대해 실행하는 메서드이다.
배열을 순회해서 작업을 수행하는데 사용되는 메서드 정도로 요약할 수 있다.
그리고 이와 비슷한 것이 하나 있는데 바로 나중에 배울 map 메서드이다. forEach가 콜백 함수인 반면, map은 그렇지 않다. 또한, map은 작업한 결과를 새로운 배열에 담아서 반환한다. 그러나 forEach는 반환값이 없고, 원본 배열 그 자체를 수정한다. 이러한 반환값이 없다보니 JSX에서는 return 문 내부에서만 쓸 수 있다.
분기문 (Branching statement)
분기문은 조건문과 반복문에 중간에서 주어진 조건의 흐름을 바꿀 수 있는 구문이다.
break 문
for (let i = 1; i <= 10; i++) {
if (i == 6) {
break;
}
console.log(i);
}
break 문은 코드 블록을 탈출한다.
정확하게는 코드 블록을 탈출하는 것이 아니라 레이블 문, 반복문, switch 문의 코드 블록을 탈출한다. 레이블 문, 반복문, switch 문의 코드 블록 이외에 break 문을 사용하면 SyntaxError(문법 에러)가 발생한다.
continue 문
for (let i = 1; i <= 10; i++) {
if (i >= 3 && i <= 7) {
continue;
}
console.log(i);
}
// 3부터 7까지 continue 되어서 생략됨.
// 따라서 1, 2, 8, 9, 10이 개행되어서 출력됨.
continue 문은 반복문의 코드 블록 실행을 현 지점에서 중단한다. 그리고 반복문의 증감문(for문) 혹은 조건문(while문)으로 이동한다.
break 문처럼 반복문을 탈출하지는 않는다.
예외 상황 (Exception)
런타임 때 발생할 수 있는 의도치 않은 상황을 뜻한다.
흐름 제어 시 발생할 수 있는 예외 상황이므로, 이를 이해하여 코드 레벨에서 대응해야 한다.예외 상황을 핸들링(대응)하지 않는다면, 기능이 동작하지 않거나 어플리케이션이 shut down 될 수도 있다.
예외의 원인
언어 레벨 ~ 외부 유인에 의한 예외 상황까지 다양하다.
- 코드 레벨에서의 문제
- 하드웨어, 디바이스의 문제
- 라이브러리 손상
- 사용자의 입력 실수
기타 등등이 있다.
예외의 종류
ECMAScript Error
자바스크립트 언어에서 발생하는 에러 타입.
- RangeError: 값이 집합에 없거나 허용 범위가 아닐 경우.
- RefereneceError: 존재하지 않는 변수를 참조했을 경우.
- SyntaxError: 문법을 지키지 않았을 경우.
- TypeError: 값이 기대한 자료형이 아니라서 연산이 불가능할 경우.
기타 등등이 있다.
DOMException
Web API 레벨에서 발생하는 에러 타입.
- NetworkError: 네트워크 문제가 발생한 경우.
- AbortError: 작업이 중단되었을 경우.
- TimeoutError: 작업이 시간 초과되었을 경우.
기타 등등이 있다.
기타 종류
개발자도 예상치 못한 상황이 해당한다.
그렇기에 개발자가 직접 예외 상황을 예상하여 핸들링 할 수 있다.
자바스크립트의 Error 객체를 사용하여 에러를 정의내리고 핸들링 할 수 있다.
throw 문
예외를 발생시킬 때 사용한다.
catch 블럭에서 여러 에러 객체를 핸들링할 수 있다.
예외가 발생하면 다음과 같은 일이 벌어진다.
- 현재 함수의 실행이 중지된다.
- 에러 객체와 함께 에러가 throw된다.
- 여기서 상황에 따라 제어 흐름은 갈라진다.
- 호출자 사이에 catch블록이 있으면, catch블록으로 전달
- 호출자 사이에 catch블록이 없으면, 프로그램 종료
여기서 에러를 catch하여 프로그램이 종료되지 않도록 해야 한다.
그리고 이러한 예외를 발생시키고자 throw 문을 사용한다.
throw 여기에 표현식 작성;
throw 표현식은 위와 같으며, 해당 표현식 이후의 명령문은 실행되지 않는다.
Error 객체
const customErr = new Error();
customErr.name = '대충 새로운 에러 이름';
customErr.message = '대충 에러 메시지';
throw customErr;
사용자가 직접 Rrror 객체를 정의하여 사용할 수 있다.
- new Error('에러 메세지')
- error.message
- error.name
참고로 ECMAScript 표준 내장 오류 객체가 존재한다. 그리고 해당 링크에 가보면 알겠지만, 비표준 속성들이 상당히 많은 편이다. 이거 주의하며 사용하자.
콜 스택 (Call stack)
기본적으로 에러가 발생하면 에러를 catch하여 프로그램이 종료되지 않도록 해야 한다.
그리고 catch는 예외 처리를 담당하는 핸들러를 찾기 위해, 순서대로 콜 스택(call stack)을 거슬러 올라간다. 그리고 그곳에서 올바른 핸들러를 찾아내어 처리한다.
이때 말하는 콜 스택은 호출 스택이라고도 불린다.
그리고 이는 자바스크립트 코드가 실행되며 생성되는 실행 컨텍스트를 저장하는 자료구조이다.
더 정확하게는 '스택'은 출입구가 하나인 데이터 구조(Last In First Out)이다. 순서대로 a, b, c가 들어갔다면 나갈 때는 반대인 c, b, a 순으로 나간다. 이는 일종의 적층 구조에서 위의 부분을 우선적으로 가져가는 것이라 할 수 있다.
그리고 함수를 호출할 때마다 콜스택에 스택이 쌓이고, 이 함수의 실행이 종료되면 콜스택에서 스택을 제거하는 것이 기본적인 원리이다. 그리고 에러가 throw되면 콜 스택을 확인하여, 핸들링하고 있는 catch문이 있는 스택에서 처리한다.
try ~ catch 문
try ~ catch 문은 블록문 내에서 예외가 발생할 경우, 예외 처리를 맡을 하나 이상의 반응 명령문을 지정한다.
try 블록은 예외가 발생할 가능성이 있는 코드를 작성하고.
cat 블록은 try 블록에서 던져진 에러 객체 or 에러 발생시의 내용을 작성한다.
- try 블록의 명령문 중 하나가 실패하면, catch로 제어권이 넘어간다.
- try 블록의 명령문 중 하나가 성공하면, catch로 제어권이 넘어가지 않는다.
또한, catch블럭에서 인자로 throw된 catchID를 참조할 수 있다.
try {
fun(value); //일반 내용 작성
} catch (catchID) {
console.log('catch', catchID); // 오류 발생시 내용 작성
}
콜스택 중 하나에서 catch 문 예외가 처리된다면, 그보다 높은 상위의 콜 스택에는 더 이상 예외가 타고 올라오지 않는다.
이러한 try ~ catch문의 사례는 다음과 같다.
- 네트워크 에러 발생 시
- 에러를 감지해야 하는 비즈니스 로직
- 비즈니스 데이터가 유효하지 않는 경우 - 유저가 잘못된 데이터를 입력한 경우
- 기타 등등...
대부분이 외부에 의존하는 로직에서 사용되었다.
(즉, 자신들이 모르는 예기치 못한 상황에 대응하고자 사용된 것이다.)
finally 블록
try {
// 대충 일반 내용
}
catch(error) {
// 대충 예외가 발생한 내용
}
finally() {
// 대충 예외와 상관없이 '항상' 작동하는 내용
}
try 블록에서 예외 상황이 발생하지 않더라도 그대로 실행된다.
이는 심지어 try ~ catch 문에서 return을 사용해도 실행된다.
예외 상황이 발생하였을 때의 순서는 try → catch → finally이다.
반면 예외 상황이 발생하지 않았을 때의 순서는 그냥 try → finally이다.
즉, finally는 항상 실행되는 블록이다.
이를 이용해 파일 닫기나 네트워크 닫기 등 리소스 해제 구현을 finally에서 할 수 있다.
(해제를 하기도 전에 예외 상황으로 멈추면 그건 그것대로 참사니까.)
사족
10일차 강의 TIL 분량이 21,000자가 넘게 나왔다. 🔥 🔥 🔥
이거 쓰는데 걸린 시간이 거의 12시간 정도 되는 것 같다. 덕분에 11일차 강의의 경우 대충 읽고만 넘기게 되었다. 아무래도 11일차 강의는 프로그래머스 데브코스 TIL을 추가로 쓰면서 한 번 더 다시 봐야 할 것 같은 느낌이다.
그리고 솔직하게 느끼는 거지만, 굳이 알고 있는 내용을 너무 자세하게 쓸 필요가 있나 싶기도 하다. 그러니까 공부는 좋은데 if나 for문 같은 건 기존에도 알고 있으니 이런 건 다음에도 나오면 생략하는 게 어떤가 싶다.
내가 만들고 싶은 사이트를 바닐라 HTML&CSS로 만들고, 노션 클로닝도 나중에 한 번 해보려면 시간 조절은 확실하게 필요하다. 이거만 적다가 하루가 그냥 가버리니 원. 😱
'💻 종합 개발 주제 > 📚 웹앱 데브코스' 카테고리의 다른 글
11일차 데브코스 pt.2 - 클라우딩 어플리케이션 엔지니어링 TIL (0) | 2024.01.12 |
---|---|
11일차 데브코스 pt.1 - 클라우딩 어플리케이션 엔지니어링 TIL (2) | 2024.01.11 |
9일차 데브코스 - 클라우딩 어플리케이션 엔지니어링 TIL (6) | 2024.01.09 |
8일차 데브코스 - 클라우딩 어플리케이션 엔지니어링 TIL (2) | 2024.01.06 |
7일차 데브코스 - 클라우딩 어플리케이션 엔지니어링 TIL (2) | 2024.01.05 |