표현식과 연산자는 프로그래밍 언어의 기본적인 구성 요소입니다. JS의 표현식과 연산자 표기법은 C와 자바의 그것과 매우 비슷하지만, JS에는 래퍼 객체가 있고 묵시적 타입 변환을 할 수 있다는 차이점이 있습니다.
1. 연산자
표현식과 연산자
표현식은 결과적으로 어떤 값으로 평가(evaluation)되는 것인데, '표현식을 평가한다'는 표현식의 값, 변수, 함수 등의 값을 바탕으로 식의 값을 계산하는 행위를 말합니다.
가장 간단한 표현식은 숫자, 문자열, 논리값 등의 원시 값(primitive values)이며 변수, 프로퍼티, 배열 요소, 함수 호출, 메서드 호출도 표현식입니다. 연산자(operator)를 사용하면 표현식을 조합해 더욱 복잡한 표현식을 만들 수 있습니다.
a + b
a(피연산자, operand) +(연산자, operator) b(피연산자) 로 위의 예는 2항 연산자입니다.
피연산자의 개수에 따라 연산자를 단항, 이항, 삼항 연산자로 구분합니다. 피연산자 개수가 하나면 단항, 세개면 삼항연산자이며 ?: 뿐입니다.
표현식은 왼쪽 피연산자 값부터 순서대로 평가되고 그 다음에 연산자로 계산이 시작됩니다. 예외로 ||, &&, ?:는 연산자가 먼저 평가된 후 피연산자값을 평가합니다.
연산자의 우선순위
연산자가 여러 개 있으면 계산 순서에 따라 결과가 달라집니다. 연산자에는 우선순위가 있고, 그에 따라 연산 순서가 정해집니다. 괄호 등의 그룹 연산자를 사용하면 괄호 안의 표현식이 가장 먼저 평가됩니다.
연산자 결합 법칙
결합법칙에 따라 연산자의 우선순위가 같으면 왼쪽에서 오른쪽 방향으로 결합합니다.
연산자의 부수 효과
부수 효과가 있는 표현식 : 대입하는 표현식 x = y 처럼 변수 값을 바꾸는 표현식으로 대입연산자, 증가연산자, 감소연산자, delete 가 있습니다.
2. 산술 연산
산술 연산자
산술연산자는 피연산자가 숫자인 연산자로 숫자가 아닐 때는 연산자가 피연산자 타입을 숫자로 바꾸어 연산합니다. 그러므로 결과도 숫자 값이 나오지만 피연산자가 숫자로 바꿀 수 없거나 계산이 불가할 때는 NaN이 나오게 됩니다. 모든 산술 연산은 64비트 부동소수점 연산으로 이루어집니다.
산술 이항 연산자
+, -, *, /, %
사용 시 주의점
-
정수끼리 나누어도 결과는 부동소수점
- 다른언어는 정수끼리 나눈 결과값이 정수이나, JS는 정수끼리 나누어도 모두 부동소수점
7 / 2 // 3.5
-
부동소수점으로 a%b 계산 가능
-
+ 연산자는 피연산자 중 하나가 문자열이면 나머지 피연산자도 문자열로 만듦
-
계산 불가하면 NaN으로 평가 (undefined도 마찬가지)
- true 는 1, false, null은 0
0/0 // NaN
"one" * 1 // NaN
true+true // 2
1+null // 1
1+undefined // NaN
산술 단항 연산자
증가연산자는 피연산자값에 1을 더하고 감소연산자는 1을 뺍니다. 모두 부수 효과가 있는 연산자이며 피연산자는 좌변에 둡니다. 증감연산자는 전위표기법이냐 후위표기법이냐에 따라 피연산자의 평가 시점이 달라져 결과값이 다르게 나오게 됩니다. 대입연산자의 왼쪽에 둘 수 있는 표현식인 좌변값은 JS에서 변수, 객체의 프로퍼티, 배열 요소를 둡니다.
* 증감연산자를 연속 사용하면 참조 오류가 발생합니다. (a++)++ 처럼요.
++a // a에서 1을 더한 후 a 값을 평가
a++ // a 평가 후 1을 더함
--a // a에서 1을 뺀 후 a를 평가
a-- //a 평가 후 1을 뺌
+a //아무코토하지않음
-a //부호 반전
산술 대입 연산자
대입연산자와 산술이항연산자를 조합한 연산을 좀 더 간략하게 표현한 것입니다.
+= // a += b (a=a+b 와 같음)
-=
*=
/=
%=
Math 객체의 프로퍼티
Math 객체에는 다양한 프로퍼티와 메서드가 있습니다. 메서드와 설명을 더 보시려면 링크를 확인.
부동소수점과 정확도 문제
산술 연산 시 숫자에 유효한 자리수가 있어 계산 시 오차가 발생합니다. JS 숫자는 IEEE754로 규정된 64비트 부동소수점입니다. IEEE 754는 IEEE에서 개발한 컴퓨터에서 부동소수점을 표현하는 가장 널리 쓰이는 표준으로 ±0 등의 수와 무한, NaN 등의 기호를 표시하는 법과 이러한 수에 대한 연산을 정의하고 있습니다.
일반적으로 값이 가까운 두 수를 뺄 때 정밀도 손실이 일어납니다. 피하는 방법 중 하나로 다음처럼 바꾸어 계산할 수도 있습니다.
1/(Math.sqrt(10001) + Math.sqrt(10000))
10진수로 딱 떨어지는 값도 산술연산을 내부적으로는 2진수로 계산하여 결과가 조금씩 어긋납니다. 아래식으로 해결할 수 있습니다.
a / b // 0.799999999999
a/b == 0.8 // false
Math.abs(a/b - 0.8) < 1e-10
3. 문자열 제어
문자열 연결
+ 연산자로 연결합니다. 피연산자 중 하나가 문자열로 변환할 수 있는 객체라면 다른 피연산자의 타입을 문자열로 바꾼 후 연결합니다.
문자열 조작 메서드
문자열 처리 객체로 String 객체가 있습니다. 문자열을 String 객체로 변환하려면 String 생성자를 사용합니다. 아래처럼 원시 값을 객체로 변환하는 행위는 래핑(wrapping)이라고 합니다. 스트링 객체에는 문자열을 처리하기 위한 다양한 프로퍼티와 메서드가 있습니다.
String 객체의 주요 메서드 >> 이동
문자열은 객체가 아니므로 프로퍼티가 없지만, 문자열에서 프로퍼티를 사용하려고 하면 자동으로 String 객체로 변환되어 동작하게 됩니다. 내부적으로는 아래와 같은 작업이 실행되며, 처리가 끝나면 메모리에서 바로 삭제됩니다.
var msgObj = new String(msg);
var c = msgObj.charAt(3);
이 객체를 래퍼 객체(wrapper object)라고 하며, 원시 값 처리 시 원시값은 래퍼 객체로 자동 변환됩니다. 문자열은 String 객체, 숫자는 Number 객체, 논리값은 Boolean 객체로 변환됩니다. null, undefined에는 wrapper 객체가 없습니다.
var msgObj = new String(msg);
var c = msgObj.charAt(3);
원시 값을 처리할 때는 원시값의 메서드를 바로 호출하며 String 객체 등으로 다시 변환해 처리하진 않습니다. 기대한 것과 다른 값이 불러졌을 때 오류가 발생할 수도 있기에 그렇습니다. 변환된 String 객체는 valueOf() 메서드를 사용해 다시 문자열로 변환해 출력할 수 있습니다.
var msg = new String("test");
console.log(msg); // String{[[PrimitiveValue]]: "test"}
msg.valueOf();
console.log(msg); //test
// ??? 예상결과인데 다 마지막 줄처럼 나옴
자바스크립트의 문자열은 불변 (immutable) 합니다. replace, toUpperCase 메서드 등이 반환하는 것은 새로운 문자열이며 메서드를 호출한 문자열은 수정되지 않습니다.
String 생성자의 메서드
JS의 함수는 객체이며 프로퍼티를 가집니다. String 생성자 또한 일종의 함수이며 프로퍼티를 가집니다.
프로퍼티 | ||
String.length | 항상 1 | |
String.fromCharCode() | 인수로 넘긴 문자 코드 목록(UTF-16 인코딩값)으로 문자열을 반환 | |
String.fromCodePoint() | 인수로 넘긴 코드 포인트 목록으로 문자열을 반환 | Es6 |
String.prototype | String의 프로토타입 객체 | |
String.raw() | 템플릿 문자열의 원시 문자열 형식을 반환 | Es6 |
문자열을 배열로 읽고 쓰기
문자열을 읽을 때에는 charAt 메서드 대신 대괄호 연산자도 가능합니다. 그러나 배열처럼 값을 대입, 수정은 불가능합니다. 써로게이트 페어로 표현하는 문자는 두 개의 배열 요소로 분리합니다.
msg[3]
msg[msg.length-1]
s.codePointAt(0).toString(16) // 코드 포인트의 스칼라값
s[0].codePointAt(0).toString(16) // 상위 써로게이트
s[1].codePointAt(0).toString(16) // 하위 써로게이트
s[0] + s[1] // 써로게이트 페어 문자
써로게이트 페어 (surrogate pair) : 2바이트의 인코딩 값을 한 쌍 (a,b)로 묶은 것으로 a는 상위 써로게이트, b는 하위 써로게이트라고 합니다. 유니코드의 코드포인트 값 u를 구하는 공식은 다음과 같습니다.
u = (a - 0xD800) * 0x400 + (b-0xDC00) + 0x10000
a = (u - 0x10000) / 0x400 + 0xD800
b = (u - 0x10000) % 0x400 + 0xDC00
stringToArray는 정규표현식을 사용해서 써로게이트 페어가 포함된 문자열을 문자 배열로 만들어주는 함수입니다. 이를 사용하면 써로게이트 페어를 포함하는 문자열도 배열로 만들 수 있습니다.
4. 논리 연산자와 관계 연산자
관계 연산자
관계 연산자는 두 개의 피연산자를 비교한 결과를 논리값 (true/false)로 반환합니다. 주로 제어구조 (if/else, while, do/while, for문)에서 조건식을 만들 때 사용합니다.
연산자 | 뜻 | 예제 | 예제 뜻 |
== | 값이 같음 | a == b | a값과 b값이 같으면 true, 그 외 false |
!= | 값이 다름 | a != b | a값과 b값이 다르면 true, 그 외 false |
=== | 값, 타입이 같음 | a === b | a와 b의 값, 타입이 같으면 true, 그 외 false |
!== | 값, 타입이 다름 | a !== b | a와 b의 값, 타입이 다르면 true, 그 외 false |
< | 작음 | a < b | a값과 b값이 작으면 true, 그 외 false |
> | 큼 | a > b | a값과 b값이 크다면 true, 그 외 false |
<= | 작거나 같음 | a <= b | a값이 b값보다 작거나 같으면 true, 그 외 false |
>= | 크거나 같음 | a >= b | a값이 b값보다 크거나 같으면 true, 그 외 false |
동일연산자 (==)
좌,우변의 피연산자가 동일한지를 판별하는데, 내부적으로 피연산자들의 타입을 변환한 후 서로 같은지 비교합니다.
좌우피연산자의 타입이 같으면 true, false로 판정합니다. 원시타입 변수의 값과 객체타입 변수의 값은 내용이 다르기에 값의 의미가 달라집니다. (데이터 자체 vs 객체의 참조) 따라서 같은 객체를 가리키는지 판별하는 것과 같습니다.
좌우 피연산자의 타입이 다르면 두 피연산자가 같은 타입이 되도록 변환 후 다음 규칙에 따라 동일 여부를 판별합니다.
- undefined, null은 같은 것으로 한다
- 한쪽이 숫자고 다른 쪽이 문자열이면 문자열을 숫자로 변환해 비교한다
- 둘 중 한쪽이 논리값이면 true는 1, false는 0으로 변환해 비교한다
- 한쪽이 객체고 다른쪽이 숫자나 문자열이면 객체를 toString이나 valueOf 메서드를 사용해서 원시타입으로 변환 후 비교한다
- 규칙에서 벗어나면 모두 같지 않음으로 판정.
일치연산자 (===)
피연산자를 평가한 후 타입을 변환하지 않은 상태의 두 값을 비교합니다. 타입과 값이 모두 같아야 같다고 판정합니다. Nan은 NaN 을 포함한 모든 값과 같지 않다(false)로 판정합니다. (변수 값이 NaN인지는 isNaN 함수를 활용하면 됩니다.)
논리 연산자
관계연산자를 사용하여 만든 논리식과 결합해 복잡한 논리를 정리합니다. 관계연산자의 우선순위가 논리 연산자보다 높으므로 그럴 경우 괄호를 사용할 필요가 없습니다.
연산자 | 뜻 | 예제 | 예제 뜻 |
&& | 논리곱 | a && b | a와 b가 모두 true면 true, 그 외 false |
|| | 논리합 | a || b | a와 b 중 하나라도 true면 true, 모두가 false면 false |
! | 부정 | !a | a가 true면 false, false면 true |
피연산자의 평가
논리 연산자의 피연산자는 논리값이 아니어도 됩니다. 논리값이 아닐 경우 피연산자의 타입을 변환합니다.
-
0, -0, 빈문자열, NaN, null, undefined : false
-
0을 제외한 숫자, 빈문자열을 제외한 문자열, 모든객체, 심벌 : true
논리곱 연산자와 논리합 연산자의 단락 평가
&& 연산자와 || 연산자는 단락평가(short-circuit evalution)를 합니다. 즉 첫 번째 피연산자값이 표현식을 결정하면 두 번째 피연산자를 평가하지 않는 것을 말합니다. 또한 논리곱, 논리합연산자가 가진 자바와의 차이점은, true, false라는 논리값 대신 마지막으로 평가한 피연산자 값을 반환합니다.
a && b
a 평가값이 false이면 값과 무관하게 표현식 전체값이 false가 되며, b 값은 평가하지 않습니다. 이 때 && (논리곱) 연산자는 false 대신 a의 값 자체를 반환합니다. a 의 평가값이 true라면 전체 표현식 값은 b의 값으로 결정됩니다. true/false 대신 b의 값 자체를 반환하고요. 이렇더라도 논리값이 필요한 장소에서는 논리값으로 타입이 변경되어 식이 정상 작동합니다. (...라는데 안됨..)
&& 논리곱연산자는 객체의 프로퍼티를 이용할 때와 객체가 null인지의 여부를 체크할 때 자주 쓰입니다.
var p = null;
p && p.name // null : p가 false로 평가되므로 p 반환. 우변 평가 x
p = { name: "Tom", age: 18 }'
p && p.name // "Tom" : p가 true로 평가되므로 p.name 반환
논리합연산자 (||)도 값 자체를 반환하는 것은 마찬가지입니다. || 연산자는 여러 값 후보 중 null 또는 undefined가 아닌 값을 선택할 때 유용하게 쓰입니다. time_interval과 animationSettings의 프로퍼티가 정의되어있지 않다면 정수 33을 값으로 사용합니다. (...라는데 안됨..)
var time = time_interval || animationSettings.time || 33;
또한 논리합 연산자는 함수에서 인수의 초기값을 설정할 때 유용하게 사용할 수 있습니다.
아래 예시의 함수 f를 인수 없이 f()라고 호출하면 인수 x에는 undefined가 들어가고, false로 평가되므로 x는 100이 됩니다. f(2) 이런식으로 인수를 지정해주면 x는 true가 되어 2가 됩니다. 단점은 x에 false라고 평가되는 값 (0, 빈문자열) 등을 넘기면 그 값이 사용되지 않고 초기값(예시는 100)이 사용된다는 것입니다.
function f(x) {
w = w || 100 ;
}
5. 비트연산
비트 연산은 2진수 숫자의 자리별 값(비트 값)을 다루는 연산입니다. 비트연산자는 피연산자를 부호 있는 32비트 정수로 변환해서 처리합니다. 비트연산자에는 비트논리연산자와 비트시프트연산자가 있으며 모든 비트연산자에는 대입연산자를 사용할 수 있습니다.
비트 논리 연산자
비트 값이 0이면 false, 1이면 true로 평가하는 연산자입니다. 자바스크립트에서는 음의 정수를 보수를 사용해서 표현합니다. a의 보수를 ~a+1이라고 표현하는 공식에 따라 ~105 = 105 - 1 = -106 이 됩니다.
비트 논리 연산자
연산자 |
뜻 |
예제 |
예제 뜻 |
& |
비트 논리곱 (AND) |
105 & (-91) |
33 = 00100001 |
| |
비트 논리합 (OR) |
105 | (-91) |
-19 = 11101101 |
^ |
비트 배타적 논리합 (XOR) |
105 ^ (-91) |
-52 = 11001100 |
~ |
비트 논리 부정 (NOT) |
~105 |
-106 = 10010110 |
논리 연산의 진리표
p |
q |
p∧q |
p∨q |
p(+)q |
1 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
p |
~p |
1 |
0 |
0 |
1 |
비트 시프트 연산자
정수를 2진수 비트 단위로 좌/우측으로 이동(시프트)시키는 연산자입니다. 지정된 수만큼 지정 방향으로 이동시키는데 빈자리는 0으로 채워지고 오버플로된 쪽의 비트 값은 버려집니다.
연산자 | 뜻 | 예 | 예제의 값 (32비트 값) |
<< | 왼쪽 시프트 | 105 << 3 | 840 |
>> | 부호 있는 우측 시프트 | -91 >> 3 | -12 |
>>> | 부호 없는 우측 시프트 | -91 >>> 3 | 536870900 |
비트 대입 연산자
비트 연산의 대입연산자를 사용할 수 있습니다.
연산자 | 예 | 뜻 | 연산자 | 예 | 뜻 |
&= | a &= b | a = a & b | <<= | a <<= b | a = a << b |
|= | a |= b | a = a | b | >>= | a >>= b | a = a >> b |
^= | a ^= b | a = a ^ b | >>>= | a >>>= b | a = a >>> b |
6. 기타연산
typeof 연산자 : 단항 연산자이며 피연산자의 데이터타입을 뜻하는 문자열을 반환합니다. 함수 이외의 객체에 대해 모두 object를 반환하여 객체 유형을 알아보려면 instanceof 연산자와 객체의 constructor 프로퍼티를 사용합니다.
var s = "ABC";
console.log(typeof s); //string
?: (조건 연산자) : 3항 연산자로 첫번째 피연산자를 조건식으로 평가한 후 결과에 따라 값을 선택합니다.
var parity = (a % 2 == 0)? "짝수" : "홀수";
// 괄호 안의 값이 true면 짝수 false면 홀수
// 같은 결과
if(a%2 ==0) {
parity = "짝수";
}else{
parity = "홀수";
}
void : 정의되지 않은 값 반환
, (쉼표 연산자) : 이항연산자로 왼쪽에서 오른쪽 순서대로 피연산자를 평가한 후 우측 끝 피연산자의 값을 반환합니다. for문 소괄호 안에서 자주 사용합니다.
i = 0, sum = 0, product = 1;
eval 함수 : 연산자는 아니지만 연산자의 역할을 담당합니다. 문자열 하나만을 인수로 받아 JS 코드로 해석합니다. 코드를 평가한 후 마지막 표현식이나 문장의 값을 반환합니다. 문자열을 해석할 수 없을 시 문법 오류를 반환합니다.
함수가 실행되면 eval 함수를 호출한 환경의 유효범위 내의 변수를 사용하며 일반 함수처럼 함수 유효범위를 만들지 않습니다. eval함수를 호출하는 행위는 인수로 받은 문자열을 코드로 바꿔 eval 함수를 호출한 부분과 맞바꾸겠다는 뜻입니다.
var x = 1;
eval("x++;");
// 이와 같음
// var x = 1;
// x++;
eval 함수로 실행한 코드는 속도가 느리며, 악의적으로 입력한 문자열을 eval 함수에 인수로 넘기면 악성코드가 실행되어 보안 문제가 생길 수도 있는 단점이 있습니다. 따라서 eval 함수 사용 시 인수의 문자열을 분석해서 안전을 확인 후 실행해야합니다.
delete : 객체의 프로퍼티나 배열 요소를 제거
new : 새로운 객체 생성
in : 객체의 프로퍼티 포함 여부를 확인
instanceof : 객체의 종류 확인
7. 명시적 타입 변환
JS는 피연산자의 타입을 묵시적으로 변환해 유연하게 연산을 시행하지만, 변수 값을 출력할 때처럼 명시적으로 타입을 변환해야 할 경우도 있습니다. 아래의 방법을 사용합니다.
숫자를 문자열로 변환
-
숫자 + 문자열
-
Number 객체의 메서드 활용 (Number.prototype)
-
String 함수 사용 : new 연산자를 붙여 String 객체를 생성하지 말고 String() 이런 형태로 사용하면 문자열을 반환
문자열을 숫자로 변환
-
수식 안에서 문자열을 숫자타입으로 묵시적으로 변환
var s = "2";
s-0 //2
+s; //2
-
parseInt와 parseFloat 함수 사용 : 문자열을 해석(parse)해서 숫자로 바꿉니다. 문자를 해석할 수 없을 때는 NaN을 반환합니다. 문자열 앞의 공백은 무시합니다.
-
Number 함수 활용
논리값으로 변환
모든 값을 논리값으로 바꾸는 방법은 두가지입니다.
!!x
Boolean(x)
'Javascript' 카테고리의 다른 글
자바스크립트 배열 메서드 array reduce (0) | 2020.06.12 |
---|---|
html 문서간 변수값 주고받기 (0) | 2020.06.10 |
[JS] 자바스크립트 :: 의 의미 JavaScript double colon (이중콜론) (0) | 2020.05.21 |
[JS] 정규식 (정규표현식) 정리 (0) | 2020.05.15 |
[JS] Window.localStorage 로 값 조회, 추가(저장), 수정, 삭제 (3) | 2020.04.16 |