[REAL Deep Dive into JS] 7. 연산자 (feat. ES12 Logical Assignment Operators)

young_pallete·2022년 8월 24일
0

REAL JavaScript Deep Dive

목록 보기
7/46

🌈 시작하며

흐음, 이 파트 뭐라 할까요. 지루한 감이 있습니다. 한 마디로 노잼이라는 얘기죠.

이 파트가 개발에 있어 중요하지 않다는 것이 아닙니다. 다만 이 이야기를 너무 원론적으로 익히기에는 상당한 정신적인 피로가 발생한다는 거죠. 실제로 <코어 자바스크립트>나, <YOU DON'T KNOW JS>에는 간단한 언급만 있을 뿐, 연산자 파트가 존재하지 않습니다!

처음 <모던 자바스크립트 Deep Dive> 책을 읽으시는 분께 권하는 방법은, 너무 하나하나의 정의에 매몰되지 않았으면 좋겠다입니다.

물론 이는 각자의 차이가 있겠지만, 저는 코드란 어떻게 실제로 사용할 수 있는가가 중요하지, 이 개념이 무엇이다!라고 정확히 딱 말하는 게 중요하다고 생각하지 않습니다.

부연 설명하자면, 다음과 같은 예시가 있겠어요.

👨‍💻 면접관: ++가 뭔지 아나요?
🙇🏻‍♂️ 면접자: 앞의 숫자 값을 증가시키는 연산자입니다!
👨‍💻 면접관: 아니, 정확히 무슨 개념인지 말씀해보세요!
🙇🏻‍♂️ 면접자: (머리를 긁적이며) 연산자 아닌가요?
👨‍💻 면접관: (씨익 웃으며) 아니에요. ++단항 산술 연산자죠! 당신은 연산자를 알지 못하는군요.

과연 위와 같은 상황이 존재할까요? 만약 존재한다면, 면접관과 같은 정의에 입각한 사고방식이 좋은 사고 방식이라고 단언할 수 있을까요?

너무 하나하나의 정의에 집착하기보다는, 기호를 사용하는 개발자로서의 관점에 입각하여 실전적으로 이 파트를 들여다보면 좋을 것 같아요. 우리, 이론을 연구하는 사람이 아니라 코드로 문제를 해결하는 사람들이니까요!

그럼, 그런 관점에서 우리 들여다 볼까요~? 🙇🏻‍♂️


🚦본론

연산자의 개념

우리는 리터럴로 모든 것을 표현하기엔, 상당히 애매한 것들이 많아요. 예컨대 다음과 같은 것들 말이죠.

임의의 정수인 A와 B를 더한 값을 지속하여 구하려 한다. 어떻게 해결할 수 있는가?

이를 정확히 리터럴로 딱! 표현할 수는 없겠죠. 따라서 다음과 같은 표현식을 함수의 리턴 값으로 넣게 됩니다.

const add = (a: number, b: number) => a + b;

이때, a + b에서 +가 바로 연산자죠?
즉, 연산자란, 굳이 정의하자면 연산을 통해 하나의 값을 산출하도록 하기 위한, 연산에 있어 약속된 기호라고 할 수 있겠네요!

여기서는 엄~청 많이 연산자를 분류를 해놓았는데... 이렇게 외우는 건 제 스타일이 아닌지라, 저는 좀 더 예제를 통해 편하게 익히겠습니다 🙆🏻

이 파트가 너무 지루해서... 저는 vitest라는 Vite 기반 테스트 프레임워크 연습 하면서 연산자를 익혀볼게요!
아, 현재 vitest 테스트 프레임워크 버전은 ^0.22.1입니다! 혹시 나중에 문법이 바뀔 수 있으니... 😅

산술 연산자

describe("산술 연산자를 통해 연산하면", () => {
  it("+는 더하기를 수행하므로 1 + 2는 3이 나와야 한다.", () => {
    expect(1 + 2).toEqual(3);
  });

  it("-는 빼기를 수행하므로 5 - 2는 3이 나와야 한다.", () => {
    expect(5 - 2).toEqual(3);
  });

  it("*는 곱하기를 수행하므로 5 * 2는 10이 나와야 한다.", () => {
    expect(5 * 2).toEqual(10);
  });

  it("/는 나누기를 수행하므로 5 / 2는 2.5가 나와야 한다.", () => {
    expect(5 / 2).toEqual(2.5);
  });

  it("%는 나머지를 수행하므로 5 % 2는 1이 나와야 한다.", () => {
    expect(5 - 2).toEqual(3);
  });
});

