웹공부/JS

러닝 자바스크립트 - 표현식과 연산자

syom 2021. 9. 8. 15:15

0. 표현식이란?


표현식(expression)

  • 값으로 평가될 수 있는 문, 즉 결과가 값인 문
  • 표현식이 아닌 문(statement)은 일종의 지시
  • 주로 무언가를 요청하고 그 결과를 명시적으로 반환하는 것

⇒ 따라서 표현식이 될 수 있고, 그 결과를 다른 표현식에 결합하여 다른값을 얻는 식으로 이어질 수 있다

let x; // 선언문 x = 3 * 5; // 표현식

⇒ 첫번째 표현식은 3 * 5 이고, 곱셉표현식이며 결과는 15 이다.

⇒ 그 다음은 값 15를 x 에 할당하는 표현식이다. 할당은 그 자체가 표현식이다

let x, y; y = x = 3 * 5;
  • 결과let x, y; // 선언문 y = x = 3 * 5; // 3 * 5 곱셈 표현식 y = x = 15; // x = 15 할당 표현식 y = 15; // y = 15 할당 표현식 15; // 15, 이 값은 사용하지도 할당하지도 않으니 그냥 버려진다

표현식을 실행하는 순서?

자바스크립트가 표현식을 평가하는 순서를 연산자 우선순위라 부른다.

표현식의 종류

표현식의 대부분은 연산자(operator) 표현식이다.

그 외에는 식별자 표현식(변수와 상수 이름)과 리터럴 표현식 두가지가 있다

// 리터럴 표현식(JS엔진에 의해 런타임에 평가되어 값 생성 => 표현식)
100;
('Jimmy');

// 식별자 표현식(식별자 참조)
var sum = 50;
sum; // -> 변수 sum에 100이라는 값이 할당되어 있다면 참조를 통해 값(100)이 평가됨 => 표현식
person.name;

// 연산자 표현식
10 + 20;
sum = 10;
sum === 10; // true라는 값을 생성

1. 연산자


표현식의 '명사'에 대한 '동사'라고 생각해도 좋다.

표현식이 이 되는 것이라면 연산자는 값을 만드는 행동 이라는 뜻이다.

1-1. 산술 연산자

  • 자바스크립트의 숫자는 모두 더블 형식이라 정수를 연산한 결과도 더블 형식으로 나온다
  • 뺄셈과 단항 부정은 모두 - 기호를 사용한다. → 둘을 구별하는 것은 여기서 다루지 않음
  • 단항부정이 먼저 이루어지고 그 다음에 뺄셈을 한다 (단항 플러스도 마찬가지)
  • ⇒ 보통 이 연산자를 사용하는 경우는 문자열을 숫자로 강제 변환하는 경우거나 줄을 맞추기위한 경우이다
  • 예제
const s = "5";
const y = 3 + +s; //y는8입니다.단항플러스를사용하지않았다면
				// 문자열 병합이 일어나서 결과는 "35"가 됩니다.

// 여기서는 굳이 단항 플러스가 필요하지 않지만 줄을 잘 맞출 수 있습니다. 
const x1 = 0, x2 = 3, x3 = -1.5, x4 = -6.33;
const p1 = -x1*1;
const p2 = +x2*2;
const p3 = +x3*3; 
const p3 = -x4*4;

나머지 연산자 (모듈러 연산자)

나머지 연산자는 음수에 적용하면 제수가 아닌 피제수의 부호를 따라가기 때문에 이 연산자를 진정한 나머지 연산자라 할 수는 없다.

⇒ 참고 사이트 : https://ko.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/what-is-modular-arithmetic

1-2. 연산자 우선순위

기본적으로 수학시간에 배웠던 연산자 우선순위대로 진행된다

  • 곱셉과 나눗셈은 우선순위가 둘 다 14이고, 왼쪽→오른쪽 으로 평가
  • 할당연산자는 우선순위가 3이고, 오른쪽→왼쪽으로 평가
  • 예제
