[JS] 모던 자바스크립트 Deep Dive 스터디 7장 연산자를 배우며 프로젝트에 어떻게 적용할지 알아보기 #해달

2024. 1. 19. 00:59_Web/JavaScript

728x90

 

 

 

 

자바스크립트로 개발하다보면 정말 수상한 오류가 발생한다. 이는 대부분 연산에서 발생하는 오류이며 연산자의 개념을 바로잡고가야 치명적인 오류에도 빠른 대처가 가능하다. 필자는 그동안의 프로젝트 경험에서 발생했던 수상한 오류의 원인을 찾아보며 정리하였다. 해당 문법을 프로젝트에선 어디에서 적용할 수 있을까?

 

 

 

#연산자 #피연산자 #부수효과 #암묵적타입변환 #타입강제변환

 


 

 

07장

연산자

 

 

연산자(operator)는 하나 이상의 표현싱을 대상으로 산술, 할당(=), 비교, 논리, 타입, 지수 연산(operation) 등을 수행해 하나의 값을 만든다. 연산의 대상을 피연산자(operand)라 하며 연산의 대상이 되어야 하므로 값으로 평가될 수 있는 표현식이어야 한다.

 

F12로 콘솔을 띄워 입력하면 다음과 같다.

 

 

자바스크립트는 동적 타입변환을 제공하기 때문에 산술연산에서 타입이 변환할 수 있다.

타입이 변해버리면 이후 연결되는 모든 로직에 문제가 생긴다.

 

따라서 산술연산자에서 발생하는 버그를 수정하기 위해서는 연산자에서 발생하는 타입변환을 유의해야한다.

아래에 발생하는 버그의 문제점을 찾아보자.

 

다음은 index가 있으면 이를 출력하는 함수이다. 기본적으로 1이 선택되어있다.

아래의 코드를 브라우저에 입력하여 확인해보면 '1'이 출력된다.

function check(index = 1) {
    answer = ""
    if (index) {
        answer += index
    }
    return answer
}

check();

 

 

다음과 같은 코드는 치명적인 오류가 발생할 수 있다.

 answer이 현재의 index번호일때, index를 증가하면 1+1 = 11번째가 될 수 있다.

이유는 내부의 answer자체가 문자열로 설정되어 있기 때문에 발생하는 오류이다.

 

 

따라서 반환된 값을 사용할 때는 항상 주의해야한다. index증가 값을 잘 확인했음에도 answer의 반환값을 index로 사용하기 위해 엮는 순간 코드는 크게 꼬이게 된다.  그 밖에도 치명적인 오류가 존재하지만 다른 연산자에서 알아보자. 따라서 부수효과를 최대한 줄이면서 프로그래밍을 진행해야한다. let 대신 const를 자주 사용하는 것도 오류를 줄이기 위해서다.

 

 

 

7.1. 산술연산자 (arithmetric operator)

 

산술연산자는 피연산자를 대상으로 수학적 계산을 수행해 새로운 숫자 값을 만든다.

NaN은 산술 연산이 불가능한 경우를 말하며 피연산자의 개수에 따라 이항 산술연사자와 단항 산술자를 구분 할 수 있다.

 

 

 

이항 산술연자(binary)는 2개의 피연산자를 산술 연산하여 숫자을 만든다.

 

 

모든 이항 산술연산자는 피연자의 값을 변경하는 부수효과(side effect)가 없다. 다시 말해, 어떤 산술 연산을 해도 피연산자의 값이 바뀌의 경우는 없고 새로운 값을 만들 뿐이다. 따라서 변수의 값이 변경되지 않는다는 의미이다.

 

first, second는 유지된다

 

5+2
5-2
5*2
5/2
5%2

 

 

단항 산술 연산자(unary)는 1개의 피연산자를 산술 연산하여 숫자 값을 만든다.

 

++ 증가

-- 감소

+ 어떠한 효과도 없다
- 양수를 음수로, 음수를 양수로 반전한 값을 반환한다.

 

증가/감소(++/--) 연산자는 피연산자의 값을 변경하는 부수 효과가 있으며 이는 암묵적 할당에 이뤄진다.

여기서 --x를 하면 1? 이 출력이 될까 2가 출력이 될까?
답은 1이다. 피연산자와 증감연산자의 위치는 연산 순서로 매우 중요하다.

아래의 계산에따라 콘솔에 어떤 값이 출력되는지 예측해보자.

 

