[JavaScript] 3. Operators

100tick·2022년 12월 14일
0

JavaScript Deep Dive

목록 보기
3/16
post-thumbnail

산술 Arithmetic Operators, 논리 Logical, 할당 Assignment, 비교 Comparison 연산자 등이 존재한다.

연산자의 종류와 특징을 살펴보자.

1. Arithmetic Operators

1.1. Binary Operators

1 + 1; // 2

1 - 1; // 0

1 * 2; // 2

2 / 1; // 2

3 % 2; // 1

Binary Arithmetic Operators 이항 산술 연산자는 2개의 Operands 피연산자를 가진다.

산술 연산이므로 두 피연산자는 숫자로 이루어진 number 타입이어야 하며, 다른 타입인 경우 암묵적으로 number 타입으로 변환한다.
그러나 변환 후에도 연산이 불가능한 경우(1 * "abc" 등) NaN를 반환한다.

1.2 Unary Operators

1++; // 2

++1; // 2

1--; // 0

--1; // 0

+1; // 1

-1 // -1

++, --는 피연산자의 값을 변경하는 Side Effect가 발생하기 때문에 주의해야한다.

let a = 1;
let b = 1;

a++; // a == 2

b+1; // b == 1

변수 a에 재할당이 일어나지 않았음에도 불구하고 값이 증가되었다.
++, -- 연산자는 암묵적 할당이 일어나기 때문이다.

++, --가 앞에 붙으면 Prefix Operator 전위 연산자, 뒤에 붙으면 Postfix Operator 후위 연산자라고 부르며, 연산 결과는 같지만 암묵적 할당의 시점이 달라진다.
아래의 예시를 보면 이해가 될 것이다.

let a = 1;
console.log(a++); // 1		 원래의 값 반환 후, 1을 증가시킨다
console.log(a); // 2

let b = 1;
console.log(++b); // 2		1을 증가시킨 후, 그 값을 반환한다
console.log(b); // 2

전위 연산자는 연산 -> 할당 -> 반환
후위 연산자는 반환 -> 연산 -> 할당

위 순서로 처리된다.

a라는 원본 변수는 변경하지 않으면서 피연산자로 사용하고 싶다면 반드시 Binary Operator를 사용해야 한다.

+1 // 1
-1 // -1

- 연산자는 부호를 반전시키지만, + 연산자는 숫자 앞에 붙여도 아무런 영향이 없다.
그러나 만약 문자열 앞에 + or -를 붙이면 암묵적으로 number로 타입이 변환된다.
그래서 문자열 -> 숫자로 변환을 할 때 사용된다.
단, +"a" 등 숫자 이외의 문자가 포함된 문자열의 경우 NaN을 반환한다.

+"1" // 1

typeof "1" // string
typeof +"1" // number
typeof -"1" // number

+"a" // NaN

typeof는 변수의 데이터 타입을 문자열로 반환하는 연산자이며 "1" 앞에 + or -를 붙였을 때 string -> number로 타입이 변환되는 것을 볼 수 있다.(Type Coercion)

1.3 String Concatenation Operator

+ 연산자는 피연산자가 2개일 때 덧셈 연산, 1개일 때 부호 반전 연산을 수행하였다.
그러나 피연산자가 2개면서 그 중 하나라도 string이 포함되면 문자열을 이어붙이는 문자열 연결 연산자 String Concatenation Operator로 작동한다.

1 + "2" // "12"

1 + (+"2") // 3

그래서 덧셈 연산을 하고 싶은 경우에는 +로 단항 연산을 통해 미리 string -> number로 타입 변환을 해서, 모든 피연산자를 number로 되게끔 만든 후 연산을 수행해야 한다.

그렇게 하지 않으면 의도한대로 작동하지 않아서 예기치 못한 오류가 발생할 수 있을 것이다.


2. Assignment Operator

let a;

a = 1;

a += 10; // a = a + 10;

a -= 1; // a = a - 10;

a *= 2; // a = a * 2;

a /= 2; // a = a / 2;