let x = 3, y;
x += y = 6 * 5 / 2;
// 이 표현식을 우선순위에 따라 다음에 일어날 행동에 괄호를 치겠습니다. //

// 곱셈과 나눗셈. 우선순위 14, 왼쪽에서 오른쪽으로
// x += y = (6 * 5) / 2
// x += y = (30 / 2)
// x += y = 15

// 할당. 우선순위 3, 오른쪽에서 왼쪽으로
// x += (y = 15)
// x += 15        y는 이제 15입니다.
// 18             x는 이제 18입니다.

1-3. 비교 연산자

두개의 값을 비교하는 연산자.

크게 나누자면 일치함(strict equality, ===), 동등함(loose equality, ==), 대소 관계(<, ≤, >, ≤), 세가지 타입으로 나뉜다

일치 연산자와 동등 연산자의 예

const n = 5; 
const s = "5"; 
n === s;                // false -- 타입이 다릅니다.
n !== s;                // true
n === Number(s);        // true - - 문자열 "5"를 숫자 5로 변환했습니다.
n !== Number(s);        // false
n == s;                 // true; 권장하지 않습니다.
n != s;                 // false; 권장하지 않습니다.

const a = { name: "an object");
const b = { name: "an object");
a === b;                // false -- 객체는 항상 다릅니다. 
a !== b;                // true
a == b;                 // false; 권장하지 않습니다.
a != b;                 // true; 권장하지 않습니다.

⇒ 기계적으로 동등 연산자를 쓰는 습관은 버리는게 좋고, 일치 연산자만 사용하는 편이 낫다.

1-4. 숫자 비교

NaN (Not a Number)

특별한 숫자형 값 NaN은 그 자신을 포함하여 무엇과도 같지 않다.

⇒ Nan === Nan / Nan == Nan 둘 다 false

Number.EPSILON

자바스크립트에서 정수를 비교할 때, 그 정수가 안전한 범위라면(Number.MIN_SAFE_INTEGER 이상, Number.MAX_SAFE_INTEGER 이하) 안심하고 일치 연산자를 사용할 수 있다

⇒ Number.MIN_SAFE_INTEGER : -9007199254740991 === (-(2^53 - 1))

Number.MAX_SAFE_INTEGER : 9007199254740991 === (2^53 - 1)

하지만 소수점이 있는 숫자를 비교할 때는 결과가 제대로 나오지 않을 때가 있다.

이럴 때에는 관계 연산자를 써서 테스트 하는 숫자가 대상 숫자에 "충분히 가까운지" 확인하는 편이 좋다

  • 예제 코드
let n = 0; 
while(true) {
	n += 0.1;
	if(n === 0.3) break; 
}
console.log(`Stopped at ${n}`);   // ---> 무한루프로 진입됨

//=============== 때문에 아래와 같이 비교해야함
let n = 0; 
while(true) {
	n += 0.1;
	if(Math.abs(n - 0.3) < Number.EPSILON) break; 
}
console.log(`Stopped at ${n}`);
// Number.EPSILON과 관계 연산자를 사용해서 ‘느슨하게’ 비교하고 성공적으로 루프를 빠져나갈 수 있다

1-5. 문자열 병합

자바스크립트에서 + 는 덧셈과 문자열 병합에 모두 사용된다. (둘 다 왼쪽→오른쪽으로 평가 됨)

피연산자 중 하나라도 문자열이면 문자열 병합을 수행한다.

3 + 5 + "8";    // 문자열 "88"이 된다
"3" + 5 + 8;    // 문자열 "358"이 된다

1-6. 논리 연산자

논리연산자는 불리언 값 (false / true) 만 다룰 수 있다.

수학, 또는 대부분의 프로그래밍 언어에서는 논리 연산자는 불리언 값에서만 동작하며 불리언 값만 반환한다. 하지만 자바스크립트의 논리 연산자는 불리언이 아닌 값도 다룰 수 있고, 불리언이 아닌 값을 반환하기도 한다😱