모두 정상적으로 연산이 되죠?

그렇다면, 우리 이런 건 어떨까요?

  it("0으로 나누기를 수행하면 결과 값은 NaN으로 나온다", () => {
    expect(5 / 0).toEqual(NaN);
  });

이 결과 값은 틀립니다! 0으로 나누면 무한이므로, 자바스크립트에서는 Infinity라는 number 타입의 값을 지원하므로 Infinity가 나옵니다!

테스트 프레임워크를 쓰면, 이렇게 예상되는 기댓값이 나옵니다. 참 신기하죠? 이럴 때 장난스럽게 만지면서 친해지는 것도 굉장히 좋은 습관입니다 😁

// 수정한 결과
  it("0으로 나누기를 수행하면 결과 값은 Infinity으로 나온다", () => {
    expect(5 / 0).toEqual(Infinity);
  });

결과가 잘 나오게 되죠?!

이항 산술 연산자

자! 이제 우리는 애증의 ++, -- 연산을 살펴보러 갈까요~?

describe.concurrent("단항 연산자를 통해 연산하면", () => {
  it("++x, --x는 먼저 피연산자의 연산을 수행한 후 연산을 수행해야 한다.", () => {
    let x = 5;
    let y = 10;

    let result = ++x * ++y;
    expect(result).toEqual(66); // ( 5 + 1 ) * ( 10 + 1 )

    result = ++x * y;
    expect(result).toEqual(77); // ( 6 + 1 ) * 11

    result = --x * --y;
    expect(result).toEqual(60); // ( 7 - 1 ) * (11 - 1)
  });

  it("x++는 먼저 피연산자의 연산을 수행한 후 연산을 수행해야 한다.", () => {
    let x = 3;
    let y = 4;

    let result = x++ * y++;
    expect(result).toEqual(12);
    expect(x).toEqual(4);
    expect(y).toEqual(5);
  });
})

결과가 잘 나오는 것을 확인할 수 있답니다 🚀

어떤가요? 이해가 잘 되시나요?
그렇다면 이 테스트 코드의 결과는 어떻게 나올까요?

  it("이 테스트의 결과는 어떻게 될까요?", () => {
    let x = 1;

    let z = ++x * x++;

    // expect(z).toEqual(?????);
    // expect(x).toEqual(?????);
  });

결과는 다음과 같습니다!

혹시나 이해가 잘 되지 않아서 스트레스 받으시는 분들은 손! 👋🏻
너무 스트레스 받지 않아도 돼요. 사실, 위 단항 연산자는 꽤나 로직에 있어서 유지보수를 어렵게 한다는 의견이 있어요. 아니, 기존 코드를 갑자기 아래에서 별다른 할당 없이 몰래 변형시키니 말이죠!

자바스크립트 개발 참여자인 더글라스 크락포드도 <자바스크립트는 왜 우리를 힘들게 하는 걸까?>라는 책에서 위 연산자 사용은 자제하자는 의견을 전해주었습니다!
따라서 실제로 x += 1의 방식으로 개발 하시는 분들도 많다고 합니다. 저도 그 중 하나...😅

아, 지나칠 뻔했는데, 여기서 + 설명이 잘못 되어 있어요!

책에서는 '어떠한 효과도 없다'라고는 하지만, 실제로 효과가 있습니다.
어떨 때냐면, '문자열을 숫자로 변환할 때' +를 사용할 수 있어요. -도 마찬가지로요. 예제로 보여드릴게요!

it("+는 문자열을 넘버 타입으로 변환시켜야 한다.", () => {
   let x: number | string = "1";
   x = +x;

   let y: number | string = "5";
   y = -y;

   expect(x).toEqual(1);
   expect(y).toEqual(-5);
 });

넘버 타입으로 변환되기 때문에 테스트를 통과할 수 있게 되는 것을 확인 가능하죠!

문자열 연결 연산자

요건 좀 중요합니다. 자주 실수하는 오류 중 하나에요.