var x = 5,result;
result = x++;
console.log(result, x);
result = ++x
console.log(result,x)
result = x--;
console.log(result,x)
result = --x;
console.log(result,x);

 

 

더보기

마지막에 5 5 가 출력됐는가? 4 4가 출력됐는가? 이런 간단한 실수가 개발하면서 오류가 발생할 수 있는 요인이 될 수 있다. 답은 5 5 가 맞으며 코드를 실행하지 않아도 이를 확신할 줄 알아야한다. ReferenceError처럼 값이 출력되는 곳에서도 에러가 발생할 수 있다는 점을 항상 유의해야한다.

 

 

단항연산자 중에서 +와 -를 사용하면 +는 아무 상관이 없지만 -는 수를 반전한다.

그렇지만 반환한 값을 따로 생성해서 반환하기 때문에 타입이 변환될 수 있다.

 

 

자바스크립트 스타일중에 에어비앤비 컨벤션을 살펴보면, 

https://github.com/parksb/javascript-style-guide

 

GitHub - parksb/javascript-style-guide: Airbnb JavaScript 스타일 가이드

Airbnb JavaScript 스타일 가이드. Contribute to parksb/javascript-style-guide development by creating an account on GitHub.

github.com

https://github.com/tipjs/javascript-style-guide

 

GitHub - tipjs/javascript-style-guide: Airbnb JavaScript 스타일 가이드 한국어

Airbnb JavaScript 스타일 가이드 한국어. Contribute to tipjs/javascript-style-guide development by creating an account on GitHub.

github.com

 

 

 

우테코프리코스 동료 코드리뷰 받았을 때 받은 피드백으로 예기치 못한 오류가 발생하니 += 1로 사용하라고 한다.

그러나 숫자같은 경우 직관적으로 예측이 가능하지만, 자바스크립트엔 다양한 타입이 있다.

 

 

이런걸 코드에 적용했다간 상상도 못한 오류가 발생할 것이다. 대부분 아차! 에서 발생한다.

 

 

// ⚠️ 오류. 왼쪽 값은 부수효과의 대상(변수)이어야 함
let str = '헬로' += '월드';

 

// ⚠️ 오류. 왼쪽 값은 부수효과의 대상(변수)이어야 함
const STR = '안녕~';
STR += ' 반가워요!';

 

let result = '안녕' + 1 + true;

console.log(result);
console.log(typeof result);

 

 

문자열 연결 연산자

index 문제처럼 문자열과 산술연산이 섞이게 되는경우, true, false마저 섞이게 되는경우 연산은 정말 헷갈리게 된다.

도서에 적혀있듯이, 개발자의 의도와는 상관없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환되므로 이를 암묵적 타입변환 (implicit coercion) 또는 타입 강제 변환(type coercion)이라고 한다.

 

 

 

7.2 할당연산자(assginment operator)

할당연산자는 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당하여 부수효과기 있다.

앞서 표현식은 값으로 평가될 수 있는 문이고, 문에는 표현식인 문과 표현식이 아닌 문이 있다고 했다.

그렇다면 할당문은 표현식인 문일까, 표현식이 아닌 문일까?

=> 표현식인 문이다. x=10이면 10으로 평가되기 때문!

 

할당문은 값으로 평가되는 표현식인 문으로서 할당된 값으로 평가된다.

a = b = c = 0; // 연쇄 할당

 

 

 

7.3 비교 연산자 (comparison operator)

 

비교 연산자(comparison operator)는 좌항과 우항의 피연산자를 비교한 다음, 그결과를 불리언 값으러 변환하며, if for문과 같은 제어문의 조건식에서 주로 이용한다.

 

동등비교(loose equality) 연산자일치 비교 (strict equality) 연산자는 좌항과 우항의 피연산자가 같은 값으로 평가되는 지 비교해 불리언 값을 반환한다. 하지만 비교하는 엄격성의 정도가 다르다.

 

 

동등 비교 연산자(==)는 좌항과 우항의 피연산자를 비교할 때 먼저 암묵적 타입 변환을 통해 타입을 일치한 후 같은 값인지 교한다. 따라서 타입은 다르더라도 같은 값이면 true를 반환한다.

 

일치 비교(===)연산자는 좌항과 우항의 피연산자가 타입도 같고 값도 같은 경우에 한하여 true를 반환한다.

 