(이는 잘못만들어진게 아니다. 논리 연산자를 불리언 값에 사용하면 결과는 불리언 값뿐이다)

연산자에 대해 설명하기 전에 자바스크립트가 불리언이 아닌 값을 불리언으로 값을 바꾸는 방법에 익숙해져야 한다

참 같은 값, 거짓 같은 값

**'참 같은 값'**과 **'거짓 같은 값'**이란 개념이 있는 언어도 많고, C언어처럼 불리언 타입이 아예 존재하지 않는 언어도 있다. 자바스크립트는 이들을 적절히 혼합한 방식을 취한다.

  • 거짓 같은 값 (거짓도포함)
  • ⇒ undefined / null / false / 0 / NaN / ''(빈 문자열)

이들을 제외한 값은 모두 참 같은 값이다.

  • 유의해야할 부분 (true)
    • 모든 객체. valueOf ( ) 메서드를 호출했을 때 false를 반환하는 객체도 참 같은 값에 속합니다.
    • 배열. 빈배열도 참 같은 값에 속합니다. → 하지만 [] == false 는 true
    • ⇒ 빈 배열을 false 로 평가하게 하려면 arr.length 를 사용하자
    • 공백만 있는 문자열 (" "등)
    • 문자열 "false"

1-7. AND, OR, NOT

자바스크립트에서 지원하는 논리 연산자는 AND(&&), OR(||), NOT(!) 세 가지다.

단축 평가

AND 연산자의 경우 x 가 거짓 같은 값이라면, x && y 는 y 값을 평가할 필요도 없이 false 이다. 이와 마찬가지로 OR 연산자에서 x가 참 같은 값이라면 x || y 는 y 값을 평가할 필요 없이 true 이다.

자바스크립트는 정확히 이런 방식으로 동작하며 이런 동작을 단축 평가(short-circuit evaluation)라고 한다.

단축 평가가 중요한 이유는 두번째 피연산자에 부수 효과가 있다 하더라도 단축 평가를 거치면 그 효과는 일어나지 않기 때문이다.

예제1)

const skipIt = true;
let x = 0;
const result = skipIt || x++;

설명

  • 만약 skipIt 을 false 로 바꾸면 논리연산자의 두 피연산자 모두 평가해야하고, x가 증가하게 된다.
  • 여기서는 증가 연산이 부수효과이다.
  • 예제의 세번째 행 결과는 result 에 저장이 된다. 첫번째 피연산자가 skipIt이 true 이므로 result의 값에는 true가 된다. 주목해야할 점은 x는 여전히 0이라는 점이다.

예제2)

const doIt = true;
let x = 0;
const result = doIt && ++x;
  • 설명하지만 doIt을 true 로 바꾸면 자바스크립트는 두 피연산자를 모두 평가해야 하므로 증가연산이 일어나고 result는 0이 된다
  • result의 값이 false 가 아닌 0이 되는 이유는 다음 섹션에서.
  • 이번에도 비슷하게, AND 연산자의 첫번째 피연산자가 false 이므로 두번째 피연산자를 평가하지 않는다. 따라서 result는 false이고, x는 늘어나지 않는다.

피연산자가 불리언이 아닐 때 논리 연산자가 동작하는 방법

불리언 피연산자를 사용하면 논리연산자는 당연히 항상 불리언을 반환하지만,

피연산자가 불리언이 아니라면 결과를 결정한 값이 반환된다.

  • 이런 동작방식을 활용한 간편한 팁

const options = suppliedOptions || { name: "Default" }

⇒ 객체는 빈 객체더라도 항상 참 같은 값으로 평가된다. 따라서 suppliedOptions 가 객체라면 options는 suppliedOptions 를 가리키게 된다. 옵션이 제공되지 않으면(suppiedOptions 가 null 이거나 undefined 라면) options 는 기본값( {name: "Default"} )을 갖게 된다.

조건 연산자

삼항 연산자(temary)를 말한다.

