연산자(operator)는 하나 이상의 표현식을 대상으로 일정 수행을 거쳐서 하나의 값을 만든다. 이때 연산의 대상을 피 연산자라고 한다. 한마디로 연산자는 값으로 평가된 피연산자를 연산해 새로운 값을 생성한다.
7-1 산술연산자
말 그대로 수학적 계산을 통해서 새로운 숫자 값을 생성한다. 산술 연산이 불가능한 경우 NaN(not a number) 을 반환한다.
5 + 2;
5 - 2;
5 * 2;
5 / 2; // 2.5
5 % 2; // 1
위 코드처럼 피 연산자 2개를 산술 연산하여 숫자값을 생성하는 것을 이항(binary) 산술 연산자 라고 한다. 위 경우를 보면 알겠지만, 이 연산자는 새로운 값을 생성하는 것이지, 기존의 피연산자를 변경하진 않는다.
var x = 1;
//++ 연산자는 피연산자의 값을 변경하는 암묵적 할당이 이뤄진다.
x++; // x = x + 1
console.log(x); // 2
// -- 연산자는 피연산자의 값을 변경하는 암묵적 할당이 이뤄진다.
x--; // x = x - 1
console.log(x); // 1
반면 위 코드처럼 피 연산자 1개를 산술 연산하여 숫자 값을 만드는 것을 단항(unary) 산술 연산자 라고 한다. 코드에서 확인 할 수 있듯이, 단항 산술 연산자 중 증가/감소(++/--) 연산자는 피연산자의 값을 변경하는 부수 효과(side effect) 가 있다는 점이다. 실제로 피연산자 x 의 값을 변경시키고 있다.
이러한 증가와 감소 단항 산술 연산자는 이 연산자가 변수의 어느 위치에 있느냐에 따라 그 의미가 달라진다.
var x = 5, result;
//선할당 후 증가
result = x++;
console.log(result, x) // 5 6
//선증가 후 할당
result = ++x;
console.log(result ,x) // 7 7
//선할당 후 감소
result = x--;
console.log(result, x) // 7 6
//선감소 후 할당
result = --x;
console.log(result, x) // 5 5
햇갈릴 수 있지만, 좀 간단히 생각을 해보자면, x = x + 1 이니깐 result = x = x + 1 이렇게 치환해서 생각을 해보자. 그렇다면 왼쪽 result = x 에서 x가 먼저 result 에 할당이 되고, 그 다음 x + 1 이 x 에 할당이 되는거니, 이런식으로 이해를 해보자.
이와 대조적으로 단항 + 같은 경우 피연산자에 어떠한 효과도 없다. 다만, 다른 타입에 + 나 - 를 붙이면 그 값으로 숫자를 내보낸다. 하지만 원본은 그대로이기 때문에 부수 효과는 없다.
또 다른 경우라면, + 연산자의 경우 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작을 한다는 점이다. 그외 다른 속성들을 살펴보자면,
'1' + 2; // '12'
1 + '2'; // '12'
1 + true; // 2(true는 1타입으로 변환)
1 + false; //1 (false는 0)
1 + null // 1 (null 은 0)
//undefined 는 숫자로 타입변환 되지 않는다.
여기서 주목할 점은 개발자의 의도와 관계없이 1 + true 가 2가 됬다는 점이다. 이렇게 된 이유는 암묵적으로 타입이 변경이 되어버린 경우인데, 이를 암묵적 타입 변환(implicit coercion) 또는 타입 강제 변환(type coercion) 이라고 한다.
추후 자세히 살펴보겠지만, 분명 개발자의 의도와 다른 결과가 발생한 예시이다. 이러한 점 때문에 혹여나 프로그램에 오류가 발생할 수 있으므로 현재 타입스크립트를 장려하는 이유가 이런 부분에 있다.
7-2. 할당 연산자
할당 연산자(assignment operator)는 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당한다. 쉽게 말해 좌항의 변수에 값을 할당한다고 생각하자.
var x;
x = 10;
console.log(x) // 10
x += 5; // x = x + 5 (이렇게 좌항의 값을 변수에 할당하기에 변수의 값이 변하는 부수효과가 발생한다)
console.log(x) // 15
x -= 5; // x = x -5
console.log(x) // 10
x *= 5;
console.log(x) // 50
x /= 5;
console.log(x) // 10
x %= 5;
console.log(x) // 0
var str = "My name is ";
str += "Lee";
console.log(str) // "My name is Lee"
코드를 작성하거나, 남이 만든 코드를 보게 되면, 그리고 그냥 인간적인 관점에서 가능할 것이라 판단할 수 있는 연쇄할당에 대해 본적이 있을 것이다.
var a, b, c;
//연쇄 할당. 오른쪽에서 왼쪽으로 진행
a = b = c = 0;
console.log(a, b, c); // 0 0 0
당연하게도 셋 다 0일 수 있다는 것을 예측할 수 있지만, 사실 이는 인간의 관점에서 살펴본 것이고, 할당문이 값으로 평가되는 표현식이기에 가능한 것이다. 즉 c = 0 이 0 이라는 값으로 평가된다는 의미이다. 이런식으로 오른쪽에서 왼쪽으로 타고타고 들어가다 보니 셋 다 0 이라는 값을 가리키는 것이다.
7-3. 비교 연산자
비교 연산자(comparison operator) 는 좌항과 우항의 피연산자를 비교한 다음 그 결과를 불리언 값으로 반환한다.
반환값이 불리언이기에 조건식에서 많이 활용이 되는데, 여기서 따져봐야 하는것이 비교하는 엄격성이다. 언어적으로 좀 더 쉽게 이해해보자면 비교하는 수준에 따라 true 가 될수도 있고 false 가 될 수도 있다는 의미다. 방식에 따라 값이 변화한다면 이를 통해 조건을 걸어야 하는 입장에서는 예민해질 수 있다.
엄격도에 따라 크게 동등 비교(loose equality) 와 일치 비교(static equality) 로 나뉘게 된다. 한글보다 영어로 익혀보는게 좀 더 기억이 쉽게 날 것이다. (느슨함과 엄격함)
// 동등 비교
5 == 5; // true
// 타입은 다르지만 암묵적 타입 변환을 통해 타입을 일치시키면 동등하다.
5 == '5'; // true
// 결과 예측이 힘들 수 있다.
'0' == ' ' // false;
0 == ' '; // true (?)
0 == '0' // true
false == 'false' // false
false == '0' // true...
....
동등 비교(==) 연산자는 좌항과 우항의 피연산자를 비교할 때 먼저 암묵적 타입 변환을 통해 타입을 일치 시킨 후 같은 값인지 비교한다. 이러한 이유로서 숫자 5와 문자 5가 같다는 결과값을 반환하게 된다. 그래서 위처럼 이상한 결과를 도출한다.
이렇기 때문에 동등비교보단 일치비교(===) 를 사용한다.
5 === 5; // true;
5 === '5' // false;
// 암묵적 타입 변환을 하지 않고 값 비교를 한다.
일치 비교(===) 연산자는 좌항과 우항의 피연산자가 타입도 같고 값도 같은 경우에 한하여 true 를 반환한다.
이러한 일치 비교에도 주의할 부분이 있는데, NaN 과 0 이다. 예제를 보면서 그냥 그렇구나 하고 익히면 될 것이다.
NaN === NaN // false;
// 숫자가 NaN인지 조사하기 위해 빌트인 함수 Number.isNaN 을 사용한다.
Number.isNaN(NaN) // true
Number.isNaN(0) // false
Number.isNaN(1 + undefined) // true
// 숫자 0도 주의하자. 자바스크립트에는 양과 음의 0이 있다.
0 === -0; // true
0 == -0; // true
ES6에서 도입된 Object.is 를 사용한다면 정확한 비교 결과를 반환한다. Object.is(NaN, NaN) 은 true 다.
대소 관계 비교 연산자 역시 불리언 값을 반환한다. 이 연산자는 우리가 수학때 알고있던 의미 그대로이다.
5 > 0; // true
5 > 5 // false
5 >= 5 // true
5 <= 5 // true
7-4. 삼항 조건 연산자
리엑트에서 가장 많이 사용되는 조건 연산자로서, 삼항 조건 연산자(ternary operator) 는 조건식의 평가 결과에 따라 반환할 값을 결정한다. 자바스크립트의 유일한 삼항 연산자이며, 부수효과는 없다.
var result = result > 60 ? 'pass' : 'fail'
// 조건식 ? 조건식이 true 일때 반환할 값 : 조건식이 false 일때 반환할 값
설명에 적어 두었듯이 첫 번째 피 연산자가 true 라면 값으로 두번째 피연산자(pass) 를 반환한다. 아니라면 세번째 피연산자(fail) 를 반환한다. 첫번째 피연산자는 불리언 타입으로 평가될 표현식이다. 만일 첫번째 피연산자가 불리언 타입의 평가가 되는 것이 아니라면 암묵적으로 타입으로 변환된다. 예를 들자면
var x = 2;
var result = x % 2 ? '홀수' : '짝수';
console.log(result) // 짝수
// 조건문으로 변경할 시
if(x % 2) result = '홀수'
else result = '짝수'
console.log(result) // 짝수
x의 2로 나눈 나머지 값은 0이다. 불리언 값은 아니지만, 삼항 조건식에 들어가면서 자동적으로 변환된다. 0 은 false 이므로 뒤에 짝수가 반환된다. 위 조건식은 if ...else... 로 변경이 가능하다.
그렇다면 궁금할 수 있다. 왜 조건식 표현이 2가지로 나뉘는건가. 좀 더 간단하게 표현할 수 있기 때문인가? 물론 그러한 이유도 있겠지만, 삼항 조건식의 중요한 의의는 값으로 평가될 수 있는 표현식 이라는 점에 있다. 즉, 변수에 값으로 할당될 수 있다는 의미!
var x = 10;
var result = if(..){result = ..}else(..){result = ..};
// SyntaxError: Unexpected token if
var result = x % 2 ? "홀수" : "짝수";
console.log(result) // 짝수
일정한 변수에 조건에 따라 값을 할당하는 경우 삼항 조건식이 편리할 것이다. 다만 조건문이 중첩이 되기 시작한다면 if else 를 활용하는것이 훨씬 깔끔하다.
7-5. 논리 연산자
논리 연산자(logical operator) 는 우항과 좌항의 피연산자(부정 논리 연산자의 경우 우항의 피연산자)를 논리 연산 한다. 예시로 보는것이 훨씬 이해가 빠르다.
true || true; // true
true || false // true
false || true // true
false || false // false
true && true // true
true && false // false
false && true // false
false && false // false
!true // false
!false // true
상식적으로 알고 있는 논리 연산자로 생각하면 되기도 하고, 좀 더 나아가서 생각해보자면 사실 이러한 연산자는 식과 식 사이에도 사용될 수 있는데, 리엑트에서 조건부 랜더링을 구현할 때 자주 이용되는 개념이다.
논리합 연산자의 경우 좌항이 true 라면 우항의 조건은 따지지 않겠다는 의미다. 허나 좌항이 false 라면 우항의 조건을 따져보겠다는 의미이다. 이러한 의미로 true 우선이다.
반대로 논리곱 연산자의 경우 좌항이 true 라면 우항의 조건을 따져보겠다는 의미다. 좌항이 true 이며 우항을 따져보니 true 라면 그제서야 true 를 반환하겠다는 의미다. 이러한 의미로 false 가 우선이다.
const function = () => {
const [result, setResult] = useState(false);
return (
<>
{result && <div>....
리엑트에서 화면을 렌더링할 때 result 값이 false 라면 그 뒤 논리곱 연산자 우항부분의 조건을 따져보지 않겠다는 의미이다. 데이터를 가져와서 렌더링을 해야하는 경우처럼 순서가 우선시 될 때 사용되곤 한다.
7-6. 쉼표 연산자 && 그룹 연산자
쉼표 연산자는 왼쪽 피연산자부터 차례대로 피연산자를 평가하고 마지막 피연산자의 평가가 끝나면 마지막 피연산자의 평가 결과를 반환한다.
var x,y,z;
x = 1; y = 2; z = 3; // 3
소괄호로 피연산자를 감싸는 그룹 연산자는 자신이 피연산자인 표현식을 가장 먼저 평가한다. 그룹 연산자가 우선순위가 가장 높다. 그냥 상식적으로 괄호 친 부분이 우선순위가 더 놓다.
7-8. typeof 연산자
이 연산자는 피연산자의 데이터 타입을 문자열로 반환한다. 총 7가지 문자열을 반환하는데 예제를 보자
typeof '' // string
typeof 1 // number
typeof NaN // number
typeof true // boolean
typeof undefined // undefined
typeof symbol() // symbol
typeof null // object ... 오류다.
typeof [] // object
typeof {} // object
typeof new Date() // object
typeof function(){} // function
//변수 undeclared 를 앞에서 선언하지 않았을 경우 에러대신 undefined 가 뜬다.
typeof undeclared // undefined
null 의 경우를 비교할때는 === 을 이용하자.
7-9. 지수 연산자
ES7 에 도입된 지수 연산자는 좌항의 피연산자를 밑으로 우항의 피연산자를 지수로 거듭 제곱하여 숫자 값을 반환한다.
2 ** 2; // 4
2 ** 0 // 1
2 ** -2 // 0.25
지수 연산자가 없을 때는 Math.pow 메서드를 사용했다. 가독성이 더 좋아졌다 할 수 있다.
2 ** (3 ** 2); // 512
Math.pow(2, Math.pow(3,2)); // 512
음수의 경우 괄호로 묶어서 사용을 해야하며, 다른 산술 연산자와 마찬가지로 할당 연산자와 함꼐 사용할 수 있다.
(-5) ** 2 // 25
var num = 5;
num **= 2; // 25
// 지수 연산자는 이항 연산자 중에서 우선순위가 가장 높다
2 * 5 ** 2; // 50
7-10. 그 외의 연산자들은...
그외 다양한 연산자가 있지만 이들은 차후 다른 주제와 관계가 되어있어서 해당 주제를 소개하는 장에서 살펴 보도록 하자.
이 장에서 다루지 못한 부분은 모던 자바스크립트 Deep Dive 책을 참고하면 될 것이다.
'Programing > Javascript' 카테고리의 다른 글
[Deep Dive] 타입 변환과 단축 평가 (0) | 2022.10.22 |
---|---|
[Deep Dive] 제어문 (0) | 2022.10.21 |
[Deep Dive] 데이터 타입 (2) | 2022.10.07 |
[Deep Dive] 표현식과 문 (0) | 2022.10.06 |
[Deep Dive] 변수 (0) | 2022.10.06 |