하지만 결과를 예측하기 어렵고 실수하기 쉽다., 사용하지 않는 편이 좋다 라고 적혀있지만 필자는 얽히고 얽힌 코드에서 언제부턴가 type이 변경되어 다른 컴포넌트로 전달되어 어느 순간부터 ===가 돌아가지 않아 ==로 고쳐 버그를 고친 경험이 있다. 따라서 ===를 권장하지만 상황에 따라 빠른 버그수정을 해야한다면 느슨하게 바꿔버리는 것도 방법이다. 그야 아예 안돌아가면 대참사다.

타입스크립트로 이전해야지..... 망할

 

 

다음과 같은 경우는 예외처리에서 조심해야한다.

특히 유저아이디를 공백('')으로 설정하고 들어온다던가

아니면 이름이 undefined로 들어온다던가

이러면 유저가 없을 때로 처리되는 수상한 계정이 되버릴 수 있다.

 

백엔드의 java같은경우 string으로 타입명을 지정할 수 있기 때문에 DB에선 undefined같은경우 레어닉이 될 수 있지만 

클라이언트에서 입력받는 프론트엔드에서 '"는 대참사가 일어날 수 있다. '0'도 마찬가지...

백엔드에게 전해주기 전까지 프론트에서 이를 다루고있으면 수상한 버그가 발생한다.

 

 

일치 비교 연산자에서 주의해야할 것은 NaN으로 자신과 일치하지 않는 유일한 값이다.

이것도 우테코에서 Number.isNaN()으로 사용하라고 코드리뷰에서 받았다.

세상이 억까하네

 

 

 

그야 isNaN()만 썼다가 수상한 일이 발생했는데... 자세한 원인은 아래 블로그에 있다.

 

console.log(
  typeof '1', isNaN('1'), Number.isNaN('1')
); // 특정 숫자로 변환 가능한 문자

console.log(
  typeof true, isNaN(true), Number.isNaN(true)
); // true는 1, false는 0으로 변환됨

console.log(
  typeof 'a', isNaN('a'), Number.isNaN('a')
); // ⚠️ 특정 숫자로 변환 불가인 문자의 경우 차이

console.log(
  typeof (1/'a'), isNaN(1/'a'), Number.isNaN(1/'a')
); // NaN값인 경우

 

 

그러나 Number.isNaN()으로 쓰면 입력값에 저 값이 hello world?는 true! 그래 숫자다! 이렇게 처리해버리기 때문에 그냥 상상도 못한 로직으로 인해 코드가 박살났고 3주차 과제부터 예외처리로 인해 터져 하산했다.

 

 

 

https://velog.io/@pul8219/JS-NaN-isNaN%EA%B3%BC-Number.isNaN%EC%9D%98-%EC%B0%A8%EC%9D%B4

 

[JS] NaN, isNaN()과 Number.isNaN()의 차이

자바스크립트 NaN, Number.NaN, isNaN()과 Number.isNaN()의 차이

velog.io

 

 

 

 

 

부동등 비교연산자(!=), 불일치 비교연산자(!==)는 앞서 말한 동등비교 연산자와 연산자의 반대 개념이다.

 

 

 

대소 관계 비교 연산자

읽고 넘어가면 되는 부분이며 아래만 주의하면 된다.

 

// ⚠️ 숫자 문자열 관련 주의!
console.log(
  100 > 12, // 숫자는 그 자체로 비교
  '100' > '12', // 문자는 사전순으로 비교
  '100' > 12, // 문자와 숫자를 비교하면 문자를 숫자로 변환
);

 

 

 

7.4. 삼항 조건 연산자 (ternary operator)

 

삼항 조건 연산자는 조건식의 평가 결과에 따라 반환할 값을 결정한다.

자바스크립트의 유일한 삼항 연산자이며, 부수효과도 없다.

삼항 조건 연산자 표현식은 값으로 평가할 수 있는 표현식인 문이다.

사용할 수 없다

 

 

if else는 값처럼 사용할 수 없다는 게 중요한 다른 점이다.

 

 

 

 

7.5. 논리 연산자(logical operator)

논리연산자는 우항과 좌항의 피연산자(부정 논리 연산자의 경우 우항의 피연산자)를 논리 연산한다.

|| 는 or의 의미라서 둘 중 하나만 true여도 true고

&& 는 and의 의미라서 둘 중 하나만 false여도 false 이다.

 

 