const doIt = false;
const result = doIt ? "Did it!" : "Didn't do it.";

쉼표 연산자

쉼표 연산자는 표현식을 결합하여 두 표현식을 평가한 후, 두번째 표현식의 결과를 반환한다.

let x = 0, y = 10, z; 
z = (x++, y++);

이 예제에서 x와 y는 모두 1만큼 늘어나지만 z의 값은 10이 된다. 즉 y++가 반환하는 값이다.

쉼표 연산자는 우선순위가 가장 낮은 연산자이므로 괄호를 사용했다.

만약 괄호를 사용하지 않았더라면 z 에는 0이 저장되고 그다음에 y가 1만큼 늘었을 것이다.

쉼표연산자는 for 문에서 표현식을 결합할 때 사용하거나, 함수에서 빠져나오기 전에 여러가지 작업을 한데 묶을 때 사용한다

1-8. 연산자 그룹

그룹 연산자 (괄호)에는 아무 효과도 없지만 연산자 우선순위를 높이거나 명확히 표현하는데 쓸 수 있다.

따라서 그룹 연산자는 연산 순서만 바꿀 뿐, 다른 부작용은 전혀 없는 안전한 연산자이다.

비트 연산자

비트 연산자는 피연산자를 2의 보수(two’s complement) 형식으로 저장된 32 비트 부호 붙은 정수(signed integer)로 간주한다

자바스크립트의 숫자는 모두 더블 형식이므로 비트연산자를 실행하기 전에 숫자를 먼저 32비트 정수로 변환하고, 그 결과를 반환할 때 다시 더블 형식으로 변환한다.

let n = 22 // 32 비트 바이너리 : 00000000000000000000000000010110
n >> 1     //                 00000000000000000000000000001011
n >>> 1    //                 00000000000000000000000000001011
n =~ n     // 1의 보수:         11111111111111111111111111101001
n ++       // 2의 보수:         11111111111111111111111111101010
n >> 1     //                 11111111111111111111111111110101
n >>> 1    //                 01111111111111111111111111110101

→ 보통 비트연산자를 쓰는 경우가 효율적인 경우는 플래그를 다룰때 이다

  • 예제
const FLAG_EXECUTE = 1 // 0b001
const FLAG_WRITE = 2   // 0b010 
const FLAG_READ = 4    // 0b100

let p = FLAG_READ | FLAG_WRITE;  // 0b110
let hasWrite = p & FLAG_WRITE;   // 0b010 - 참 같은 값
let hasExecute = p & FLAG_EXECUTE; // 0b000 - 거짓 같은 값
p = p ^ FLAG_WRITE;          // 0b100 -- 쓰기 플래그 토글 (이제 쓰기 권한이 없습니다)
p = p ^ FLAG_WRITE;          // 0b110 -- 쓰기 플래그 토글 (쓰기 권한이 다시 생겼습니다)

// 표현식 하나로 여러 플래그를 동시에 판단할 수도 있습니다.
const hasReadOrExecute = p & (FLAG_READ | FLAG_EXECUTE);
const hasReadAndExecute = p & (FLAG_READ | FLAG_EXECUTE) === FLAG_READ | FLAG_ EXECUTE;

typeof 연산자

typeof 연산자는 피연산자의 타입을 나타내는 문자열을 반환한다.

자바스크립트에서는 일곱가지 데이터 타입(undefined, null, 불리언, 숫자, 문자열, 심볼, 객체)를 정확하게 나타내지 못해서 혼란을 초래했고 계속 비판을 받았다.

  • 반환값

void 연산자

이 연산자가 하는 일은 피연산자를 평가한 후 undefined를 반환하는 일 하나 뿐이다.

<a href="javascript:void 0">Do nothing.</a>

할당 연산자

할당연산자에서 왼쪽에 있는 것(l-value)은 반드시 변수나 프로퍼티, 배열 요소중 하나여야 한다. (저장할 수 있는 것이어야 한다)

  • 예제 코드