a %= 3; // a = a % 3;

a **= 2; // a = a ** 2;
let s = "abc";

s += "def"; // s = s + "def";

=는 메모리 공간에 값을 할당하는 연산자다.

나머지는 이 할당 연산자에 추가적인 연산을 곁들인 것인데,

만약 a += 1;이라는 연산을 수행한다면 이전에 a가 갖고 있던 값에 1을 더하여 재할당한다는 의미다.

JS는 변수를 생성할 때, 운영체제로부터 다른 변수에 의해 사용중이지 않은 메모리 공간을 할당 받고, 그 안에 값을 저장한다고 하였다.

만약 어떤 변수가 임의의 메모리 공간을 할당 받아 그 안에 값을 가지고 있는 상태에서 재할당이 일어난다면 어떻게 될까.

같은 공간에 다른 값을 덮어 쓸 것이라는 생각이 들 수도 있겠지만, JS에서는 재할당이 일어날 때마다 새로운 메모리 공간을 다시 할당받아서 사용하게 된다.

예를 들어, 변수 a0xFFFFFFFF라는 주소에 대한 별칭이고, 1이라는 값을 담고 있다면, 재할당이 일어났을 때 0xEEEEEEEE 등 임의의 주소를 다시 받아서 사용하는, 메모리 공간 자체가 바뀌어버리는 방식인 것이다.

이전에 사용하던 0xFFFFFFFF는 더이상 사용이 되고 있지 않으므로 다른 변수에게 할당될 수 있도록 사용 가능한 메모리 공간으로 지정되어야 할 것이다.

이 과정을 Free, 메모리 해제라고 부르며 Garbage Collector, 가비지 콜렉터에 의해 수행된다.

만약 메모리가 해제되지 않고 계속 사용중인 공간으로 표시된다면, 해당 공간은 영원히 다른 변수에게 할당되지 못하고 낭비될 것이다. 이를 Memory Leak, 메모리 누수라고 부른다.

메모리를 할당, 해제를 개발자가 직접 해줘야하는 C, C++ 프로그래밍 언어를 Unmanaged Language라고 부르는데, 사람이 처리하다보니 숙련된 개발자가 아니라면 실수로 인해 메모리 누수가 매우 발생할 가능성이 높다.

JS처럼 메모리 할당, 해제를 자동으로 해주는 프로그래밍 언어를 Managed Language라고 부르며, Garbage Collector가 그 역할을 담당한다.


3. Comparison Operators

3.1 Equality Operators

1 == 1; // true
1 === 1; // true

1 != 1; // false
1 !== 1; // false

두 피연산자가 일치하는지 아닌지 비교하는 연산자로, 일치 여부에 따라 true or false를 값을 반환한다.

주의할 점은 !===의 작동 방식이 상당히 난해하기 때문에 사용이 권장되지 않는다는 것이다.

1 == "1"; // true
1 === "1"; // false

number 타입인 1string 타입인 "1"은 내부적으로 비트 패턴도 다르고, 애초에 다른 타입이기 때문에 비교가 가능하다는 것이 논리적으로도 말이 되지 않는다.

그래서 다른 언어에서는 이렇게 다른 타입 간에 비교 연산을 수행하려고 하면 오류를 발생시킨다.
그러나 JS의 비교 연산자들은 피연산자의 타입이 달라도 그대로 작동시키며, 특히 ==, != 연산자들은 타입이 다른 경우, 멋대로 타입을 변환시켜서 비교를 수행한다.

적어도 ===, !== 연산자는 타입이 다르면 연산을 수행할지언정 false를 뱉어서 논리적으로 올바른 결과를 도출하여 결과를 예측하기 쉽기 때문에 권장되는 편이다.

NaN === NaN; // false

단, 숫자가 아님을 나타내는 변수 NaN을 비교하는 경우 같은 값을 비교했음에도 불구하고 false를 반환하기 때문에 이 경우 isNaN()을 사용해야 한다.

Number.isNaN(NaN); // true

3.2 Comparison Operators for Numbers

