You Don't Know JS - 1.타입과 문법 - 2.값
[ You Don’t Know JS 정리 ]
2. 값
2.1 배열
2.1.1 유사 배열
2.2 문자열
2.3 숫자
2.3.1 숫자 구문
2.3.2 작은 소수 값
2.3.3 안전한 정수 범위
2.3.4 정수인지 확인
2.3.5 32비트 (부호 있는) 정수
2.4 특수 값
2.4.1 값 아닌 값
2.4.2 Undefined
2.4.3 특수 숫자
2.4.4 특이한 동등 비교
2.5 값 vs 레퍼런스
2.1 배열
어떤 타입의 값이라도 담을 수 있는 그릇
배열 크기는 미리 정하지 않고도 선언할 수 있으며 원하는 값을 추가하면 된다.
빈/빠진 슬롯이 있는 ‘구멍 난’ 배열을 다룰 때는 조심
1
2
3
4
5
6
7
8
9
10var a = [];
a[0] = 1;
a[2]= [3]; // a[1] 건너뜀
a[1]; // undefined
a.length; // 3
/*
명시적으로 a[1] = undefined 준 것과 똑같지는 않다.
*/배열의 인덱스는 숫자인데, 배열 자체도 하나의 객체여서 키/프로퍼티 문자열을 추가할 수 있지만 배열 length가 증가하지는 않는다
1
2
3
4
5
6var a = [];
a[0] = 1;
a["foobar"] = 2;
a.length; //1
a.foobar; // 2키로 숫자형태의 문자열이 들어가면, 마치 문자열 키가 아닌 숫자 키를 사용한 것과 같은 결과를 초래
1
2
3var a = [];
a["13"] = 42;
a.length; // 14일반적으로 배열에 문자열 타입의 키/프로퍼티를 두는 것을 추천하지 않는다. 그렇게 해야 한다면 객체를 대용하고 배열 원소의 인덱스를 확실히 숫자만 쓰자
2.1.1 유사 배열
- 유사 배열 값을 진짜 배열로 바꾸고 싶을 때
indexOf(),concat(),forEach()등을 사용하는 것이 일반적Array.prototype.slice.call():slice()에 인자가 없으면 기본 인자 값으로 구성된 배열을 복사한다.- ES6부터
Array.from()이 이 일을 대신한다.
2.2 문자열
문자열은 유사배열
1
2
3var a = new String('23')
a; // String {"23"}
// 0: "2" 1: "3".length,.indexOf(),.concat()메서드 가짐하지만 문자열 !== 배열
문자열 : 불변 값(immutable value)
- 문자열 메서드는 그 내용을 바로 변경하지 않고 항상 새로운 문자열을 생성한 후 반환한다.
배열 : 가변 값(mutable value)
- 대부분의 배열 메서드는 그 자리에서 곧바로 원소를 수정한다.
a[1]처럼 문자열의 특정 문자를 접근하는 형태가 모든 자바스크립트 엔진에서 유효하지는 않다. (IE 8 이전까지는 이를 문법 에러로 인식) =>a.charAt(1)로 접근해야 맞다.문자열을 다룰 때 유용한 대부분의 배열 메서드는 사실상 문자열에 쓸 수 없지만 빌려 쓸 수는 있다.
1
2
3
4
5var a = "foo";
var b = ["f", "o", "o"];
a.reverse();// undefined
b.reverse(); //["o", "o", "f"]문자열을 배열 => 원하는 작업 수행 => 다시 문자열로 되돌리기
1
2var c = a.split("").reverse().join("")
c; // "oof"
2.3 숫자
- 숫자 타입은 number가 유일
- 정수, 부동 소수점 숫자 모두 아우른다
2.3.1 숫자 구문
자바스크립트 숫자 리터럴은 10진수 리터럴로 표시한다.
1
2var a = 42;
var b = 42.3;소수점 앞 정수가 0이면 생략 가능
1
2var a = 0.42;
var b = .42;소수점 이하가 0일 때도 생략 가능
1
2var a = 42.0;
var b = 42;- 하지만
42.처럼 표기하는 것은 일반적이지도 않고 다른 사람이 코드를 읽을 때 혼동을 일으킬 수 있으니 권장하지 않는다.
- 하지만
toExponential(): 아주 크거나 아주 작은 숫자를 지수형으로 표시toFixed(): 지정한 소수점 이하 자릿수까지 숫자를 나타냄위 메서드들은 숫자 리터럴에서 바로 접근할 수 있으므로 굳이 변수를 만들어 할당하지 않아도 된다. 하지만
.이 소수점일 경우엔 프로퍼티 접근자가 아닌 숫자 리터럴의 일부로 해석되므로 주의하자.1
2
3
4
542.toFixed(3) // SyntaxError
// 42.이 리터럴의 일부가 되어버려 .toFixed 메서드에 접근할 .이 없다
(42).toFixed(3); //"42.000"
0.42.toFixed(3); // "0.420"0다음 대문자 O는 헷갈리니 언제나 소문자로 0o, 0b 와 같이 표기하자
2.3.2 작은 소수 값
이진 부동 소수점 숫자의 부작용
1
0.1 + 0.2 === 0.3 //false
- 실제로는 0.30000000000000004에 가깝기 때문에 정확하게 0.3이 아니다.
- 가장 일반적으로 미세한 ‘반올림 오차’(머신 입실론)를 허용 공차로 처리하는 방법
Number.EPSILON(ES6부터)
2.3.3 안전한 정수 범위
- 안전하게 표현할 수 있는 (즉. 표현한 값과 실제 값이 정확하게 일치한다고 장담할 수 있는) 정수는 최대 9천 조가 넘는 수
- 9천 조가 넘는 수의 사용해야 한다면 Big Number utility 사용을 권장
2.3.4 정수인지 확인
- ES6부터는
Number.isInteger()로 어떤 값의 정수 여부를 확인한다.1
Number.isInteger(42); //true
2.3.5 32비트 (부호 있는) 정수
스킵
2.4 특수 값
2.4.1 값 아닌 값
undefined와null은 타입과 값이 같다null은 식별자가 아닌 특별한 키워드이므로null이라는 변수에 뭔가를 할당할 수 없다.
2.4.2 Undefined
Normal mode에서는 전역 스코프에서
undefined라는 식별자에 값을 할당할 수 있다. (절대 추천하지 않음)1
2
3
4
5
6
7
8
9
10function foo() {
undefined = 2;
}
foo();
function foo() {
undefined = 2; // 타입 에러
}
foo()Void 연산자
표현식 void는 어떤 값이든 ‘무효’로 만들어, 항상 결과값을 undefined로 만든다.
void 연산자는 어떤 표현식의 결과값이 없다는 것을 확실히 밝혀야할 때 좋다
1
2
3
4
5
6
7
8
9
10
11function doSomething() {
if(!App.ready) {
return void setTimeout(doSomething, 100);
// setTimeout은 숫자 값을 반환 하지만 이 숫자 값을 무효로 만들어
}
var result;
return result;
}
if(doSomething()) {
// doSomething()의 결과값이 if 문에서 긍정 오류가 일어나지 않게 했다.
}하지만 밑에 코드처럼 두 줄로 분리해서 쓰는 것을 선호하는 개발자들이 많고 void연산자는 잘 쓰지 않는다.
1
2
3
4if(!App.ready) {
setTimeout(doSomething, 100);
return;
}어떤 표현식으로부터 값이 존재하는 곳에서 그 값이
undefined가 되어야 좋을 경우에만 사용하자2.4.3 특수 숫자
NaN(Not A Number)
두 피연산자가 전부 숫자가 아닐 경우 유효한 숫자가 나올 수 없으므로 그 결과는
NaN이다.경계 값의 일종으로 숫자 집합 내에서 특별한 종류의 에러 상황을 나타낸다.
NaN은 어떤NaN과도 동등하지 않다.(즉, 자기 자신과도 같지 않다.)반사성이 없는(x === x로 식별되지 않는) 유일무이한 값
내장 전역 유틸리티
isNaN()함수로NaN여부를 알 수 있다.1
2
3
4
5
6
7
8var a = 2 / "foo";
var b = "foo";
a; // NaN
b; // "foo"
window.isNaN(a); //true
window.isNan(b); //true- “foo”는 당연히 숫자가 아니지만 그렇다고
NaN은 아니다. 이를 해결하기 위해Number.isNaN()이 ES6부터 등장
- “foo”는 당연히 숫자가 아니지만 그렇다고
무한대
자바스크립트에서는 0으로 나누기 연산이 잘 정의되어 있어서 에러 없이
Infinity(Number.POSITIVE_INFINITY)라는 결과값이 나온다.1
2var a = 1 / 0 ; //Infinity
var b = -1 / 0; //-Infinity자바스크립트는 유한 숫자 표현식을 사용하므로 +무한대/ -무한대가 될 수 있다.
무한대 / 무한대 : NaN
0 / 유한한 양수 : 0
0(영)
음의 영은 표기 뿐만 아니라 연산 결과 또한 -0으로 떨어진다.
1
var a = 0 / -3; //-0;
하지만 명세에 의하면 -0을 문자열화하면 항상 “0”이다.
1
2a.toString(); //"0"
JSON.stringify(a); //"0"반대로 하면 있는 그대로를 보여준다.
1
2Number("-0"); //-0
JSON.parse("-0");//-0왜 -0을 만들었을까?
- 값의 크기로 어떤 정보(ex: 애니메이션 프레임당 넘김 속도)와 그 값의 부호로 또 다른 정보(ex: 넘김 방향)를 동시에 나타내야 하는 어플리케이션이 있기 때문
- 잠재적인 정보 소실을 방지하기 위해 0의 부호를 보존한 셈
2.4.4 특이한 동등 비교
NaN과-0의 동등 비교가 까다로웠는데 ES6부터는Object.is()라는 유틸리티를 지원한다.1
2
3
4
5var a = 2 / "foo";
var b = -3 * 0;
Object.is(a, NaN); //true
Object.is(b, -0); //true==나===가 안전하다면 굳이Object.is()는 사용하지 않는 편이 좋다. 기본 연산자가 좀 더 효율이 좋고 일반적이기 때문.
##2.5 값 VS 레퍼런스
값의 타입만으로 값-복사 혹은 레퍼런스-복사가 결정된다.
원시 값 : 값-복사
레퍼런스 값 : 레퍼런스-복사
1
2
3
4
5
6
7
8
9
10
11var a = 2;
var b = a; //b는 언제나 a에서 값을 복사한다.
b++;
a;// 2
b;// 3
var c = [1,2,3];
var d = c; // d는 공유된 [1,2,3]값의 레퍼런스다.
d.push(4);
c;// [1,2,3,4]
d;// [1,2,3,4]2는 원시 값이므로a에는2의 초기 사본이,b에는 또 다른 사본이 들어간다. 따라서b를 바꿔도a까지 동시에 바뀔 수 없다.c와d는 모두 함성 값이자 동일한 공유 값[1,2,3]에 대한 개별 레퍼런스다. (단지[1,2,3]을 동등하게 참조만!한다는 사실) 따라서 레퍼런스로 실제 공유한 배열 값이 변경되면, 이 공유 값 한 군데에만 영향을 미치므로 두 레퍼런스는 갱신된 값을 동시에 바라보게 된다.레퍼런스는 변수가 아닌 값 자체를 가리키므로
a레퍼런스로b레퍼런스가 가리키는 대상을 변경할 수 없다.1
2
3
4
5
6
7
8var a = [1,2,3]
var b = a;
a;//[1,2,3]
b;//[1,2,3]
b = [4,5,6]
a;//[1,2,3]
b;//[4,5,6]call by value / call by reference
1
2
3
4
5
6
7
8
9
10
11
12
13
14function foo(x) {
x.push(4);
x; //[1,2,3]
x = [4,5,6];
x.push(7);
x; //[4,5,6,7]
}
var a = [1,2,3];
foo(a);
a; // [1,2,3,4]a를 인자로 넘기면a의 레퍼런스 사본이x에 할당 =>x와a는 동일한[1,2,3]을 가리키는 별도의 레퍼런스 => 함수 내부에서 레퍼런스를 이용하여 값 자체를 변경 =>x = [4,5,6]으로 새 값을 할당해도 초기 레퍼런스a가 참조하고 있던 값에는 아무런 영향이 없다.- 속성 변경 => 변경
- 재할당 => 서로 다른 객체 바라봄
배열 같은 합성 값을
값-복사로 전달하려면 값의 사본을 만들어 전달한 레퍼런스가 원본을 가리키지 않게 하면 된다.1
foo(a.slice());
- 인자 없이
slice()를 호출하면 전혀 새로운 배열의(shallow copy)사본을 만든다. 이렇게 복사한 사본만을 가리키는 레퍼런스를 전달하니foo()는a의 내용을 건드릴 수 없다.
- 인자 없이
원시값을 레퍼런스-복사로 전달하려면 원시 값을 다른 합성값으로 감싸야한다.
1
2
3
4
5
6
7
8
9function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a:2
}
foo(obj);
obj.a;// 42obj는 원시 프로퍼티a를 감싼 wrapper =>foo()에obj레퍼런스 사본이 전달 => 래퍼 인자의 값을 바꿈 => 래퍼 레퍼런스로 공유된 객체에 접근하여 프로퍼티 수정