[javascript] 타입과 문법
타입
내장 타입
null
undefined
boolean
number
string
object
symbol (ES6 ~)
null과 typeof
typeof null === "null" // false
typeof null === "object" // true
20년간 유지된 버그.
null값 확실히 확인
let isNull = (n) => !n && typeof n === "object";
undefined vs is not defined
동의어로 여겨지기도 하지만 다른 개념. 둘을 구분할수는 없지만 typeof는 변수가 정의되지 않아도 오류를 뿜지 않아 대처는 가능.
undefVal; // error
typeof undefVal // 'undefined'
전역변수 체크방식
if(window.Global) // X
위의 방식은 node 등의 다중 자바스크립트 환경을 고려하면 좋은 방법이 아니다. node만 해도 전역 객체가 window가 아닌 global이기 때문. 아래의 방식이 쓸만하다.
if(typeof Global !== "undefined") // O
배열
typeof [] // object
배열은 객체이기도 하기 때문에 Key / Property 추가 가능 (length는 늘어나지 않음). 하지만 키 프로퍼티를 배열에서 쓰는건 추천하지 않음.
let arr = []
arr["100"] = 1;
a.length // 101
이 사단이 날 수 있기 때문
문자열
문자열은 여러모로 배열과 닮음. length 있고 indexOf, concat, push 등 배열처럼 가능.
하지만 문자열은 불변이라는 차이가 있음.
숫자
1E3 // 1 * 10^3
15E10 // 15 * 10^10
정수 체크
Number.isInteger(1); // true
Number.isInteger(1.1); // false
작은 소수값
0.1 + 0.2 === 0.3 // false
부동 소수점 방식이라 안 맞음. 이런 미세한 두 값 간의 차이를 머신 입실론이라고 함.
Number.EPSILON == Math.pow(2, -52) // 2^-52 -> EPSILON
이걸로 차이를 구할 수 있음.
const isEqualbothDecimal = (a, b) => Math.abs(a - b) < Number.EPSILON;
그래서 정수는 안전의 값이 정해져 있는데, 이 값의 범위 안에서는 안전하게 표현할 수 있는 것.
Number.MIN_SAFE_INTEGER < num < Number.MAX_SAFE_INTEGER
특수한 값
null은 예전에는 값이 있었거나 지금은 빈 값.
undefined는 값이 아직 없거나 undefined로 할당된 상태
void 연산자
어떤 값이든 무효로 만들어 undefined로 만듬.
let a = 2
console.log(void a, a) // undefined, 2
관례적으로 void 0으로 많이 쓰는데, 자주 쓰이진 않지만 어떤 식의 결과값이 없다는 것을 확실히 밝힐 때 요긴하다.
NaN (Not a Number)
글자 그대로 숫자가 아닌 것. 하지만 NaN의 타입은 숫자다.
typeof NaN // "number"
게다가 NaN끼리는 늘 틀림.
NaN === NaN // false
그래서 NaN끼리 비교하는 함수가 있지만 문제적인 함수임.
window.isNaN(2 * 'hi') // true
window.isNaN('hi') // true
그저 숫자인지만 확인하기 때문에, Number.isNaN을 써야한다.
Object.is()
-0 == 0 // true
-0 === 0 // true
0 > -0 // false
0과 -0은 값이 0이 되는 순간 그 직전까지의 이동 방향을 알아야 하기 때문에 중요하지만 단순 비교로는 난해한 결과를 내기 때문에 Object.is 함수를 사용한다.
Object.is(0, -0) // false
Native (내장 함수)
let str = new String("hi")
typeof str // "object" => string 아님
String 함수는 문자열을 생성하지만 문자열 래퍼를 생성하여 원시값 문자와는 다르다. 일례로,
if(false) // false
if(new Boolean(false)) // true
상단의 예는 Boolean으로 객체 래퍼가 생성되었기 때문에 객체로 인식되어 falsy가 아니기 때문에 true.
내장 함수로 래퍼 없이 변환하려면 new 키워드를 빼버리면 된다.
String('hi') // "hi"
Number(1000) // 1000
원시 값
원시 값은 valueOf 함수로 구할 수 있다.
(new String("hi")).valueOf() // "hi"
(new Boolean(false)).valueOf() // false
Array
배열에는 크기를 미리 정하는 기능이 있다.
let a = [];
a.length = 100;
a // (100) [empty × 100]
new Array(100) // (100) [empty × 100]
그런데 저 빈 슬롯들은 구멍 난 배열 (sparse array) 이라고 하는 문제아들이라는 것. 즉 슬롯 자체가 존재하지 않는다. 그러니 진짜 undefined 값으로 채워진 배열을 생성해야 한다.
Array.apply(null, { length: 100 }) // undefined x 100
강제변환
추상 연산
ToString
ToNumber
ToBoolean
ToPrimitive
암시적 변환
장황함, 보일러 플레이트, 불필요한 상세적 구현을 방지하는 장점이 있다.
&&와 ||
이는 다른 언어와는 많이 다르다. 첫번째 피연산자의 불리언 값을 평가한다는 공통점이 있다.
||는 결과가 true면 첫번째 피연산자, false면 두번째 피연산자 값을 반환한다.
웬만하면 삼항연산자로 더 명시적인 식을 구성하는 것이 권장됨.
&& 는 반대로 true면 두번째 피연산자, false면 첫번째 피연산자 값을 반환함.
첫번째 피연산자의 결과가 truthy일때만 두번째 연산자를 선택하는 것, 이를 가드 연산자라고 하는데 첫번째 표현식이 두번째의 가드 역할을 하기 때문.
let isFlag = false;
!isFlag && flag()
위의 코드는 플래그가 없으면 flag함수를 호출한다.
*&&는 ||보다, ||는 삼항연산자보다 우선순위가 높다.
비교
2 == "2" = 2 == Number("2")
"2" == 2 = Number("2") == 2
42 == [42] // true
위의 결과는 [42]가 ToPrimitive 추상 연산을 거친다. 그런 다음 42 == 42 는 true.
비교 순서
a == 1 && a == 2
위의 둘은 동시에 비교되는것이 아니다. 엄밀히 a == 1 가 먼저 평가됨.
이로서 ==는 꽤나 머리아프니, === 를 권장한다.
24 == "24" // X
24 === +"24" // O
즉 둘의 비교는 비교 시 강제변환을 허용하느냐의 문제.
하지만 You Don't Know JS 저자의 주장대로 == 역시 개발자가 조심할 능력이 된다면 사용해도 무리없다는 논리도 납득할 수 있다.
추상적 관계 비교
[42] < ["43"] // true
[42] > ["43"] // false
어느 한쪽이라도 문자열이 아니면 양쪽 모두 ToNumber로 강제변환한다.
["42"] < ["043"] // false
하지만 위처럼 모두 문자열이라면 문자를 단순 어휘 (ASCII ??) 로서 비교함.
부수효과
모든 문은 완료값을 가지며, 하단의 코드 역시 마찬가지다.
let a = b; // undefined
var a;
(a = 24) // 24
equal문은 반환값이 존재한다.
switch문
switch문은 case문에 === 를 사용한다. == 를 사용하고 싶다면 아래의 코드를 참고하면 된다.