07장 연산자
연산자는 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입, 지수연산 등을 수행해 하나의 값을 만든다, 이때 연산의 대상을 피연산자
라고 한다.
피연산자가 "값"이라는 명사 역할이라면, 연산자는 "피연산자를 연산하여 새로운 값을 만드는" 동사 역할을 한다.
이항 산술 연산자(+
, -
, *
, /
, %
)는 2개의 피연산자를 산술 연산해 숫자 값을 만든다.
단항 산술 연산자는 1개의 피연산자는 산술 연산해 숫자 값을 만든다.
++
증가
--
감소
+
어떠한 효과도 없다.
-
양수를 음수로, 음수를 양수로 변환
증가/감소(++
/--
) 연산자는 위치에 의미가 있다.
숫자 타입이 아닌 피연산자에 +
단항 연산자를 사용하면 피연산자를 숫자 타입으로 변환해 반환한다.
+
연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작하고, 그 외의 경우는 산술 연산자로 동작한다.
개발자의 의도와 상관없이 자바스크립트 엔진에 의해 암묵적으로 타입이 변환되기도 하는데( ex) 1 + true = 2 ) 이를 암묵적 타입 변환 또는 타입 강제 변환이라고 한다.
할당 연산자(=
, +=
, -=
, *=
, /=
, %=
)는 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당한다.
할당 연산자는 좌항의 변수에 값을 할당하므로 변수 값이 변하는 부수 효과가 있다.
할당문은 변수에 값을 할당하는 부수 효과만 있고 값으로 평가되지 않는 것처럼 보이지만, 값으로 평가되는 표현식인 문으로서 할당된 값으로 평가된다.
비교 연산자(==
, ===
, !=
, !==
)는 좌항과 우항의 피연산자를 비교한 결과를 boolean 값으로 반환한다.
동등 비교(==
) 연산자는 좌항과 우항의 피연산자를 비교할 때 암묵적 타입 변환으로 타입을 일치시킨 후에 같은 값인지를 비교한다.
일치 비교(===
) 연산자는 좌항과 우항의 피연산자가 타입, 값이 같은 경우에 한해 true를 반환한다.
NaN === NaN; // false
NaN은 자신과 일치하지 않는 유일한 값이다.
숫자가 NaN인지 조사하려면 빌트인 함수 Number.isNaN을 사용해야 한다.
0 === -0; // true
자바스크립트에는 양의 0과 음의 0이 있는데 이들을 비교하면 true를 반환한다.
ES6에서 도입된 Object.is 메서드는 정확한 비교 결과를 반환한다.
Object.is(-0, +0); // false
대소 관계 비교 연산자(>
, <
, >=
, <=
)는 피연산자의 크기를 비교해 boolean 값을 반환한다.
삼항 조건 연산자는 조건식의 평가 결과에 따라 반환할 값을 결정한다.
삼항 조건 연산자는 첫 번째 피연산자가 true로 평가되면 두 번째 피연산자를 반환하고, 첫 번째 피연산자가 false로 평가되면 세 번째 피연산자를 반환한다.
물음표(?
) 앞의 첫 번째 피연산자는 조건식, 즉 boolean 타입으로 평가될 표현식이다. 이 때 조건식이 참이면 콜론(:
) 앞의 두 번째 피연산자가 평가되어 반환되고, 거짓이면 콜론(:
) 뒤의 세 번째 피연산자가 평가되어 반환된다.
if...else문을 사용해도 되지만 표현식이 아니기 때문에, 삼항 조건 연산자 표현식은 값처럼 사용할 수 있지만 if...else문은 값처럼 사용할 수 없다.
논리 연산자는 우항과 좌항의 피연산자를 논리 연산한다.
||
OR
&&
AND
!
NOT
논리 부정(!
) 연산자는 언제나 boolean 값을 반환한다.
!0; // true
!'hello'; // false
쉼표(,
) 연산자는 왼쪽 피연산자부터 차례대로 평가하고 마지막 피연산자의 평가가 끝나면 마지막 피연산자의 평가 결과를 반환한다.
var x, y, z;
x = 1, y = 2, z = 3; // 3
소괄호(()
)로 피연산자를 감싸는 그룹 연산자는 자신의 피연산자인 표현식을 가장 먼저 평가하기 때문에 우선순위를 조절할 수 있다.
typeof 연산자는 피연산자의 데이터 타입을 7가지 문자열(string
, number
, boolean
, undefined
, symbol
, object
, function
)로 반환한다.
주의🧐
typeof(null); // "object"
값이 null 값인지 확인할 때는 일치 연산자(===
) 사용해주기
ES7에서 도입된 지수 연산자는 좌항의 피연산자를 밑으로, 우항의 피연산자를 지수로 거듭제곱해 숫자 값을 반환한다.
2 ** 2; // 4
2 ** 0; // 1
2 ** -2; // 0.25
지수 연산자 도입 이전에는 Math.pow 메서드를 사용했다.
Math.pow(2, 2); // 4
Math.pow(2, 0); // 1
Math.pow(2, -2); // 0.25
음수를 거듭제곱의 밑으로 사용해 계산하려면 괄호로 묶어준다.
(-5) ** 2; // 25
지수 연산자는 다른 산술 연산자와 마찬가지로 할당 연산자와 함께 사용할 수 있고(**=
), 이항 연산자 중에서 우선순위가 가장 높다.
?.
옵셔널 체이닝
??
null 병합
delete
프로퍼티 삭제
new
생성자 함수 호출할 때 인스턴스 생성
instanceof
좌변 객체가 우변 생성자 함수와 연결된 인스턴스인지 판별
in
프로퍼티 존재 확인
대부분의 연산자는 다른 코드에 영향을 주지 않지만 일부 연산자는 다른 코드에 영향을 주는 부수 효과가 있다.
할당 연산자(=
), 증가/감소 연산자(++/--
), delete 연산자
연산자 우선순위란 여러 개의 연산자로 이루어진 문이 실행될 때 연산자가 실행되는 순서를 말한다.
1: ()
2: new
(매개변수 존재), []
(프로퍼티 접근), ()
(함수 호출), ?.
(옵셔널 체이닝)
3: new
(매개변수 미존재)
4: x++
, x--
5: !x
, +x
, -x
, ++x
, --x
, typeof
, delete
6: **
(이항 연산자 중 가장 높다)
7: +
, -
8: <
, <=
, >
, >=
, in
, instanceof
10: ==
, !=
, ===
, !==
11: ??
(null 병합 연산자)
12: &&
13: ||
14: ? ... : ...
15: 할당 연산자(=
, +=
, -=
, ...)
16: ,
연산자 결합 순서란 연산자의 어느 쪽부터 평가할 것인지 나타내는 순서를 말한다.
+
, -
, /
, %
, <
, <=
, >
, >=
, &&
, ||
, []
, ()
, ??
, ?.
, in
, instanceof
++
, --
, 할당 연산자(=
, +=
, -=
, ...), !x
, +x
, -x
, ++x
, --x
, typeof
, delete
, ? ... : ...
, **
08장 제어문
제어문은 조건에 따라 코드 블록을 실행하거나 반복할 때 사용한다.
일반적으로 코드는 위에서 아래 방향으로 실행되는데 제어문을 사용하면 코드의 실행 흐름을 인위적으로 제어할 수 있다.
블록문은 0개 이상의 문을 중괄호로 묶은 것이다. 자바스크립트는 블록문을 하나의 실행 단위로 취급한다.
조건문은 주어진 조건식의 평가 결과에 따라 코드 블록의 실행을 결정한다.
if...else문은 주어진 조건식의 평가 결과인 참 또는 거짓에 따라 실행할 코드 블록을 결정한다. 평가 결과가 true일 경우 if문의 코드 블록이 실행되고 false일 경우 else문의 코드 블록이 실행된다.
실행될 코드 블록을 늘리고 싶으면 else if문을 사용한다.
else if문과 else문은 사용할 수도 있고 사용하지 않을 수도 있다. else if문은 여러 번 사용할 수 있다.
조건에 따라 단순히 값을 결정해 변수에 할당하는 경우 if...else문보다 삼항 조건 연산자를 사용하는 것이 가독성이 좋다. 하지만 조건에 따라 실행할 내용이 복잡하다면 if...else문을 사용하는 것이 가독성이 좋다.
var num = 2;
var kind;
if (num > 0) kind = '양수';
else if (num < 0) kind = '음수';
else kind = '영';
console.log(kind); // 양수
switch문은 주어진 표현식을 평가해 그 값과 일치하는 표현식을 갖는 case문으로 실행 흐름을 옮긴다.
case문은 상황을 의미하는 표현식을 지정하고 콜론으로 마친다.
switch문의 표현식과 일치하는 case문이 없다면 실행 순서는 default문으로 이동한다. default문은 사용할 수도 있고 사용하지 않을 수도 있다.
switch (표현식) {
case 표현식1:
switch문의 표현식과 표현식1이 일치하면 실행;
break;
case 표현식2:
switch문의 표현식과 표현식2가 일치하면 실행;
break;
default:
일치하는 case문이 없을 때 실행;
}
break문은 코드 블록에서 탈출하는 역할을 한다. break문을 사용하지 않으면 case문의 표현식과 일치하지 않아도 실행 흐름이 다음 case문으로 이동한다.
if...else문은 논리적 참, 거짓으로 실행할 코드 블록을 결정하고, switch문은 다양한 상황에 따라 실행할 코드 블록을 결정할 때 사용한다.
반복문(for문
, while문
, do...while문
)은 조건식의 평가 결과가 참인 경우 코드 블록을 실행한다. 이는 조건식이 거짓일 때까지 반복된다.
for문은 조건식이 거짓으로 평가될 때까지 코드를 반복 실행한다.
for(var i = 0; i < 2; i++) {
변수 선언문 조건식 증감식
console.log(i);
}
0
1
for문의 변수 선언문, 조건식, 증감식은 모두 옵션이다. 단, 어떤 식도 선언하지 않으면 무한루프가 된다.
for문 내에 for문을 중첩해 사용할 수 있다.
while문은 주어진 조건식의 평가 결과가 참이면 코드 블록을 반복 실행한다. 평가 결과가 거짓이 되면 코드 블록을 실행하지 않고 종료한다.
for문은 반복 횟수가 명확할 때 주로 사용하고, while문은 반복 횟수가 불명확할 때 주로 사용한다.
평가 결과가 언제나 참이면 무한루프가 되기 때문에 코드 블록 내에 if문으로 탈출 조건을 만들고 break문으로 코드 블록을 탈출한다.
var count = 0;
while (true) {
console.log(count);
count++;
if (count === 3) break;
}
// 0 1 2
do...while문은 코드 블록을 먼저 실행하고 조건식을 평가한다. 따라서 코드 블록은 한 번 이상 무조건 실행된다.
var count = 0;
do {
console.log(count); // 0 1 2
count++;
} while (count < 3);
break문은 코드 블록을 탈출한다기보다는 레이블문
, 반복문
또는 switch문
의 코드 블록을 탈출한다.
레이블문은 식별자가 붙은 문을 말하는데,
// foo라는 레이블 식별자가 붙은 레이블문
foo: console.log('foo');
레이블문은 프로그램의 실행 순서를 제어하는 데 사용한다. switch문의 case문과 default문도 레이블문이다.
레이블문을 탈출하려면 break문에 레이블 식별자를 지정한다.
foo: {
console.log(1);
break foo;
console.log(2);
}
console.log('Done')
중첩된 for문의 내부 for문에서 break문을 실행하면 외부 for문으로 진입한다. 이 때 내부 for문이 아닌 외부 for문을 탈출하려면 레이블문을 사용한다.
outer: for(var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
// i + j === 3이면 outer라는 식별자가 붙은 for문을 탈출
if (i + j === 3) break outer;
console.log(`inner [${i}, ${j}]`);
}
}
console.log('Done');
레이블문은 중첩된 for문 외부로 탈출할 때 유용하지만 일반적으로 프로그램 흐름이 복잡해져 가독성이 좋지 않고 오류 발생 가능성이 있어 권장하지 않는다.
continue문은 반복문의 코드 블록 실행을 현 지점에서 중단하고 반복문의 증감식으로 실행 흐름을 이동시킨다.
var string = "Hello World";
var search = "l";
var count = 0;
// 문자열은 유사배열로 for문으로 순회할 수 있다.
for(var i = 0; i < string.length; i++) {
// 'l'이 아니면 현 지점에서 실행 중단하고 증감식으로 이동
if (string[i] !== search) continue;
count++;
}
console.log(count); // 3
09장 타입 변환과 단축 평가
자바스크립트의 모든 값에는 타입이 있다. 개발자가 의도적으로 값의 타입을 변환하는 것을 명시적 타입 변환 또는 타입 캐스팅이라고 한다.
(개발자의 의도와는 상관없이 표현식 평가 도중 자바스크립트 엔진에 의해 타입이 자동 변환되는 것은 암묵적 타입 변환 또는 타입 강제 변환)
명시적 타입 변환이나 암묵적 타입 변환이 기존 원시 값을 직접 변경하는 것은 아니다. 원시 값은 변경 불가능한 값이므로 변경할 수 없다.
타입 변환은 기존 원시 값을 사용해 다른 타입의 새로운 원시 값을 생성하는 것이다.
자바스크립트 엔진은 표현식을 에러 없이 평가하기 위해 피연산자의 값을 암묵적 타입 변환해서 새로운 타입의 값을 만들어 한 번만 사용하고 버린다.
명시적 타입 변환은 타입을 변경하겠다는 개발자의 의지가 코드에 드러나지만, 암묵적 타입 변환은 자동 변환되기 때문에 그렇지 않다.
따라서 작성한 코드에서 암묵적 타입 변환이 발생하는지, 발생한다면 어떤 타입의 어떤 값으로 변환되고, 표현식이 어떻게 평가될 것인지 예측할 수 있어야 한다.
그렇다면 명시적 타입 변환만 사용하고 암묵적 타입 변환은 발생하지 않도록 하면 될까?🧐
때로는 명시적 타입 변환보다 암묵적 타입 변환이 가독성 측면에서 좋다. (10).toString()보다 10+''이 더 간결하다.
표현식을 평가할 때 피연산자가 모두 문자열 타입이거나 모두 숫자 타입이어야 하는 문맥일 때 에러가 발생하기도 하지만, 자바스크립트는 가급적 에러를 발생시키지 않도록 암묵적 타입 변환을 통해 표현식을 평가한다.
1 + '2' // '12'
+
연산자는 피연산자 중 하나 이상이 문자열이므로 문자열 연결 연산자로 동작한다. 문자열 연산자의 역할은 문자열 값을 만드는 것이기 때문에 코드 문맥 상 모두 문자열 타입이어야 한다.
연산자 표현식의 피연산자만이 암묵적 타입 변환의 대상이 되는 것은 아니다.
ES6에서 도입된 템플릿 리터럴의 표현식 삽입은 표현식의 평가 결과를 문자열 타입으로 암묵적 타입 변환한다.
`1 + 1 = ${1 + 1}` // '1 + 1 = 2'
자바스크립트 엔진은 문자열 타입이 아닌 값을 문자열 타입으로 암묵적 타입 변환할 때 다음과 같이 동작한다.
// 숫자 타입
0 + '' // '0'
-0 + '' // '0'
1 + '' // '1'
-1 + '' // '-1'
NaN + '' // 'NaN'
Infinity + '' // 'Infinity'
-Infinity + '' // 'Infinity'
// boolean 타입
true + '' // 'true'
false + '' // 'false'
// null 타입
null + '' // 'null'
// undefined 타입
undefined + '' // 'undefined'
// symbol 타입
(Symbol()) + '' // TypeError
// 객체 타입
({}) + '' // '[object Object]'
Math + '' // '[object Math]'
[] + '' // ''
[10, 20] + '' // '10, 20'
(function(){}) + '' // 'function(){}'
Array + '' // 'function Array() { [native code] }'
1 - '1' // 0
1 * '10' // 10
1 / 'one' // NaN
산술 연산자의 역할은 숫자 값을 만드는 것이다. 이 때 피연산자를 숫자 타입으로 변환할 수 없는 경우는 산술 연산을 수행할 수 없어 표현식의 평가 결과가 NaN이 된다.
'1' > 0 // true
비교 연산자의 역할은 boolean 값을 만드는 것이다. 자바스크립트 엔진은 숫자 타입이 아닌 값을 숫자 타입으로 암묵적 타입 변환할 때 다음과 같이 동작한다.
// 문자열 타입
+'' // 0
+'0' // 0
+'1' // 1
+'string; // NaN
// boolean 타입
+true // 1
+false // 0
// null 타입
+null // 0
// undefined 타입
+undefined // NaN
// Symbol 타입
+Symbol() // TypeError
// 객체 타입
+{} // NaN
+[] // 0
+[10, 20] // NaN
+(function(){}) // NaN
빈 문자열(''
), 빈 배열([]
), null, false는 0으로, true는 1로 변환된다. 객체와 빈 배열이 아닌 배열, undefined는 변환되지 않는다.
if문이나 for문 같은 제어문 또는 삼항 조건 연산자의 조건은 참과 거짓으로 평가되어야 하는 표현식이다.
if ('') console.log('1');
if (true) console.log('2');
if (0) console.log('3');
if ('str') console.log('4');
if (null) console.log('5');
// 2 4
이때 자바스크립트 엔진은 boolean 타입이 아닌 값을 Truthy한 값(참으로 평가되는 값 ) 또는 Falsy 값(거짓으로 평가되는 값 )으로 구분한다.
즉, 제어문의 조건식과 같이 boolean 값으로 평가되어야 할 문맥에서 Truthy 값은 true로, Falsy 값은 false로 암묵적 타입 변환된다.
false
undefined
null
0, -0
NaN
''(빈 문자열)
Falsy 값 외의 모든 값은 모두 true로 평가되는 Truthy한 값이다.
개발자의 의도에 따라 명시적으로 타입을 변경하는 방법은 다양하다.
🧐
표준 빌트인 생성자 함수는 객체를 생성하기 위한 함수로, new 연산자와 함께 호출한다.
표준 빌트인 메서드는 자바스크립트에서 기본 제공하는 빌트인 객체의 메서드이다.
문자열 타입이 아닌 값을 문자열 타입으로 변환하는 방법
String(1); // '1'
(1).toString(); // '1'
1 + ''; // '1'
숫자 타입이 아닌 값을 숫자 타입으로 변환하는 방법
+
단항 산술 연산자 이용*
산술 연산자 이용Number('0'); // 0
parseInt('0') // 0
+'0'; // 0
'0' * 1; // 0
boolean 타입이 아닌 값을 boolean 타입으로 변환하는 방법
!
부정 논리 연산자 두 번 사용Boolean('false'); // true
Boolean(''); // false
Boolean(0); // false
Boolean(NaN); // false
Boolean(Infinity); // true
Boolean({})' // true
Boolean([]); // true
!!'false'; // true
!!''; // false
!!0; // false
!!NaN; // false
!!Infinity; // true
!!{}; // true
!![]; // true
논리합(||
) 또는 논리곱(&&
) 연산자 표현식의 평가 결과는 boolean 값이 아닐 수도 있다. 논리합(||
) 또는 논리곱(&&
) 연산자 표현식은 언제나 2개의 피연산자 중 어느 한쪽으로 평가된다.
'Cat' && 'Dog' // 'Dog'
논리곱(&&
) 연산자는 두 개의 피연산자가 모두 true일 때 true를 반환한다.
첫 번째 피연산자 'Cat'은 Truthy한 값이므로 true로 평가된다. 하지만 두 번째 피연산자까지 평가해야 이 표현식을 평가할 수 있다. 즉, 두 번째 피연산자가 평가 결과를 결정한다. 이 때 논리곱 연산자는 논리 연산의 결과를 결정하는 두 번째 피연산자인 'Dog'를 반환한다.
(논리합(||
) 연산자도 동일하게 동작 )
'Cat' || 'Dog' // 'Cat'
논리합(||
) 연산자는 두 개의 피연산자 중 하나만 true로 평가되어도 true를 반환한다.
첫 번째 피연산자 'Cat'은 Truthy한 값이므로 true로 평가된다. 두 번째 피연산자까지 평가하지 않아도 이 표현식을 평가할 수 있다. 이 때 논리합 연산자는 논리 연산의 결과를 결정한 첫 번째 피연산자인 'Cat'을 반환한다.
논리곱 연산자와 논리합 연산자는 논리 연산의 결과를 결정하는 피연산자를 타입 변환하지 않고 그대로 반환한다.
이것을 단축 평가라고 한다!
단축 평가는 표현식을 평가하는 도중 결과가 확정된 경우 나머지 평가 과정을 생략하는 것을 말한다.
true || anything // true
false || anything // anything
true && anything // anything
false && anything // false
어떤 조건이 Truthy한 값일 때 무언가를 해야 한다면 논리곱(&&
) 연산자 표현식으로 if문을 대체할 수 있다.
var done = true;
var message = '';
if (done) message = '완료';
// done이 true라면 message에 '완료'를 할당
message = done && '완료';
console.log(message); // 완료
조건이 Falsy 값일 때 무언가를 해야한 다면 논리합(||
) 연산자 표현식으로 if문을 대체할 수 있다.
var done = false;
var message = '';
if (!done) message = '미완료';
// done이 false라면 message에 '미완료'를 할당
message = done || '미완료';
console.log(message); // 미완료
객체는 키와 값으로 구성된 프로퍼티의 집합이다. 만약 객체를 가리키기를 기대하는 변수의 값이 객체가 아니라 null 또는 undefined인 경우 객체 프로퍼티를 참조하면 타입 에러가 발생한다.
var elem = null;
var value - elem.value; // TypeError
이 때 단축 평가를 사용하면 에러를 발생시키지 않는다.
var elem = null;
var value = elem && elem.value; // null
함수를 호출할 때 인수를 전달하지 않으면 매개변수에는 undefined가 할당되는데, 이 때 단축 평가를 사용해 매개변수의 기본값을 설정하면 undefined로 인해 발생할 수 있는 에러를 방지할 수 있다.
function getStringLength(str) {
str = str || '';
return str.length;
}
getStringLength(); // 0
getStringLength('hi'); // 2
function getStringLength(str = '') {
return str.length;
}
getStringLength(); // 0
getStringLength('hi'); // 2
ES11에서 도입된 옵셔널 체이닝 연산자(?.
)는 좌항의 피연산자가 null 또는 undefined인 경우 undefined를 반환하고, 그렇지 않으면 우항의 프로퍼티 참조를 이어간다.
var elem = null;
var value = elem?.value;
console.log(value); // undefined
옵셔널 체이닝 연산자는 객체를 가리키기를 기대하는 변수가 null 또는 undefined가 아닌지 확인하고 프로터피를 참조할 때 유용하다.
옵셔널 체이닝 연산자가 도입되기 전에는 논리 연산자 &&
를 사용한 단축 평가를 통해 변수가 null 또는 undefined인지 확인했다.
var elem = null;
var value = elem && elem.value;
console.log(value); // null
논리 연산자 &&
는 좌항 피연산자가 Falsy 값이면 좌항 피연산자를 그대로 반환한다. 좌항 피연산자가 0이나 ''인 경우도 마찬가지이다. 하지만 0이나 ''은 객체로 평가될 때도 있다.
var str = '';
var length = str && str.length;
// 문자열의 길이를 참조하지 못한다.
console.log(length); // ''
하지만 옵셔널 체이닝 연산자는 좌항 피연산자가 Falsy 값이라도 null 또는 undefined가 아니면 우항의 프로퍼티 참조를 이어간다.
var str = '';
var length = str?.length;
console.log(length); // 0
ES11에서 도입된 null 병합 연산자??
는 좌항의 피연산자가 null 또는 undefined인 경우 우항의 피연산자를 반환하고, 그렇지 않으면 좌항의 피연산자를 반환한다.
null 병합 연산자는 변수에 기본값을 설정할 때 유용하다.
var foo = null ?? 'default string';
console.log(foo); // 'default string'
null 병합 연산자가 도입되기 전에는 논리 연산자 ||
를 사용한 단축 평가를 통해 변수에 기본값을 설정했다.
좌항의 피연산자가 Falsy 값이면 우항의 피연산자를 반환한다.
var foo = '' || 'default string';
console.log(foo); // 'default string'
하지만 null 병합 연산자는 좌항의 피연산자가 Falsy 값이라도 null 또는 undefined가 아니면 좌항의 피연산자를 그대로 반환한다.
var foo = '' ?? 'default string';
console.log(foo); // ''