쉽게 말해서, 두 개의 피연산자를 서로 더하는 연산 중, 하나라도 문자열이 있다면 문자열 연산자로 연산 기호가 해석된다는 거에요.

describe("문자열 연결 연산자는", () => {
  it("하나라도 문자열이 있다면, 문자 타입으로 해석되어야 한다.", () => {
    let x = 1;
    let y = "2";
    expect(x + y).toBeTypeOf("string");
  });

  it("문자열이 없다면, 넘버 타입으로 해석되어야 한다.", () => {
    let x = 1;
    let y = 2;
    expect(x + y).toBeTypeOf("number");
  });
});

테스트 결과 잘 동작합니다. 만약 실제 상황이었다면, 큰 오류가 나올 수 있겠죠?
이런 상황을 해결하기 위해, 이전 블로그 글에서는 타입스크립트로 해결하거나, 타입 가드를 적용하는 겁니다!


할당 연산자 (ES12 Logical Assignment Operators 추가)

이건 더 쉬워요. 그냥 x = x + 5와 같은 경우, 오른쪽 표현식에서의 피연산자 평가를 왼쪽 변수에 할당한다는 의미로 x += 5라고 쓰는 것입니다.

이건 굳이 따로 쓰지 않아도 이해하기가 쉬워서... 그냥 넘어갈게요! 대충 이런 것들이 있다고만 이해하면 될 것 같아요.

  • =
  • +=
  • -=
  • *=
  • /=
  • %=

아, 그런데 이거 쓰다가 저 또 방금 호기심이 생긴 게 있어요.
최근에 블로그 글을 쓰면서 알게 된건데, 논리 연산자 역시 위와 같은 방식으로 할당하는 방법이 생겨났다고 했거든요! (ES12)
한 번 이거 같이 해볼까요? 광기 발동... 🔥

describe("ES12_Logical Assignment Operators", () => {
  it("a &&= b is equal to a && (a = b)", () => {
    let a = 0;
    a &&= 2;
    expect(a).not.toEqual(2); // a는 falsy한 값이 아니어야 새로운 값을 할당 받을 수 있다.

    a = 1;
    a &&= 2;
    expect(a).toEqual(2);
  });

  it("a ||= b is equal to a || (a = b)", () => {
    let a = 0;
    a ||= 2;
    expect(a).toEqual(2); // a가 falsy한 값이면 오른쪽 값을 할당 받는다.

    a = 1;
    a ||= 4;
    expect(a).toEqual(1); // a가 falsy한 값이 아니라면 할당받지 않는다.
  });

  it("a ??= b is equal to a ?? (a = b)", () => {
    let a: undefined | number = 2;
    a ??= 3;

    expect(a).toEqual(2); // a가 undefined나 null이 아니라면 할당받지 않는다.

    a = undefined;
    a ??= 3;
    expect(a).toEqual(3); // a가 undefined나 null이라면 오른쪽 값을 할당 받는다.
  });
});

결과를 볼까요? 👀

오! 진짜 되네요. 이런 문법이 있었다니!
나중에 한 번 협업할 때 이걸 갖고 닌자 코드를 작성...(쿨럭쿨럭)

비교 연산자

비교 연산자에서 >=, >, <, <=는 다들 아시죠?
그럼 굳이 알 만한 것을 따로 언급하지 않겠습니다.
제가 여기서 중요시 여기는 건, 동등/일치 비교 연산자에요!

어쩌면 이 파트의 꽃이라고 할 수도 있겠네요. double Equals(==)triple Equal(===)은 그만큼이나 중요한 개념입니다.

이 개념에 관해서 유명한 블로그 글에 따르면,검사에 대한 엄격한 정도의 차이가 존재해요!
좀 더 정리해서 표현하자면, 어떤 값을 비교하는 데 있어서 자바스크립트는 타입실제 값을 검사하는데요.

  • ===: 타입과 실제 값 둘 다 검사합니다. 둘 모두 일치해야 true를 반환합니다.
  • ==: 실제 값을 느슨하게 검사합니다. 따라서 '1' == 1true와 같이 말이죠. 즉 falsyValue == falsyValuetruthyValue == truthyValue와 같은 연산들을 true로 처리한다는 겁니다!