1 > 0; // true

1 < 0; // false

1 <= 1; // true

1 >= 0; // true

"1" > "0"; // true

1 > "0"; // true

1 > "x"; // false

수의 대소를 비교하는 대소 비교 연산자다.
string을 사용하는 경우 자동으로 number로 변환한 뒤 실행된다.
x 등 대소 비교가 불가능한 string을 넣는 경우 false를 반환할 것이다.


4. Ternary Operator

<conditional expr> ? <expr1> : <expr2>;의 형태로 표현되는 연산자로, 3개의 피연산자가 사용된다.

true ? 1 : 0; // 1

false ? 1 : 0; // 0

? 앞에 들어가는 조건식, <conditional expr>이 반환값을 결정하고 만약 boolean 타입이라면 true인 경우, 2번째 값인 1을 반환하고 false라면 3번째 값인 0을 반환한다.

"some" ? 1 : 0; // 1

"" ? 1 : 0; // 0

" " ? 1 : 0; // 1

조건식에 string 타입이 들어갈 때는 "" 빈 문자열인 경우 false 취급되어 2번째 값을, 나머지 경우에는 1번째 값을 반환한다.
참고로 ""가 빈 문자열이며 " "은 공백을 하나 포함하고 있기 때문에 길이가 1인 문자열로 취급된다.(빈 문자열이 아니다)

undefined ? 1 : 0; // 0

null ? 1 : 0; // 0

빈 공간임을 나타내는 타입인 undefinednullfalse로 취급되어 마지막 값을 반환한다.


[] ? 1 : 0; // 1

{} ? 1 : 0; // Uncaught SyntaxError: Unexpected token '?'

let obj = {};

obj ? 1 : 0; // 1

아직 언급하지는 않았지만 []는 여러 개의 데이터를 담을 수 있는 배열인데, object 타입 중 하나이다.
여러 개의 number를 하나의 배열 안에 넣고 싶다면 let arr = [1, 2, 3, 4, 5, 6];와 같이 사용하면 된다.

문자열과 달리 배열은 비어있더라도 true처럼 취급되어 1번째 값을 반환한다.
object도 마찬가지다.
단, object는 객체 리터럴 형태로 넣었을 때 오류가 발생하는 것을 볼 수 있다.

object를 담고 있는 변수를 조건문으로 사용했을 경우는 배열과 마찬가지로 비어있더라도 true처럼 취급된다.


5. Logical Operators

3가지 논리 연산자가 있다.
NOT: !
AND: &&
OR: ||

!true; // false

true && true; // true
true && false; // false

true || false; // true
false || false; // false

!NOT 부정의 의미로, truefalse로, falsetrue로 반전시킨다.
그리고 단일 피연산자를 갖는다.

주의할 점은 true, false만 존재하는 boolean 뿐만 아니라, 숫자, 문자열 등의 다른 데이터 타입에도 사용할 수 있다는 것이다.(데이터 타입이 가진 값에 따라 자동으로 boolean으로 변환)

다른 데이터 타입들을 !로 연산했을 때 어떤 결과가 나오는지 살펴보자.

// number
// 0은 false, 나머지는 true 취급하여 반전

!1; // false

!0; // true

!-1; // false
// string
// 빈 문자열은 false, 나머지는 true 취급하여 반전
!""; // true

!" "; // false

!"a"; // false
// object
// 빈 배열, 객체 모두 true 취급하여 반전

![]; // false

![1]; // false

!{}; // false

&&||은 두개의 피연산자를 갖는다.

&&은 두 피연산자 모두 true인 경우에만 true를 반환하며 하나라도 false라면 false를 반환한다.

||은 하나라도 true라면 true를 반환한다.

true && true; // true
false && true; // false

true || false; // true
false || false; // false

boolean 이외에 다른 데이터 타입을 사용하면 어떤 값이 반환되는지 아래 코드를 살펴보자.

"a" || false; // "a"
"a" || true; // "a"

[] || false; // []
{} || false; // Uncaught SyntaxError: Unexpected token '||'


