흐음, 이 파트 뭐라 할까요. 지루한 감이 있습니다. 한 마디로
노잼이라는 얘기죠.
이 파트가 개발에 있어 중요하지 않다는 것이 아닙니다. 다만 이 이야기를 너무 원론적으로 익히기에는 상당한 정신적인 피로가 발생한다는 거죠. 실제로 <코어 자바스크립트>나, <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");
});
});
테스트 결과 잘 동작합니다. 만약 실제 상황이었다면, 큰 오류가 나올 수 있겠죠?
이런 상황을 해결하기 위해, 이전 블로그 글에서는 타입스크립트로 해결하거나, 타입 가드를 적용하는 겁니다!
이건 더 쉬워요. 그냥 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' == 1
은true
와 같이 말이죠. 즉falsyValue == falsyValue
나truthyValue == 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
는 해당 값의 타입을 알려주죠? 총 일곱가지가 있어요.
그런데 좀 이상한 게 있어요.
우리의
null
어디갔니...? 😮
음... 이걸 설명하자면, 자바스크립트는 프로토타입 기반 언어죠?
모든 것들은 다 객체를 원형으로 하여 생성되었어요.
그런데, 이것에 관해서 null
도 하나의 타입을 만들어줘야 했는데, 자바스크립트가 워낙 호다닥 만들어버리는 바람에(...) 이를 처리하지 못했다죠. 따라서 object
로 나오게 됩니다.
나중에 알게 된 이후에, 이를 수정하려 했지만, 혹여나 이로 인해 레거시한 사이트들이 대거 터져버릴까 우려했대요. 결국 null
은 자바스크립트에서 object
로 나옵니다!
describe("typeof", () => {
it("개발 당시 실수로 인해 null은 타입 검사 시 object로 나와야 한다.", () => {
expect(null).toBeTypeOf("object");
});
});
2 ** 3 = 8
, (-2) ** 3 = -8
. 다들 아시죠? 생략!
null
병합 연산자는 이미 아까 설명했으니 생략!
나중에 나올 것들이라 생략!
굳이 잘 보아야 할 것을 짚는다면, 옵셔널 체이닝 연산자와 null 병합 연산자, new 정도만 잘 알아놔도 될 것 같아요!
나머지는 쓰기는 하지만 위 셋의 중요도에 비해서는 낮아서, 해당 파트 때 알아도 될 것 같네요~
delete
랑 ++
, --
연산자가 다른 코드에 영향을 주니까, 조심해서 써라! 이 말이에요. 아까 저 위의 두 개는 사이드 이펙트가 발생하니 자제해서 써야 한다고 이미 설명했었죠? 😆 역시 모든 지식은 돌고 돌아옵니다 🚀
다음과 같은 방식으로 호출된다고 하는데요! 이거 되게 중요한 거 맞거든요?!
그런데 이렇게 외워봤자 사실 잘 안 외워져요!
그저... 너무 복잡한 연산을 고민하지 않도록, 깔끔한 코드를 작성하는 습관을 들이는 게 더 중요하다고 볼 수 있겠어요!
무슨 깜지마냥 쓰기보다는, 나중에 직접 코드 치면서, 에러 상황을 마주하면서 배우는 것도 좋은 방법이라고 권해드립니다 🙇🏻♂️
new
(매개변수 있을 경우), []
(인덱스나 프로퍼티 접근할 때), ()
(함수호출), ?.
(옵셔널 체이닝)new
(매개변수 없을 경우)x++
, x--
!x
, +x
, -x
, --x
, ++x
, typeof
, delete
**
*
, /
, %
+
, -
<
, <=
, >
, >=
, in
, instanceof
==
, !=
, ===
, !==
??
&&
(요거, 13번이랑 정말 많이 헷갈려요!)||
(요거, 12번이랑 정말 많이 헷갈려요!)삼항 연산자
할당 연산자
,
12번 13번은 정말 많이 실수하는 것 중 하나라, 둘을 같이 곁들여 쓰실 때에는 괄호를 잘 써주시길 바라요!
여기서 말하는 결합 순서란, 어떤 쪽부터 코드를 평가하고 연산할 것인지를 따지는 건데요!
요것도 그냥 실제로 마주하면서 익히시길 권장해요! (이렇게 실제로 다 코드 치라고 하는 게 맞나?!라고 생각이 들 수 있겠는데, 역시 코드는 직접 마주해야 더 잘 압니다)
+
-
/
%
비교연산자
&&
||
[]
()
?.
in
instanceof
--
++
할당 연산자
!x
+x
-x
typeof
delete
삼항연산자
와, 제가 vitest
랑 vite
한 번 맛 좀 볼까 싶어서 무리한 것도 있기는 한데, 역시 이걸 하나하나 쳐보는 게 참 시간이 많이 걸리네요. 거의 4시간...?
그런데 오늘 이렇게 기본 공부하면서 또 배운 것들은 많네요.
ES12
문법을 맛보게 됐어요! 새로운 연산자를 써보니 뭔가 신기하고, 신났답니다 😆vitest
써봤는데요. 이 프레임워크, 맛있네요! concurrent
를 통한 병렬 연산을 따로 지원하기도 하고, vite
환경에서 큰 설정 없이도 잘 돌아간다는 게 엄청난 강점이 있어요!vite
도 이번에 인상 깊게 봤습니다. configuration
을 최소화하도록 초반 설정도 잘 지원해주고, 덕분에 yarn berry
로 세팅하는 데 별 어려움이 없었어요!다들, 너무 이론적인 공부에 치우치지 말고, 다양한 환경들을 맛보면서, 즐거운 코딩하시길! 이상 🌈