describe("비교 연산자는", () => {
  it("===는 타입과 관계 없이 값을 느슨하게 검사해야 한다.", () => {
    let x = 1;
    let y = true;

    expect(x === y).toEqual(false);
  });

  it("==는 타입과 관계 없이 값을 느슨하게 검사해야 한다.", () => {
    let x = 1;
    let y = true;

    expect(x == y).toEqual(true);
  });

  it("객체 타입의 값의 비교는 항상 다르게 나와야 한다", () => {
    let a = [1, 2, 3];
    let b = [1, 2, 3];

    /**
     * 이거 처음에 정~말 많이 헷갈려 하시는데, 이는 같지 않아요. 왜냐! 배열은 객체타입이죠?
     * 객체 타입은 주소 값을 참조하며 값을 불러오죠!
     * 그런데 두 배열의 주소 값이 같지 않겠죠? 따라서 false가 나와야 합니다!
     */
    expect(a == b).not.toEqual(true);
    expect(a === b).not.toEqual(true);
  });
});

대개 현재 실무를 할 때에는 ===한 값으로 처리하려 노력해요.
(아니, 사실상 이것만 씁... 크흡. 어떤 코드든지 의도가 중요하겠죠!)

나머지

음... 나머지는 그냥 다른 데에서도 볼 수 있는 것들 같아서, 그냥 간단하게 짚고 넘어갈까요?

삼항 조건 연산자

const variable = checkValue ? callIfTrue : callIfFalse와 같은 방식으로 사용해요.
쉽게 말해서 ?의 좌항을 기준으로 할 때, 만약 truthy하다면 :의 왼쪽 표현식의 결과 값을, 아니라면 :의 오른쪽 표현식의 결과를 값으로 할당하죠.

describe("삼항 연산자", () => {
  it("만약 조건이 truthy하다면 좌항을 실시해야 한다.", () => {
    let check = 1 === 1;
    let result = check ? true : false;
    expect(result).toBeTruthy();

    check = 1 !== 1;
    result = check ? true : false;
    expect(result).toBeFalsy();
  });
});

논리 연산자

이미 위에서 저의 광기로 인해(...) 대충 예제로 이해할 수 있는 거죠? 간단히 설명하고 생략합니다!

  • &&: AND
  • ||: OR
  • !: 부정의 의미

쉼표 연산자

음... let a, b, c;와 같이 나열해서 쓸 수 있다는 건데, 이건 잡기술에 가깝다고 생각해서... 저는 생략할게요.

이유는 보통 코드를 작성할 때에는 변수의 안정성을 위해 const로 쓰는 것을 권장하는 편인데요. const를 저렇게 병렬로 쓸 수 없기 때문입니다.

다만, 저는 Airbnb 스타일 가이드에 따라 const를 위주로 사용하는 개발자라 다른 의견이 있을 수 있으니, 절대적인 수용은 금지입니다!
모든 코드는 팀간의 컨벤션에 기반해서!! 😉

그룹 연산자

네, 다들 아는 괄호! 이상입니다. (호다닥)

typeof 연산자

음... 이거는 좀 중요한데, typeof는 해당 값의 타입을 알려주죠? 총 일곱가지가 있어요.

  • string
  • number
  • boolean
  • object
  • symbol
  • function
  • undefined

그런데 좀 이상한 게 있어요.

우리의 null 어디갔니...? 😮

음... 이걸 설명하자면, 자바스크립트는 프로토타입 기반 언어죠?
모든 것들은 다 객체를 원형으로 하여 생성되었어요.
그런데, 이것에 관해서 null도 하나의 타입을 만들어줘야 했는데, 자바스크립트가 워낙 호다닥 만들어버리는 바람에(...) 이를 처리하지 못했다죠. 따라서 object로 나오게 됩니다.

나중에 알게 된 이후에, 이를 수정하려 했지만, 혹여나 이로 인해 레거시한 사이트들이 대거 터져버릴까 우려했대요. 결국 null은 자바스크립트에서 object로 나옵니다!

describe("typeof", () => {
  it("개발 당시 실수로 인해 null은 타입 검사 시 object로 나와야 한다.", () => {
    expect(null).toBeTypeOf("object");
  });
});

업로드중..

지수 연산자

2 ** 3 = 8, (-2) ** 3 = -8. 다들 아시죠? 생략!