true && "a"; // "a"
false && "a"; // false

처음 보면 조금 혼란스러울 수도 있는데, 우선 Short Circuit, 단축 평가라는 것에 대해 알아보자.

true || false; // true

OR 연산은 2개의 피연산자 중 하나라도 truetrue를 반환한다고 했다.
고로 첫번째 피연산자가 true인데 두번째 피연산자를 검사하는 것은 자원 낭비기 때문에 두번째 피연산자는 검사하지 않은채 종료된다.

AND 연산은 반대로 첫번째 피연산자가 false라면 두번째 피연산자와 관계 없이 무조건 false가 반환되기 때문에 검사를 마친다.

이러한 개념을 단축 평가라고 부르며, 이를 boolean이 아닌 다른 타입에 적용하면 검사가 멈추는 위치에 있는 값을 반환한다.

"abc" || false; // "abc"

string 타입의 값은 빈 문자열은 false, 그 외는 true 취급을 하였다.
여기서도 "abc"true 취급이 되기 때문에 피연산자가 하나라도 true면 검사를 멈추는 ||연산자는 검사가 멈춘 위치에 있는 값을 반환한다.
boolean으로 변환은 일어나지 않기 때문에 그대로 abc가 반환된다.

"abc" && "abc"; // "abc

&&의 경우 반대로 앞의 값이 true or 그에 상응하는 값인 경우, 뒤의 값을 변환 없이 그대로 반환한다.

익숙치 않다면 직접 실험해보는 것을 추천한다.


6. Commas Operator

let a, b, c;

a = 1, b = 2, c = 3; // 3

좌 -> 우 순서대로 피연산자를 평가하고 마지막 피연산자의 평가 결과를 반환하는 연산자다.
한 번에 여러 변수에 값을 대입할 수 있다.

let a = 1, b = 2, c = 3;

선언 + 초기화를 동시에 할 수도 있지만 이렇게 되면 expression이 아닌 statement가 되기 때문에 반환값은 없다.

let a = b = c = 1;

위와 같이 여러 개의 변수를 같은 값으로 한 번에 초기화 할 수도 있다.
마찬가지로 statement이므로 반환값은 없다.

let a = 1, b = 2;

a, b = b, a;
a // 1
b // 2

몇몇 언어에서는 위와 같은 방식으로 두 변수의 값을 교환할 수도 있지만, JS에서는 값이 그대로 나오는 것을 확인할 수 있었다.

조금 더 써줄게 많아지긴 하지만 아래와 같은 방법으로 교환이 가능하긴 하다는 것을 알 수 있었다.

let a = 1, b = 2;

[a, b] = [b, a];
a // 2
b // 1

7. Group Operator

let a = 1 + 2 * 3; // 7
let b = (1 + 2) * 3; // 9

괄호 안의 연산에 우선 순위를 둔다.
수학에서 괄호와 똑같은 개념이라고 보면 될 것 같다.


8. typeof Operator

typeof 'abc' 		 // "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 /test/gi 	 // "object"
typeof function() {} // "function"

typeof 연산자는 각 변수의 데이터 타입의 이름을 string으로 반환한다.
nullobject가 반환되는 것이 인상적이며, 아직 보지 않은 function도 있다.

nulltypeof로 검사해서는 확인할 수가 없으니 반드시 === 연산자를 사용하자.

typeof x; // "undefined"

한가지 더 주의할 점은 선언하지 않은 변수명을 typedef의 피연산자로 줬을 때 오류가 발생하는 것이 아닌, "undefined"가 된다는 것이다.


9. Others

?.
??
delete
new
instanceof
in

나머지 연산자들을 모았다.
자세한 내용은 이후에 알아보도록 하자.

10. Operator Precedence

연산자도 우선순위와 진행 방향이 존재하는데, 아래 링크를 참고하고 전부 외울 수는 없으니 애매한 경우는 ()를 사용하거나(모든 연산자 중에서 최우선순위를 갖는다) 문서를 다시 찾아보도록 하자.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

0개의 댓글