let v, v0;
v = v0 = 9.8; //먼저 v0가 9.8이되고, 그 다음 v가 9.8이 됩니다.

// while 문의 조건에 있는 할당을 보십시오. 먼저 n이 nums[i]의 값을 받고, 
// 다음에는 표현식 전체가 그 값으로 평가되므로 숫자로 비교할 수 있습니다. 
const nums = [3, 5, 15, 7, 5];
let n, i = 0;
while((n = nums[i]) < 10 && i++ < nums.length) { 
	console.log(`Number less than 10: ${n}.`);
}
console.log(`Number greater than 10 found: ${n}.`); 
console.log(`${nums.length - i - 1} numbers remain.`);
  • 간편 할당 연산자는 따로 참고

1-9. 해체 할당

ES6 에서 새로 도입한 헤체 할당(destructuring assignment)은 매우 환영 받는 기능.

이 기능은 객체나 배열을 변수로 **'해체'**할 수 있다

// 객체 선언
const obj = {b: 2, c: 3, d: 4};

// 해체 할당
const {a, b, c} = obj;
a; // undefined: obj에는 "a" 프로퍼티가 없습니다.
b; // 2
c; // 3
d; // ReferenceError: "d"는 정의되지 않았습니다.
  • 객체 해체는 할당만으로 이뤄질 수 도 있지만, 그렇게 하려면 반드시 괄호를 사용해야 한다
const obj = {b: 2, c: 3, d: 4}; 
let a, b, c;

// 에러가 일어납니다. 
{a, b, c} = obj;

// 동작합니다.
({a, b, c} = obj);

               ⇒ 괄호를 쓰지 않으면 자바스크립트는 표현 식 좌변을 블록으로 해석한다

  • 배열을 해체할 때에는 배열 요소에 대응할 변수 이름을 마음대로 쓸수 있다.
// 배열 선언
const arr = [1, 2, 3];

// 배열 해체 할당 
let [x, y] = arr;
x;                   // 1
y;                   // 2
z;                   // ReferenceError: "z"는 정의되지 않았습니다.

               ⇒ 배열 순서대로 대응된다

  • 확장 연산자를 이용하여 남은 요소를 새 배열에 할당할 수 있다
const arr = [1, 2, 3, 4, 5];

let [x, y, ...rest] = arr;
x;                      // 1
y;                      // 2
rest;                   // [3, 4, 5]
  • 배열 해체를 이용하여 변수의 값을 서로 바꿀 수 있다
let a = 5, b = 10; 
[a, b] = [b, a];
a;                  // 10
b;                  // 5

해체의 진가는 다른 곳에서 가져온 객체나 배열에서 원하는 요소를 뽑아내야 할 때 드러난다.

1-10. 객체와 배열 연산자

다른 장에서 설명하니 어디서 설명하는지 간단히 요약만 한다.

1-11. 템플릿 문자열과 표현식

(3장에서 설명됨) 어떤 표현식이든 그 값을 문자열에 넣을 수 있다.

예제는 섭씨 대신 화씨온도를 표현식을 사용하여 표시하는 방법

const roomTempC = 21.5;
let currentTempC = 19.5;
const message = `The current temperature is ` +
		`${currentTempC-roomTempC}\u00b0C different than room temperature.`; 
const fahrenheit =
		`The current temperature is ${currentTempC * 9/5 + 32}\u00b0F`;

1-12. 표현식과 흐름 제어 패턴

제어문 패턴에 대해 알아보자

if ...else 문을 3항 연산자로 바꾸기

if(isPrime(n)) { 
	label = 'prime';
} else {
	label = 'non-prime'; 
}

// 삼항연산자로 변환
label = isPrime(n) ? 'prime' : 'non-prime';

if 문을 단축 평가하는 OR 표현식으로 바꾸기

할당이 주 목적인 if문은 단축평가를 사용하는 OR 표현식을 쓰면 간결하게 줄일 수 있다.

if(!options) options = {};

// OR 표현식으로 변환
options = options || {};