해당 문법은 isPC와 같이현재 화면이 PC인지 모바일인지 파악하여 창을 보여줄지 안 보여줄지로 작성할 수 있다. 모달창도 마찬가지이다.

 

 

 

드모르간의 법칙을 이용하면 좀 더 가독성 높인 코드를 작성할 수 있다. 논리적 비판적 사고나 논리회로강의에 자주 나온다. 이는 앞에 어떤 연산 있어 복잡해진 식의 괄호를 풀어준다고 생각하면된다. 앞의 연산을 각 항에 적용하고 두 사이에 있는 연산을 반전한다.

!( x || y ) === (!x && !y)

 

더하기를 뽑아버리자 더하기를 곱으로 고치고 반전(위에 줄긋기)

 

아래와 같은 회로를 설계할때 모두 드모르간으로 고쳐버려서 NAND (·) 를 만들다거나 그런식으로 사용한다.

암튼 and <-> or로 바꾸고 괄호앞 연산만 각 항에 붙여주면 된다.

먼소리고.... 먼소리고....

사실 필자는 논리회로 중간고사부터 위태로웠다. 패스

그래 이게 낫다

 

 

 

7.6 쉼표 연산자

쉼표(,) 연산자는 왼쪽 피연산자 부터 차례대로 피연산자를 평가하고 마지막 피연산자의 평가가 끝나면 마지막 피연산자의 평가 결과를 반환한다.

 

var x,y,z;

x=1, y=2, z=3;

 

 

7.7. 그룹 연산자

가장 먼저 연산할 표현식을 정할 수 있다. 즉, 우선순위를 조정할 수 있다. 에어비엔비 코드 컨벤션에 따르면 다음과 같다.

 최대한 간결하게 작성하고 괄호를 통해 우선순위를 조절해야한다.

// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;

// bad
const bar = a ** b - 5 % d;

// bad
// (a || b) && c 으로 혼동할 수 있습니다.
if (a || b && c) {
  return d;
}

// good
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);

// good
const bar = (a ** b) - (5 % d);

// good
if (a || (b && c)) {
  return d;
}

// good
const bar = a + b / c * d;

 

 

 

 

7.8. typeof 연산자

7가지 문자열 "string", "number", "boolean", "undefined", "symbol", "object", "function"로 반환한다. 여기서 typeof null 이 object로 반환된다는 것을 주의해야한다. 또한 선언되지 않은 식별자도 undefined로 반환한다.

이는 자바스크립트의 첫 번째 버그이며 아직 수정되지 못하고

?????????????????? 

null 인지 확인할때에는 === 일치 연산자를 사용해야한다.

 

 

7.9. 지수 연산자

ES7에 도이뵌 지수 연산자는 좌항의 피연산자를 밑base로, 우항의 피연산자를 지수exponent로 거듭제곱하여 숫자 값을 반환한다. 예전에는 Math.pow 메서드를 사용했었다. 지금은 지수연산자로 가독성이 개선된 사례이다.

 

 

지수 연산자는 산술 연산자와 마찬가지로 할당 연산자와 함께 사용할 수 없으며 우선순위가 가장높다.

 

 

 

그 외의 연산자는 나중에 찾아보기로하고

 

 

마지막으로 부수효과를 정리하면 할당 연산자 (=) 증가/감소 연사자 (++/--) delete 연산자가 부수효과가 있다.

 

연산자의 우선순위와 결합순서는 기억만 해두고 찾아보면된다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Operator_precedence

 

연산자 우선순위 - JavaScript | MDN

연산자 우선순위는 연산자를 실행하는 순서를 결정합니다. 우선순위가 높은 연산자가 먼저 실행됩니다.

developer.mozilla.org

 

 

너무 길어서 8장은 다음페이지에 작성하며, CS적인 지식을 늘리고 싶다면 다음 영상을 추천한다.

https://www.youtube.com/watch?v=ZQDsWySjY6g

 

let x = 0.1 * 10;
let y = 0.1 + 0.1 + 0.1 + 0.1 + 0.1
 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;

console.log(
  x, y, x === y
);
// ⭐️ 2의 거듭제곱으로 나눈 수의 계산은 정확
console.log(
  0.25 * 0.5,
  0.5 + 0.25 + 0.125 + 0.125,
  0.0625 / 0.25
);

 

 


참고자료

https://www.yalco.kr/lectures/javascript/