그 외 연산자

null 병합 연산자는 이미 아까 설명했으니 생략!
나중에 나올 것들이라 생략!
굳이 잘 보아야 할 것을 짚는다면, 옵셔널 체이닝 연산자null 병합 연산자, new 정도만 잘 알아놔도 될 것 같아요!

나머지는 쓰기는 하지만 위 셋의 중요도에 비해서는 낮아서, 해당 파트 때 알아도 될 것 같네요~

연산자의 부수 효과

delete++, -- 연산자가 다른 코드에 영향을 주니까, 조심해서 써라! 이 말이에요. 아까 저 위의 두 개는 사이드 이펙트가 발생하니 자제해서 써야 한다고 이미 설명했었죠? 😆 역시 모든 지식은 돌고 돌아옵니다 🚀

연산자 우선순위

다음과 같은 방식으로 호출된다고 하는데요! 이거 되게 중요한 거 맞거든요?!
그런데 이렇게 외워봤자 사실 잘 안 외워져요!

그저... 너무 복잡한 연산을 고민하지 않도록, 깔끔한 코드를 작성하는 습관을 들이는 게 더 중요하다고 볼 수 있겠어요!

무슨 깜지마냥 쓰기보다는, 나중에 직접 코드 치면서, 에러 상황을 마주하면서 배우는 것도 좋은 방법이라고 권해드립니다 🙇🏻‍♂️

  1. ()
  2. new(매개변수 있을 경우), [](인덱스나 프로퍼티 접근할 때), ()(함수호출), ?.(옵셔널 체이닝)
  3. new(매개변수 없을 경우)
  4. x++, x--
  5. !x, +x, -x, --x, ++x, typeof, delete
  6. **
  7. *, /, %
  8. +, -
  9. <, <=, >, >=, in, instanceof
  10. ==, !=, ===, !==
  11. ??
  12. && (요거, 13번이랑 정말 많이 헷갈려요!)
  13. || (요거, 12번이랑 정말 많이 헷갈려요!)
  14. 삼항 연산자
  15. 할당 연산자
  16. ,

12번 13번은 정말 많이 실수하는 것 중 하나라, 둘을 같이 곁들여 쓰실 때에는 괄호를 잘 써주시길 바라요!

연산자 결합 순서

여기서 말하는 결합 순서란, 어떤 쪽부터 코드를 평가하고 연산할 것인지를 따지는 건데요!

요것도 그냥 실제로 마주하면서 익히시길 권장해요! (이렇게 실제로 다 코드 치라고 하는 게 맞나?!라고 생각이 들 수 있겠는데, 역시 코드는 직접 마주해야 더 잘 압니다)

  • 좌 ➡ 우: + - / % 비교연산자 && || [] () ?. in instanceof
  • 우 ➡ 좌: -- ++ 할당 연산자 !x +x -x typeof delete 삼항연산자

🔥 마치며.

와, 제가 vitestvite 한 번 맛 좀 볼까 싶어서 무리한 것도 있기는 한데, 역시 이걸 하나하나 쳐보는 게 참 시간이 많이 걸리네요. 거의 4시간...?

그런데 오늘 이렇게 기본 공부하면서 또 배운 것들은 많네요.

  • ES12 문법을 맛보게 됐어요! 새로운 연산자를 써보니 뭔가 신기하고, 신났답니다 😆
  • vitest 써봤는데요. 이 프레임워크, 맛있네요! concurrent를 통한 병렬 연산을 따로 지원하기도 하고, vite 환경에서 큰 설정 없이도 잘 돌아간다는 게 엄청난 강점이 있어요!
  • vite도 이번에 인상 깊게 봤습니다. configuration을 최소화하도록 초반 설정도 잘 지원해주고, 덕분에 yarn berry로 세팅하는 데 별 어려움이 없었어요!
  • 연산자 결합 순서라던지 우선순위를 좀 많이 소홀히 했는데, 다시 정리해서 기분이 좋습니다! 특히 우선 순위는 제가 항상 간과했던 실수를 다시 상기시켜줘서 주의를 기울이게 됐어요 🥰

다들, 너무 이론적인 공부에 치우치지 말고, 다양한 환경들을 맛보면서, 즐거운 코딩하시길! 이상 🌈

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉

0개의 댓글