JSnote5: Data types
Data types
Methods of primitives
primitive와 object의 차이점:
- A primitive
- primitive type의 값임
- 7개의 type이 존재 :
String,Number,Bigint,Boolean,Symbol,null,undefined
- An object
- 여러 개의 값을 property로 저장할 수 있음
{}로 생성할 수 있고 JS에는 함수와 같은 여러 종류의 object가 있음- 함수도 property로 저장(method)할 수 있음
- primitive보다 자원을 많이 소모함
A primitive as an object
primitive를 method와 함께 사용하기 위해 아래와 같은 방법을 사용함:
String,Number,Boolean,Symbol의 method와 property에 접근하는 것을 허용함- 위 접근을 가능하게 하기 위해, object wrapper를 제공함(임시적으로 생성, 소멸됨)
object wrapper는 각각의 primitive type에 따라 다르며, primitive의 이름과 같음(String, Number, Boolean, Symbol)
각각 다른 methods를 제공함
Example
string method str.toUpperCase()는 str를 capitalize한 것을 반환함:
1
2
3
let str = "Hello";
alert( str.toUpperCase() ); // HELLO
이 method를 호출했을 때 일어나는 일:
str은 primitive이기 때문에, property에 접근하는 순간 string의 값과toUpperCase()등의 method를 가진 객체가 생성됨- method가 실행되고 새로운 string이 반환됨
- 객체가 파괴되고 primitive인
str만 남음
이렇게 primitive가 method를 제공하면서 가볍게 유지됨
JS 엔진은 추가적인 객체를 생성하지 않을 만큼 최적화되어 있지만 명세서에는 그것을 생성하는 것처럼 적혀있음
Number에는 toFixed(n)과 같은 method가 존재함
alert( n.toFixed(2) ); : 소수점 n자리까지 남도록 반올림함
※
String/Number/Boolean을 생성자로는 사용하지 않는 게 좋음
Java와 같은 언어에서는 명시적으로 wrapper object를 생성할 수 있음(new Number(0)과 같이)
JS에서도 가능은 하지만 크게 아래의 이유로 추천되지 않음:
- primitive가 아닌 object로 인식되기 때문에 조건문에서 값에 상관없이 참을 반환함
반면
new를 사용하지 않고String/Number/Boolean만 쓰는 것은 값을 원하는 type으로 변환하게 도와주기 때문에 유용함
e.g.let num = Number("123");
※
null/undefined는 method가 없음
위 type들은 wrapper object가 없음
alert(null.test);와 같이 property에 접근하려 하면 에러가 발생함
Summary
- primitive를 다룰 때 유용한 기능들이 각각의 primitive 안에 있음
- method로 구현된 기능을 호출하면 임시적으로 object wrapper라는 객체가 생성되고, method가 실행된 뒤 파괴됨
Tasks
primitive가 method를 호출하면 객체로 처리되는데, property를 추가할 수 있을까?
1
2
3
4
5
let str = "Hello";
str.test = 5;
alert(str.test);
- strict mode의 사용 여부에 따라 결과가 다름
- strict mode를 사용하지 않으면
undefined가 출력됨 - strict mode를 사용하면 error가 발생함
- strict mode를 사용하지 않으면
- property에 접근할 때 wrapper object가 생성은 됨!
- strict mode에서는 값을 수정할 수 없음
- strict mode가 아닐 경우, 객체가
testproperty를 가지지만, 그 statement가 끝난 후 사라짐!
따라서 마지막 줄에서str은test라는 property를 찾을 수 없음!
primitive와 object의 차이점 중 하나임:
primitive는 추가적인 데이터를 저장할 수 없음!!
Numbers
최신의 JS에는 두 가지 타입의 숫자가 존재함:
- 일반적인 숫자는 64-bit format IEEE-764(double precision floating point number)으로 저장됨
- 임의의 길이의 숫자를 표현하기 위해서 BigInt가 사용됨
일반적인 숫자는[-2^53, 2^53]의 수만 표현 가능하기 때문
이 article에서는 일반적인 숫자에 대해 다룰 예정
More ways to write a number
underscore _를 숫자 사이에 구분자로 넣을 수 있음:
1
let billion = 1_000_000_000;
- 숫자의 가독성을 높여줌
- JS 엔진은 숫자 자리수 사이에 사용된
_는 무시함
"e"를 지수승 표기로 사용할 수 있음:
1
2
3
let billion = 1e-6; // 0.000001
alert( 7.3e9 ); // 7300000000
Hex, binary and octal numbers
0x, 0b, 0o for hex, binary, octal numbers
case doesn’t matter
for other numeral systems, we should use the function parseInt
toString(base)
num.toString(base)를 사용하면 숫자를 base진법으로 변환한 결과를 string으로 반환함
1
2
3
4
let num = 255;
alert( num.toString(16) ); // ff
alert( 123456..toString(36) ); // 2n9c
base는[2, 36]내의 숫자만 가능- 마지막 줄의
..은 method를 호출하기 위해 사용한 것임
점을 하나만 쓰면 소수점이라 인식하기 때문에 method를 바로 호출하려면..을 사용해야 함
(1234).toString(36)과 같이 괄호로 묶어도 됨
Rounding
Math.floor: rounds downMath.ceil: rounds upMath.round: rounds to the nearest integerMath.trunc: removes anything afer the decimal point
위 함수들의 계산 결과는 모두 integer임
소수점 이하 n자리까지 나오게 계산하고 싶다면?
- 수학적 조작(2자리까지 나오게 하고싶다면 100을 곱한 값을 함수에 넣음)
toFixed(n)사용(자동으로 반올림됨)- 결과는
String이기 때문에 unary plus로 변환해줘야 함
- 결과는
Imprecise calculations
64비트 IEEE-754에서 52비트는 가수(fraction), 11비트는 지수(exponent), 1비트가 부호로 사용됨
- 숫자가 너무 크면
Infinity로 바뀜 - 정밀도 손실이 일어남(
0.1 + 0.2 == 0.3은false임)
(+alert( 9999999999999999 ); // shows 10000000000000000)
부동 소수점으로 저장하기 때문
해결 방법:- 소수들을 계산할 동안만 정수로 바꿔서 계산
- 값이 크지 않을 때 사용해야 함
- 다시 소수점을 돌려놓는 과정에서 에러가 발생하기 때문에 오차를 줄이는 정도임
toFixed(n)을 사용해서 오차를 표현하지 않으면 됨
e.g.alert( sum.toFixed(2) );
- 소수들을 계산할 동안만 정수로 바꿔서 계산
※ 부동소수점 덕분에 두 개의 0이 존재함(
0,-0)
Tests: isFinite and isNaN
isNaN(value)는 value가 NaN이면 true를 리턴함:
1
2
3
4
alert( isNaN(NaN) ); // true
alert( isNaN("str") ); // true
alert( NaN === NaN ); // false
- 마지막 줄처럼,
NaN은 자신을 포함해서 모든 것들과 같지 않기 때문에isNaN()함수가 필요함
isFinite(value)는 value를 숫자로 변환하고 일반적인 숫자면 true, NaN/Infinity/-Infinity면 false를 반환함:
1
2
3
alert( isFinite("15") ); // true
alert( isFinite("str") ); // false, because a special value: NaN
alert( isFinite(Infinity) ); // false, because a special value: Infinity
- 빈 문자열이나 공백,
null은0으로 취급되기 때문에true를 반환함
※
Object.is는===와 비슷한 기능의 내장 메소드지만, 아래 두 가지 edge case에서 더 신뢰할만한 결과를 반환함:
Object.is(NaN, NaN) === trueObject.is(0, -0) === false위와 같은 비교 방식은 specification에서 SameValue라고 불림
parseInt and parseFloat
인자의 시작부터 변환 가능한 문자까지만 변환해서 리턴, 변환할 수 없다면 NaN 리턴:
1
2
3
4
5
6
7
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5
alert( parseInt('12.3') ); // 12, only the integer part is returned
alert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading
alert( parseInt('a123') ); // NaN, the first symbol stops the process
parseInt(str, radix)로도 사용 가능,radix진법으로str를 읽고 숫자로 변환함
e.g.alert( parseInt('2n9c', 36) ); // 123456
Other math functions
Math.random(): returns a random number in[0, 1)Math.max(a, b, c...)/Math.min(a, b, c...): returns the max / minMath.pow(n, power): returnsn^pow
Summary
| code | description |
|---|---|
num.toString(base) |
num을 base진법의 문자열으로 변환 |
Math.floor()Math.ceil()Math.round()Math.trunc() |
소수점 처리 |
num.toFixed(n) |
소수점 이하 n자리까지 남도록 반올림 |
isNaN(val) |
val이 NaN인지 판별 |
isFinite(val) |
val이 finite한 수인지 판별 |
Object.is(v1, v2) |
v1, v2가 SameValue인지 판별 |
parseInt(str[, radix]) |
str을 radix 진법으로 파싱함 |
parseFloat(str) |
str을 소수점까지 파싱함 |
Math.random() |
[0, 1) 범위의 임의의 수 반환 |
Math.max(a, b, c...)Math.min(a, b, c...) |
max, min 반환 |
Math.pow(n, pow) |
n^pow 반환 |
- SameValue는
===과 비슷하지만,(NaN, NaN)은 같고,(0, -0)은 다름
Tasks
num.toFixed(n)는 소수점 이하n까지 나오도록 실제 값을 반올림하기 때문에 오차가 발생 가능함1 2 3 4
alert( 6.35.toFixed(1) ); // 6.3 alert( 6.35.toFixed(20) ); // 6.34999999999999964473 alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4
따라서
n번째 자리까지 구하기 위해선 마지막 줄같이 소수점 이하n번째 자리를 소수점 이하 1 번째 자리로 만든 후Math.round()를 사용하는게 좋음prompt에서 취소를 누르면null이 입력되는 것에 유의!!!
Strings
String의 포맷은 항상 UTF-16임!(page encoding과는 연관이 없음)
Quotes
quotes 중 backtick(` `)은 안에 변수를 포함(${})하거나 줄바꿈을 할 수 있음
추가로 backtick을 이용하면 template function을 사용할 수 있음:
func`string`으로 사용- string과 내장된 식을 받아서 처리할 수 있음(tagged template이라고도 불림)
- 자동완성같은 기능으로, 실무에서는 잘 사용하지 않는 듯
Special characters
| Character | Description |
|---|---|
\n |
New line |
\r |
Carriage return window에서는 line break가 \r\n으로 표현됨 |
\', \" |
Quotes |
\\ |
Backslash |
\t |
Tab |
\b, \f, \v |
Backspace, Form feed, Vertical tab 호환성을 위해서 유지되지만, 현재는 사용되지 않음 |
\xXX |
Unicode character with the given hexadecimal Unicode XXe.g. \x7A = 'z' |
\uXXXX |
Unicode symbol with the hex code XXXX in UTF-16 encoding |
\u{X...XXXXXX}(1 to 6 hex characters) |
Unicode symbol with the given UTF-32 encoding |
- escape character
\를 이용해서 특수문자를 표현
String length
length property를 사용해서 구함:
1
alert( `My\n`.length ); // 3
Accessing characters
- square brackets
[]str[0]- 해당하는 문자가 없으면
undefined리턴
str.charAt(pos)str.charAt(pos)- 해당하는 문자가 없으면 empty string
''리턴
- iterating
for (let char of "Hello")
Strings are immutable
String의 일부를 수정할 수는 없음
String 전체를 바꿀 수는 있음:
1
2
3
4
5
6
let str = 'Hi';
str[0] = 'h'; // doesn't work
alert( str[0] ); // H
str = 'h' + str[1]; // replace the string
alert( str ); // hi
Changing the case
toLowerCase(), toUpperCase() method를 사용:
1
2
3
alert( 'Interface'.toUpperCase() ); // INTERFACE
alert( 'Interface'.toLowerCase() ); // interface
alert( 'Interface'[0].toLowerCase() ); // 'i'
- 한 문자에 대해서도 사용 가능
Searching for a substring
str.indexOf
str.indexOf(substr[, pos]) : substr을 str의 pos 번째 문자부터 찾아나감
일치하는 부분이 있을 경우 시작하는 위치를 리턴, 없을 경우 -1 리턴:
1
2
3
4
5
let str = 'Widget with id';
alert( str.indexOf('Widget') ); // 0
alert( str.indexOf('widget') ); // -1
alert( str.indexOf('id', 2) ) // 12
- 대소문자 구별함
str.lastIndexOf(substr[, pos])도 있음str의 마지막부터substr을 찾아나감
The bitwise NOT trick
JS에서 bitwise NOT ~는 숫자를 32-bit 정수로 변환한 다음 NOT 연산을 실행함
~n이 -(n+1)과 같다는 점을 이용해서 indexOf로 부분문자열을 찾지 못했을 때 조건을 간단히 할 수 있음
=> if (~str.indexOf("Widget"))
큰 숫자는 잘리고, ~(2^31-1)도 0이기 때문에 긴 문자열에 대해서는 사용할 수 없음
최신의 JS에서는 .includes method를 이용함
includes, startsWith, endsWith
str.includes(substr[, pos])는 str에 substr이 들어있는지 판별함
=> true/false 리턴
str.startsWith(substr), str.endsWith(substr)는 str이 substr로 시작하거나, 끝나는지 판별함
=> true/false 리턴
1
2
alert( "Widget".startsWith("Wid") ); // true
alert( "Widget".endsWith("get") ); // true
Getting a substring
str.slice(start[, end])str의[start, end)부분문자열을 반환
end가 없을 경우start부터 끝까지 포함한 부분문자열을 반환start,end에 음수도 넣을 수 있음
str.substring(start[, end])str의start와end사이를 포함한 부분문자열을 반환
slice와 비슷하지만,start가end보다 클 수 있음:1 2 3 4 5 6 7 8 9
let str = "stringify"; // these are same for substring alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" // ...but not for slice: alert( str.slice(2, 6) ); // "ring" (the same) alert( str.slice(6, 2) ); // "" (an empty string)
- Negative arguments가 허용되지 않음
str.substr(start[, length])str의start부터 시작하는, 길이가length인 부분문자열을 반환start는 음수를 넣을 수 있음
※
substr은 JS specification에 나와있지 않아서 non-browser 환경에서는 지원하지 않을 수도 있음
웬만하면 다 지원됨
slice는 음수도 인자로 넣을 수 있기 때문에substr보다 유연함
사실상slice만 기억해도 충분함
Comparing strings
문자끼리는 알파벳 순서로 비교 가능함
ASCII를 이용하지 않고 UTF-16을 이용!
str.codePointAt(pos) : str의 pos 번째 문자의 코드 반환
String.fromCodePoint(code) : code에 해당하는 문자 반환
Example
1
2
3
4
5
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
alert( String.fromCodePoint(90) ); // Z
alert( '\u005a' ); // Z(0x005a == 90)
\uprefix를 사용해서 UTF-16 코드를 직접 입력할 수 있음
Correct comparisons
다른 언어들끼리 비교하기 위해서 ECMA-402 안의 str.localeCompare(str2) 함수를 사용하면 됨
str이str2보다 작으면 음수 리턴str이str2보다 크면 양수 리턴- 둘이 같으면
0리턴
※ default로 diacritical marks(Ö와 같은 발음 구별 부호)가 알파벳(O와 같이)과 동일하게 처리됨
※ IE10 이하는 Intl.js라는 라이브러리가 필요함
Internals, Unicode
Surrogate pairs
자주 사용되는 문자들은 2-byte code에 저장되지만, 잘 쓰이지 않는 문자들은 추가적으로 2바이트를 더해 surrogate pair로 인코딩됨:
1
2
3
alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X
alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY
alert( '𩷶'.length ); // 2, a rare Chinese hieroglyph
String.fromCodePoint와 str.codePointAt은 surrogate pairs를 넣어도 정상적으로 작동함
이 함수들이 개발되기 이전의 함수들인 String.fromCharCode와 str.charCodeAt은 surrogate pairs를 넣으면 제대로 작동하지 않음
1
2
3
4
5
6
7
8
9
10
11
12
alert( '𝒳'[0] ); // strange symbols...
alert( '𝒳'[1] ); // ...pieces of the surrogate pair
alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff
alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff
alert('𝒳'.codePointAt(0).toString(16)); // 1d4b3
alert('𝒳'.codePointAt(1).toString(16)); // dcb3 (*)
alert(String.fromCodePoint(0x1d4b3)); // 𝒳
alert('\u{1d4b3}'); // 𝒳
- 이전의 함수들은 surrogate pair를 분리해서 처리해줘야 함
surrogate pair의 첫 번째 문자는[0xd800, 0xdbff]사이의 코드,
두 번째는[0xdc00, 0xdfff]사이의 코드를 가짐 - 최신의 두 함수(
String.fromCodePoint,str.codePointAt)은 surrogate pair도 정상적으로 한 문자처럼 처리함
하지만(*)와 같이 두 번째 문자에 접근은 가능하고, surrogate pair는 두 개의 인덱스를 가지는 건 같음!
Diacritical marks and normalization
발음 구별 기호 중 자주 사용되는 것은 UTF-16에 등록되어 있지만, 나머지도 표현하기 위해서 문자를 조합 가능함:
1
2
alert( 'S\u0307' ); // Ṡ
alert( 'S\u0307\u0323' ); // Ṩ
\u0307이 위에 점,\u0323이 아래 점임
두 코드의 순서를 바꾸면, 보기에는 똑같지만 ==를 사용해서 비교할 경우 false를 반환함
이것을 해결하기 위해 Unicode normalization을 사용함
str.normalize()로 구현되어 있음:
1
2
3
4
alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true
alert( "S\u0307\u0323".normalize().length ); // 1
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
str.normalize()를 사용하면 보기에 똑같은 문자들을 모두 같게 만들어 준다는 것을 알 수 있음- normalization을 거친 문자는 code 상으로 여러 개가 합쳐졌어도 길이가
1이라는 것에 유의!
Summary
| code | description |
|---|---|
str.length |
str의 길이 리턴 |
str[n] |
str의 n 번째 문자(없으면 undefined) 리턴 |
str.charAt(pos) |
str의 pos 번째 문자(없으면 '') 리턴 |
for...of |
iterating할 때 사용 |
str.toUpperCase()str.toLowerCase() |
str을 대/소문자화 |
str.indexOf(substr[, pos]) |
str의 pos 번째 문자부터 오른쪽으로 substr을 찾고 첫 번째 위치(없으면 -1) 리턴 |
str.lastIndexOf(substr[, pos]) |
str의 pos 번째 문자부터 왼쪽으로 substr을 찾고 첫 번째 위치(없으면 -1) 리턴 |
str.includes(substr[, pos]) |
str에 substr이 있는지 판별true/false 리턴 |
str.startsWith(substr)str.endsWith(substr) |
str이 substr로 시작/끝나는지 판별true/false 리턴 |
str.slice(start[, end]) |
str의 [start, end)(end 없으면 끝까지) 리턴start, end는 음수가 허용됨 |
str.substring(start[, end]) |
start와 end 사이의 부분문자열 리턴start가 end보다 클 수 있지만, 음수가 허용되지 않음 |
str.substr(start[, length]) |
str에서 start부터 시작하고 길이가 length인 부분문자열 리턴start는 음수가 허용됨 |
str.codePointAt(pos) |
str의 pos번째 문자의 코드 리턴 |
String.fromCodePoint(code) |
code에 해당하는 문자 리턴 |
str.localeCompare(str2) |
str과 str2를 비교, (<, >, ==) 각각의 경우에 (음수, 양수, 0) 리턴 |
str.normalize() |
str을 Unicode normalization 처리함 |
str.trim() |
str을 trim한 결과 리턴 |
str.repeat(n) |
str을 n번 반복한 결과 리턴 |
str.toUpperCase(),str.toLowerCase()는 문자 하나에도 사용 가능- 문자열을 비교할 때 기본적으로 발음 구별 기호들은 알파벳으로 취급됨
Tasks
Number(str)보다+str가 편리함
Arrays
Declaration
1
2
3
4
5
6
7
8
9
let arr = new Array();
let arr = [];
let fruits = ["Apple", "Orange", "Plum"];
fruits[3] = 'Lemon';
alert( fruits.length ); // 4
alert( fruits ); // Apple,Orange,Plum,Lemon
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
- 대부분 두 번째 방법 이용
append같은 method 없이 원소를 바로 추가 가능arr.length로 길이 반환- 배열 자체를 출력할 수도 있음(csv같이 나옴)
- pyhton의 list처럼 자료형에 제한이 없음
- object literal처럼 trailing comma를 사용할 수 있음
Methods pop/push, shift/unshift
JS의 array는 아래 4가지 method를 사용해서 queue나 stack으로 활용 가능함:
pop- array의 마지막 원소를 빼서 반환
array.pop();
push- array의 마지막에 원소를 추가, 전체 길이 반환
array.push(elem[, elem2...]);
shift- array의 첫 번째 원소를 빼서 반환
array.shift();
unshift- array의 맨 처음에 원소를 추가, 전체 길이 반환
array.unshift(elem[, elem2...]);
Internals
array도 근본적으로는 object이기 때문에, reference로 복사됨
JS에서도 배열이 순서가 있는 데이터를 처리하는데 최적화가 되어있음
하지만 배열을 아래 예시처럼 일반적인 객체로 사용하면 소용이 없어짐:
1
2
3
4
5
let fruits = []; // make an array
fruits[99999] = 5; // assign a property with the index far greater than its length
fruits.age = 25; // create a property with an arbitrary name
- array를 잘 못 사용하는 경우:
- non-numeric property를 추가
- 인덱스를 불연속적이게 정의
- 배열을 역순으로 채움
Performance
push, pop은 O(1)이지만, unshift, shift는 O(n)임!
Loops
기본 for문, for...of, for...in 세 가지 방법으로 반복문을 돌릴 수 있음:
for...in과for...of를 사용하면 index는 알 수 없음for...in은 numeric이 아닌 property와 method에도 접근함
for...in은 일반적인 객체와 사용하는 것에 최적화되어있기 때문에 10-100배 정도 느림
=> array-like object와 사용하지 않는게 좋음
A word about “length”
length는 배열 안에 값의 개수를 세는 것이 아닌, 가장 큰 인덱스 + 1로 계산됨:
1
2
3
4
let fruits = [];
fruits[123] = "Apple";
alert( fruits.length ); // 124
- 이런 식으로 쓰면 안됨!
length는 수정이 가능함:
1
2
3
4
5
6
7
let arr = [1, 2, 3, 4, 5];
arr.length = 2; // truncate to 2 elements
alert( arr ); // [1, 2]
arr.length = 5; // return length back
alert( arr[3] ); // undefined: the values do not return
- 배열에 들어있는 원소의 개수보다 작게 만들면 뒤쪽의 원소들은 없어짐
다시 길이를 늘려도 복구되지 않음
따라서arr.length = 0;으로 간단하게 배열을 초기화할 수 있음
new Array()
1
2
3
4
let arr = new Array("Apple", "Pear", "etc");
let arr = new Array(2); // (*)
alert( arr[0] ); // undefined! no elements.
(*)와 같이 선언하면vector<int> vi(2);와 같이 원소가 선언되지만 안에 값이 없음
Multidimensional arrays
1
2
3
4
5
6
7
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // 5, the central element
toString
array에는 toString만 존재(Symbol.toPrimitive, valueOf는 구현되어있지 않음):
1
2
3
4
5
6
7
8
let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true
alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"
- 배열은 csv처럼 변환됨
[]와 같은 비어있는 배열은""로 변환됨
Don’t compare arrays with ==
==은 array를 위한 처리가 구현되어있지 않아서 다른 객체들처럼 처리됨:
1
2
3
4
5
alert( [] == [] ); // false, (1)
alert( [0] == [0] ); // false, (2)
alert( 0 == [] ); // true, (3)
alert('0' == [] ); // false, (4)
(1), (2)는 operand가 양쪽 다 객체이기 때문에 객체끼리 비교(3), (4)는[]가 내장toString에 의해''로 변환된 다음 알맞은 type으로 다시 변환됨
(3)에서는 숫자로 변환되는데''가0으로 변환되기 때문에true
(4)에서는'0'과''를 비교하기 때문에false
※
===는 type이 다르면 바로false, 같으면==와 동일하게 비교!!
배열을 비교하기 위해선 원소를 하나하나 비교하거나 iteration methods를 사용해야 함
Summary
| code | description |
|---|---|
arr.length |
arr의 길이 반환 |
arr.push(val[, val2...])arr.pop() |
arr의 뒤쪽에 원소 삽입/삭제 |
arr.unshift(val[, val2...])arr.shift() |
arr의 앞쪽에 원소 삽입/삭제 |
unshift,shift는O(n)임- 웬만하면
for...of사용해서 탐색 arr.length=0;으로 간단하게 초기화 가능
Tasks
ar=ar+ar2;를 실행하면ar들이String으로 변환된 후 합쳐져ar의 type이String으로 바뀜!!
ar[0]을 출력하면 첫 번째 원소가 아니라 첫 번째 문자가 출력됨ar.push(ar2);를 실행하면ar2가ar의 원소로 들어감
=> 둘 다 길이가 2였다면 실행한 후ar의 길이는 4가 아니라 3이 됨- method도 배열의 원소로 들어갈 수 있음:
1 2 3 4 5 6 7
let arr = ["a", "b"]; arr.push(function() { alert( this ); }) arr[2](); // a,b,function(){...}
arr[2]에는 함수 자체가 들어가 있기 때문에 호출하면arr이 리턴됨
Array methods
Add/remove items
push/pop/unshift/shift 이외에도 아래 method들이 있음
splice
array도 객체이므로 delete를 사용해서 property를 지울 수 있긴 하지만 index는 그대로 남음:
1
2
3
4
5
6
7
8
let arr = ["I", "go", "home"];
delete arr[2]; // remove "go"
alert( arr[2] ); // undefined
// now arr = ["I", , "home"];
alert( arr.length ); // 3
delete obj.key는key에 해당하는 값을 지우는 것이기 때문에 index가 줄어들지는 않음
arr.splice을 사용하면 됨
1
arr.splice(start[, deleteCount, elem1, ..., elemN]);
arr의start번째부터deleteCount만큼 지운 다음elem1, ..., elemN을 삽입하고,
지운 원소들의 배열을 반환함
Example
1
2
3
4
5
6
7
8
let arr = ["I", "study", "JavaScript", "right", "now"];
// remove 3 first elements and replace them with another
arr.splice(0, 3, "Let's", "dance");
alert( arr ) // now ["Let's", "dance", "right", "now"]
let removed = arr.splice(0, 2);
alert( removed ); // "Let's", "dance"
deleteCount에0을 넣어서 삽입만 할 수 있음start는 음수가 허용됨
slice
1
arr.slice([start[, end]]);
String의 method함수str.slice(start[, end])와 같은 기능임- 인자 없이
arr.slice()를 실행하면arr을 복사할 수 있음 arr이 수정되는게 아니라 새로운 배열이 반환됨
concat
1
arr.concat(arg1, arg2...);
arr의 원소 +arg...로 이루어진 배열이 반환됨
arr에 원소가 추가되지 않음!!
arr에 추가하기 위해선arr = arr.concat(arg);와 같이 사용해야 함arg로 array, value 둘 다 들어갈 수 있음- 순서대로 추가됨
-
arg가 array면 원소들이 반환되는 배열에 추가됨
array 이외의 type이면arg자체가 복사(shallow copy)되어 추가됨e.g.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
let arr = [1, 2]; let arrayLike = { 0: "something", length: 1 }; alert( arr = arr.concat(arrayLike) ); // 1,2,[object Object] alert( arr.length); // 3 arr[2][0] = 'other'; alert( arrayLike[0] ); // other arr.length = 2; let stra="asdf"; alert( arr = arr.concat(stra) ); // 1,2,asdf arr[2]="iiii"; alert(arr[2]); // iiii alert(stra); // asdf
.으로 호출하기 위해선 valid variable identifier이어야 함(공백 없음, 숫자로 시작 x, 특수문자 x)- numeric property와 length만 있다해도
new Array()나[]으로 선언하지 않으면 array-like object가 됨
array-like object는 array처럼 원소가 추가되도록 만들 수 있음
=>Symbol.isConcatSpreadableproperty를true로 설정하면 됨!1 2 3 4 5 6 7 8
let arrayLike = { 0: "something", 1: "else", [Symbol.isConcatSpreadable]: true, length: 2 }; alert( [].concat(arrayLike) ); // something,else
Iterate: forEach
1
arr.forEach(function(item, index, array) { ... });
item,index,array는 필요하면 함수의 parameter로 사용 가능- parameter name은
item/index/array로 고정된게 아니고 순서대로 저렇게 들어가는 듯
Example
1
2
3
4
5
6
// for each element call alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
alert(`${item} is at index ${index} in ${array}`);
});
- 함수의 result는 무시됨
Searching in array
indexOf/lastIndexOf and includes
String의 method들과 같음
arr.indexOf(item[, from])arr.lastIndexOf(item[, from])arr.includes(item[, from])
※ 모두 비교할 때
===을 사용함
=>false를 넣으면 falsy value가 아닌false만 찾음
includes method는 indexOf/lastIndexOf와 다르게 NaN을 처리할 수 있음:
1
2
3
const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN)
alert( arr.includes(NaN) );// true (correct)
- 포함 여부를 확인할 때는
includes를 사용하는게 좋음
find and findIndex
1
2
let result = arr.find(function(item, index, array) { ... });
let result = arr.findIndex(function(item, index, array) { ... });
- argument로 들어가는 함수가
true를 반환할 경우 탐색을 멈추고 그item/index을 반환
만족하는item/index가 없을 경우undefined/-1반환
Example
1
2
3
4
5
6
7
8
9
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
let user = users.find(item => item.id == 1);
alert(user.name); // John
filter
1
let results = arr.filter(function(item, index, array) { ... });
- 함수가
true를 반환할 경우 result array에 item을 추가하고 반복을 계속함
반복이 끝난 후 result를 반환(만족하는 item이 없었다면 empty array가 반환됨) find와 유사하지만,find는 만족하는 item 하나만 찾아주는 반면filter는 만족하는 모든 item을 찾아줌
Example
1
2
3
4
5
6
7
8
9
10
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
// returns array of the first two users
let someUsers = users.filter(item => item.id < 3);
alert(someUsers.length); // 2
Transform an array
map
1
2
3
4
5
6
let result = arr.map(function(item, index, array) { ... });
/*-------------example--------------*/
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6
- argument로 들어가는 함수를 사용해서
arr의 item을 매핑하고, 그 결과를 반환
sort(fn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
arr.sort([function() { ... }]);
/*-------------example--------------*/
let arr = [ 1, 2, 15 ];
arr.sort();
alert( arr ); // 1, 15, 2
function compareNumeric(a, b) {
if (a > b) return 1;
if (a == b) return 0;
if (a < b) return -1;
}
arr.sort(compareNumeric);
alert(arr); // 1, 2, 15
sort도 정렬된 array를 반환하긴 하지만,arr자체도 바뀌기 때문에 보통 리턴값을 사용하지 않음- 인자를 넣지 않으면
String을 기준으로 정렬함- 비교함수를 넣어서 정렬 기준을 바꿀 수 있음
비교함수는(a, b)를 비교할 때a가 더 크면 양수,b가 더 크면 음수를 리턴하도록 구현하면 됨
=>arr.sort( (a, b) => a-b )로 코드를 간결하게 만들 수 있음
- 비교함수를 넣어서 정렬 기준을 바꿀 수 있음
※ chrome에서는 항상 stable sort로 정렬됨
string을 비교하는 경우 localeCompare을 사용하는게 좋음:
1
2
3
4
5
6
7
let countries = ['Österreich', 'Andorra', 'Vietnam'];
alert( countries.sort( (a, b) => a > b ? 1 : -1) );
// Andorra, Vietnam, Österreich (wrong)
alert( countries.sort( (a, b) => a.localeCompare(b) ) );
// Andorra,Österreich,Vietnam (correct!)
reverse
1
2
3
4
5
6
7
8
arr.reverse();
/*-------------example--------------*/
let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1
sort와 마찬가지로 reversed array를 반환함
split and join
1
2
3
4
5
6
7
str.split([delim[, limit]]);
/*-------------example--------------*/
let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', '); // ['Bilbo', 'Gandalf', 'Nazgul']
delim이 생략되면str전체가 하나의 element로 들어감
delim에 문자열이 들어갈 수 있음
delim이str의 시작/끝에 나타나면 empty string이 시작/끝에 element로 들어감limit이0이면 empty array를 반환
배열의 길이가limit가 되기 전에str의 끝에 다다르면 바로 종료됨
(배열의 길이가limit가 되도록 채우지 않음)
1
2
3
4
5
6
7
8
9
arr.join(glue);
/*-------------example--------------*/
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';'); // glue the array into a string using ;
alert( str ); // Bilbo;Gandalf;Nazgul
glue를 넣지 않으면 원소만 이음
reduce/reduceRight
1
2
3
4
5
6
7
8
9
let value = arr.reduce(function(accumulator, item, index, array) { ... }, [initial]);
let value = arr.reduceRight(function(accumulator, item, index, array) { ... }, [initial]);
/*-------------example--------------*/
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15
- array를 기반으로 값을 도출할 때 사용
accumulator: 이전 호출의 결과(initial이 있는 경우 처음에는initial)
반복이 끝난 뒤accumulator가reduce의 결과로 반환됨- empty array에서
initial없이reduce를 호출할 경우 에러남!
Array.isArray
array도 object type에 속하기 때문에 typeof로 타입을 알 수 없음
=> Array.isArray(value)으로 배열인지 판별
Most methods support “thisArg”
array method 중 find, filter, map과 같이 함수를 argument로 호출하는 method들은 thisArg를 추가적으로 붙일 수 있음:
1
2
3
arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
thisArg는 optional last argument임thisArg의 값은func의this가 됨
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let army = {
minAge: 18,
maxAge: 27,
canJoin(user) {
return user.age >= this.minAge && user.age < this.maxAge;
}
};
let users = [
{age: 16},
{age: 20},
{age: 23},
{age: 30}
];
// find users, for who army.canJoin returns true
let soldiers = users.filter(army.canJoin, army);
alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23
-
army.canJoin은 method로 호출된게 아니라filter의 argument로 들어간 것이기 때문에 함수의 구현부(코드)만 인자로 들어간 상태임
=> 실행될 때this가 가리키는 것은 없는 상태이기 때문에thisArg로this를 표시해줘야 함users.filter(user => army.canJoin(user));로thisArg없이 사용할 수 있음
Summary
| code | description |
|---|---|
arr.splice(start[, deleteCount, elem1, ..., elemN]) |
arr의 start 번째부터 deleteCount만큼 지운 다음 elem1, ..., elemN을 삽입하고, 지운 원소들의 배열을 리턴arr도 변화함 |
arr.slice([start[, end]]) |
arr의 [start, end)를 리턴 |
arr.concat(arg1, arg2...) |
arr에 arg...을 더한 배열을 리턴 |
arr.forEach(function(item, index, array) { ... }) |
arr의 원소들을 순회하면서 함수에 대입함 |
arr.indexOf(item[, from])arr.lastIndexOf(item[, from]) |
arr의 from 번째부터 item을 찾고 첫 번째 일치하는 원소의 인덱스(없으면 -1)을 리턴arr의 from부터 왼쪽으로 item을 찾고 첫 번째 일치하는 원소의 인덱스(없으면 -1)을 리턴 |
arr.includes(item[, from]) |
arr의 from 번째부터 item이 존재하는지 판별true/false 리턴 |
arr.find(function(item, index, array) { ... })arr.findIndex(function(item, index, array) { ... }) |
함수가 true를 반환하면 탐색을 멈추고 해당 원소 리턴arr.find와 같지만, 해당 index 리턴 |
arr.filter(function(item, index, array) { ... }) |
함수를 true로 만드는 원소들의 배열 리턴 |
arr.map(function(item, index, array) { ... }) |
함수로 arr을 매핑하고 result array를 리턴 |
arr.sort([function() { ... }) |
함수를 기준으로 정렬한 결과를 리턴arr도 변화함함수를 생략하면 string으로 비교해서 정렬함 |
arr.reverse() |
arr을 역순으로 정렬하고 결과 리턴arr도 변화함 |
str.split([delim[, limit]]) |
str을 delim으로 구분하고 limit 개까지만 array에 저장 후 리턴아무 것도 넣지 않을 경우 나눠지지 않고, ''을 넣을 경우 한 글자씩 나눠짐 |
arr.join(glue) |
arr의 원소들을 glue를 사이에 넣어서 이은 string을 리턴 |
arr.reduce(function(accumulator, item, index, array) { ... }, [initial]) |
arr의 원소들을 함수에 넣으면서 accumulator에 결과를 저장하고 반환initial은 accumulator의 초기값 |
arr.reduceRight(function(accumulator, item, index, array) { ... }, [initial]) |
arr.reduce와 같은 기능이지만 원소들을 역순으로 처리함 |
Array.isArray(value) |
value가 Array type인지 판별 |
arr.some(function(item, index, array) { ... })arr.every(function(item, index, array) { ... }) |
arr 안에 함수를 true로 만드는 원소가 있는지 판별모든 원소가 함수를 true로 만드는지 판별 |
arr.fill(value[, start[, end]]) |
arr의 [start, end)를 value로 채우고 리턴arr도 변화함 |
arr.copyWithin(target[, start[, end]]) |
arr의 [start, end)를 target 번째부터 시작해서 붙여넣고 리턴arr도 변화함 |
arr.flat([depth]) |
arr의 원소들을 depth만큼 차원을 낮춘 결과를 리턴depth를 Infinity로 설정할 수 있음 |
arr.flatMap(function(item, index, array) { ... }) |
arr의 원소들을 함수로 매핑 후 한 차원 낮춘 결과를 리턴 |
arr.concat은 shallow copy이기 때문에 객체의 배열은 deep copy를 따로 해야함includes는NaN도 찾을 수 있음
cf.NaN === NaN이false이기 때문에indexOf로는 찾지 못함- 함수를 argument로 받는 method들은
thisArgparameter를 추가할 수 있음
(argument인 함수가 method일 때this가 가리키는 객체 지정) arr.every를 이용해서 배열 2개가 같은지 판별할 수 있음:1 2 3 4 5
function arraysEqual(arr1, arr2) { return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); } alert( arraysEqual([1, 2], [1, 2])); // true
- 호출 후
arr도 변화하는 함수들:arr.splicearr.sortarr.reversearr.fillarr.copyWithin
Tasks
- string을
-로 split하고 첫 번째 단어 제외 camelize:1 2 3 4 5
function camelize(str) { return str.split('-') .map((word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1)) .join(''); }
forEach도 반복문과 비슷하게 인덱스를 기반으로 돌아감
=> 안에서splice등으로 배열의 index가 바뀔 경우 영향을 받음
e.g.splice(i, 1)과 같이 원소 하나를 뺄 경우 반복문처럼 i 바로 다음 원소는 순회하지 않음!f(ar)에서 arr을 argument로 넘기면, arr의 레퍼런스가 argument로 들어감
=> 레퍼런스를 이용해서 method, property를 호출하면 arr을 수정할 수 있지만, 그냥 ar에 배열을 대입하려 하면 함수의 local variable ar에 대입되기 때문에 함수 밖의 arr은 수정되지 않음- 확장 가능한 계산기 만들기:
operator, expression을 분리할 필요 없이 operator를 property로 사용하는 객체를 property로 사용하면 됨1 2 3 4
this.methods = { "-": (a, b) => a - b, "+": (a, b) => a + b };
- arrow function에서 object literal을 리턴할 경우
({ ... })와 같이 괄호로 감싸야 함
∵ arrow function에는value => expr,value => { ... }와 같이 두 종류가 있음! - cpp에서의 string은 객체라서 때문에 문자 하나를 수정 가능하지만,
JS의 string은 primitive이기 때문에 문자 하나의 수정은 불가능함 - shuffle an array
arr에서 하나씩 골라 집어넣는 방법(O(N^2))1 2 3 4 5 6 7 8 9 10
function shuffle(arr) { let iar=[]; for(let i=0;i<arr.length;i++) iar.push(arr[i]); for(let i=0;i<arr.length;i++) { let t=Math.trunc(Math.random()*iar.length); arr[i]=iar[t]; iar.splice(t, 1); } }
- Fisher-Yates shuffle(
O(N))1 2 3 4 5 6
function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } }
[a, b] = [b, a]는 destructing assignment로, 나중에 다룰 예정- 모든 케이스가 동일한 확률을 가짐
arr.reduce를 사용해서 데이터들을 하나의 객체로 변환할 수 있음
Iterables
객체가 array가 아니더라도 데이터의 모음(list, set 등)으로 표현되면, iterable로 만들어 for...of로 효과적으로 순회하도록 만들 수 있음
Symbol.iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let range = {
from: 1,
to: 5
};
range[Symbol.iterator] = function() { // 1
return { // 2
current: this.from,
last: this.to,
// 3
next() { // 4
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
range를 iterable로 만들기 위해서[Symbol.iterator]()method를 추가해야 함
Symbol.iterator: 객체를 iterable하게 만들기 위해 존재하는 내장 symbolfor...of가 처음 실행될 때 이 메소드를 호출함
이 메소드는 iterator(next라는 method를 가지는 객체)를 리턴해야 함
이 메소드를 찾지 못하면 에러를 반환함for...of는 리턴된 iterator로만 실행됨for...of가 다음 iteration으로 넘어가려 할 때마다next()를 호출함next()는{done: Boolean, value: any}형식의 객체를 리턴해야 함
done=true는 iteration이 끝났다는 것을 의미함
그게 아니면value에 다음 값을 넣어야함
next()가 리턴하는 객체의 key는 이름을 바꾸면 에러남range자체를 iterator로 만들어도 됨:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
let range = { from: 1, to: 5, [Symbol.iterator]() { this.current = this.from; return this; }, next() { if (this.current <= this.to) { return { done: false, value: this.current++ }; } else { return { done: true }; } } };
Symbol.iteratormethod에서 iterator를 다른 객체로 만들어 리턴하지 않고,range자신을 리턴함
=>range에next()가 선언되어 있어야 함
- 하나의
range로 중첩된for...of를 돌릴 수 없음
안쪽 반복이 끝난 후current가 공유되기 때문에 바깥쪽 반복도 바로 종료됨
String is iterable
Array와 String이 널리 쓰이는 내장 iterable임
String의 경우 for...of를 돌리면 문자 하나씩 순회함:
1
2
3
4
5
6
7
8
9
for (let char of "test") {
// triggers 4 times: once for each character
alert( char ); // t, then e, then s, then t
}
let str = '𝒳😂';
for (let char of str) {
alert( char ); // 𝒳, and then 😂
}
- surrogate pair로 이루어진 문자열을 넣어도 정상적으로 작동함!
Calling an iterator explicitly
iterator을 명시적으로 호출할 수도 있음:
1
2
3
4
5
6
7
8
9
10
11
12
let str = "Hello";
// does the same as
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // outputs characters one by one
}
for...of를 호출한 것과 같은 결과임- string은 method를 호출할 때 wrapper object가 생성되기 때문에 따로 method를 정의해도 그 구문 뒤에 바로 사라짐
Iterables and array-likes
- iterable :
[Symbol.iterator]()method를 구현한 객체for...of반복문에 넣을 수 있음
- array-like : indexes,
length를 key로 가지는 객체- numeric indexes를 가짐
string은 iterable이면서(for...of에서 작동함) array-like(numeric indexes, length가 존재)임
보통은 iterable이거나 array-like이거나 둘 중에 하나임
둘 다 push, pop과 같은 method가 없다는 것은 같음
Array.from
Array.from method를 사용해서 iterable이나 array-like로 진짜 Array type으로 만들 수 있음:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Array.from(obj[, mapFn, thisArg]);
/*-------------example--------------*/
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (method works)
let str = '𝒳😂';
// splits str into array of characters
let chars = Array.from(str);
alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2
- iterable, array-like 모두에 대해서 작동함
- surrogate pairs에 대해서도 정상적으로 작동함
Summary
| code | description |
|---|---|
range.[Symbol.iterator]() |
객체 range를 iterable로 만들기 위해서 정의해야 함iterator 객체를 리턴해야 함 |
iterator.next() |
iterator가 다음 반복으로 넘어가기 전에 호출하는 함수done, value property를 가진 객체를 리턴해야 함 |
Array.from(obj[, mapFn, thisArg]) |
iterable 또는 array-like 객체인 obj를 Array type으로 바꾸고 mapFn을 사용해서 매핑한 배열을 리턴 |
[Symbol.iterator]()method는for...of가 호출될 때 자동으로 실행됨String,Array같은 built-in iterables도 위 메소드가 구현되어 있음- string iterable을 이용하면 surrogate pairs를 수월하게 처리 가능
Map and Set
object를 사용해서 keyed collection을 저장
array를 사용해서 ordered collection을 저장
Map
Map은 object와 비슷하게 keyed data를 저장하지만, 모든 타입의 key를 허용함
기본적인 method, property들:
new Map([entries]): entries로 초기화된 map 리턴map.set(key, value):key와value를 저장하고map리턴- 자신을 리턴하기 때문에 chaining이 가능함
map.get(key):key에 해당하는 값(key가 존재하지 않으면undefined)을 리턴map.has(key):key가 존재하면true, 아니면false리턴map.delete(key):key와 해당하는value를 삭제- 성공적으로 삭제했다면
true, 해당하는key가 었다면false리턴
- 성공적으로 삭제했다면
map.clear():map을 비움map.size:map의 현재 원소 수를 리턴
※
map[key]로 해도 property를 추가할 수 있지만, 이 문법은 일반 객체에 적용되는 제한(string/symbol key만 가능)을 적용시킴
따라서Map의 method인set,get을 사용하는게 좋음
Map은 객체도 key로 사용할 수 있음
cf. 일반 객체는 객체를 key로 사용하면 모든 객체가 "[object Object]"로 변환되기 때문에, 다른 객체를 넣어도 obj["[object Object]"]라는 property 하나로 처리됨
※
Map은 SameValue 알고리즘을 이용해서 key들을 비교함
이 알고리즘은 수정될 수 없음
Iteration over Map
Map을 순회하는 3가지 method:
map.keys(): key의 iterable을 리턴map.values(): value의 iterable을 리턴map.entries():[key, value]의 iterable을 리턴Map을for...of를 사용하여 순회할 때 default로 호출됨
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// iterate over values (amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
alert(entry); // cucumber,500 (and so on)
}
Map은 값이 삽입된 순서대로 순회함
cf. 일반 객체는 반복할 때 Integer property일 경우 정렬한 순서대로 순회함
※
Map도Array와 비슷하게forEachmethod를 가지고 있음:
1 2 3 4 // runs the function for each (key, value) pair recipeMap.forEach( (value, key, map) => { alert(`${key}: ${value}`); // cucumber: 500 etc });
Object.entries: Map from Object
Map을 생성할 때 array나 다른 iterable을 argument로 사용해서 초기화할 수 있음:
1
2
3
4
5
6
7
8
// array of [key, value] pairs
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
- 다른 iterable도 key/value pair가 property로 들어가 있어야 하고
next()를 정의할 수 있어야 하기 때문에,Map을 초기화할 때 사용하기 위해선 해봤자 integer property를 가지는 객체일 듯
Object.entries(obj) : obj -> entries
1
2
3
4
5
6
7
8
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
Object.entries(obj)는Map의 constructor 안에 들어가는 entries를obj로부터 만들어서 반환함
이 코드에서는[ ["name","John"], ["age", 30] ]를 리턴함
Object.fromEntries: Object from Map
Object.fromEntries(entries) : entries -> obj
1
2
3
4
5
6
7
8
9
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// now prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
- parameter가 꼭
Array일 필요는 없음
iterable object면 됨 => map을 바로 넣어도 됨
e.g.obj = Object.fromEntries(map)
Set
Set를 이용해서 set of values를 저장
key가 없고 각 값들은 여러 번 추가해도 처음 한 번만 저장됨
기본적인 method, property들:
new Set([iterable]):iterable의 values로 초기화된 set 리턴set.add(value):value를 추가하고 자신을 리턴set.delete(value):value가 존재하면 삭제하고true, 아니면false리턴set.has(value):value가 존재하면true, 아니면false리턴set.clear():set을 비움set.size:set의 현재 원소 개수 리턴
uniqueness check가 필요한 자료구조에 효율적
Iteration over Set
for...of, forEach 둘 다 사용 가능:
1
2
3
4
5
6
7
8
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// the same with forEach:
set.forEach((value, valueAgain, set) => {
alert(value);
});
forEach를 사용할 때 callback function에 parameter가 3개임에 주의!
valueAgain은Map과의 호환성을 위한 것임
Map을Set으로 바꿀 때 유용함Map과 비슷하게 아래의 메소드들을for...of에서 사용 가능set.keys(): value의 iterable을 리턴set.values(): value의 iterable을 리턴set.entries():[value, value]의 iterable을 리턴
Map과의 호환을 위해서 구현됨
Summary
| code | description |
|---|---|
| Map | |
new Map([entries]) |
entries로 초기화된 map 리턴 |
map.set(key, value) |
key와 value를 저장하고 map 리턴 |
map.get(key) |
key에 해당하는 값(존재하지 않으면 undefined) 리턴 |
map.has(key) |
key가 존재하면 true, 아니면 false 리턴 |
map.delete(key) |
key, value가 존재하면 삭제하고 true, 아니면 false 리턴 |
map.clear() |
map을 비움 |
map.size |
map의 현재 원소 수를 리턴 |
map.keys()map.values()map.entries() |
key의 iterable을 리턴 value의 iterable을 리턴 [key, value]의 iterable을 리턴 |
Object.entries(obj) |
obj로부터 entries의 array를 만들어서 리턴 |
Object.fromEntries(entries) |
entries로부터 object를 만들어서 리턴entries는 꼭 array를 이용하지 않아도 entries를 포함한 iterable이면 됨 |
| Set | |
new Set([iterable]) |
iterable의 values로 초기화된 set 리턴 |
set.add(value) |
value를 추가하고 set 리턴 |
set.delete(value) |
value가 존재하면 삭제하고 true, 아니면 false 리턴 |
set.has(value) |
value가 존재하면 true, 아니면 false 리턴 |
set.clear() |
set을 비움 |
set.size |
set의 현재 원소 수를 리턴 |
set.keys()set.values()set.entries() |
value의 iterable을 리턴 value의 iterable을 리턴 [value, value]의 iterable을 리턴 |
Object: collection of keyed values
Map: collection of keyed values
Array: collection of ordered values
Set: collection of unique valuesSet에는Map과의 호환성을 위해서 구현된 메소드가 많음
Tasks
Array.from(obj)는 모든 Array-like, iterable에 대해 사용 가능Object.keys/values/entries는 array를 리턴하는 반면,
map/set.keys/values/entries는 iterable을 리턴함Array.from(iterable)을[iterable]로 간단한게 구현 가능
WeakMap and WeakSet
Map은 객체도 key로 사용할 수 있는데, key로 사용되는 객체는 garbage collect되지 않음
하지만 WeakMap은 key로 사용되는 객체가 garbage collect의 대상이 되는 것을 막아주지 않음
WeakMap
WeakMap의 key는 object만 가능함
object가 WeakMap의 key로 사용되는 상황에서 object로의 reference가 하나도 없다면 그 object는 WeakMap과 메모리에서 자동으로 지워짐(garbage collect됨):
1
2
3
4
5
6
7
8
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // overwrite the reference
// john is removed from memory!
추가로, WeakMap은 iteration과 keys/values/entries()와 같은 method를 지원하지 않고, 아래의 method만 지원함:
weakMap.get(key)weakMap.set(key, value)weakMap.delete(key)weakMap.has(key)
=> 모든 key나 value를 알아낼 수 없음
∵ 객체가 다른 reference를 가지지 않으면 garbage collect의 대상이 되지만, garbage collection이 언제 일어나는지는 JS engine에 의해 결정되기 때문(바로 지워질지, 기다렸다가 한꺼번에 지워질지)
즉, WeakMap의 현재 원소 개수는 알 수 없음
Use case: additional data
WeakMap은 additional data storage로써 유용하게 사용됨
third-party library에 속하거나 다른 이유로 객체 안에 property를 추가하는게 적합하지 않은 상황에서, 객체가 살아있는 동안에만 유효한 데이터를 저장하고 싶을 때 WeakMap이 적합한 자료구조임
WeakMap에 데이터를 넣으면 key인 object가 garbage collect될 때 데이터도 자동으로 삭제되기 때문
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 📁 visitsCount.js using Map
let visitsCountMap = new Map(); // map: user => visits count
// increase the visits count
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
// 📁 main.js
let john = { name: "John" };
countUser(john); // count his visits
// later john leaves us
john = null;
// 📁 visitsCount.js using WeakMap
let visitsCountMap = new WeakMap(); // weakmap: user => visits count
// increase the visits count
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
Map을 사용할 경우john이 사라져도 수동으로visitsCountMap에서 데이터를 삭제해야 함
프로젝트가 커지면 수동으로 삭제하는데 많은 자원이 들어감
=>WeakMap으로 해결
Use case: caching
객체를 parameter로 받는 함수가 객체별로 결과를 저장(cache)해놓고 재사용할 수 있음
이 때 Map보다 WeakMap을 사용하는게 적합함:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 📁 cache.js
let cache = new WeakMap();
// calculate and remember the result
function process(obj) {
if (!cache.has(obj)) {
let result = /* calculate the result for */ obj;
cache.set(obj, result);
}
return cache.get(obj);
}
// 📁 main.js
let obj = {/* some object */};
let result1 = process(obj);
let result2 = process(obj);
// ...later, when the object is not needed any more:
obj = null;
// Can't get cache.size, as it's a WeakMap,
// but it's 0 or soon be 0
// When obj gets garbage collected, cached data will be removed as well
- memoization과 비슷
WeakSet
Set과 유사하지만, 객체만 저장 가능WeakMap과 마찬가지로 객체가 reachable할 때만WeakSet안에서 유지됨WeakMap과 마찬가지로 iteration 불가weakSet.add(value)weakSet.has(value)weakSet.delete(value)
위 3개의 method만 지원함
WeakMap과 마찬가지로 additional storage 역할이지만, 임의의 데이터가 아닌, “yes/no”만 표현하는 데이터를 위한 자료구조임
e.g. 사용자의 방문 횟수가 아닌, 방문 여부
WeakMap과 WeakSet의 가장 큰 단점은 반복 작업이 불가능한 것과 현재의 모든 원소에 대한 정보(개수 등)을 알 수 없다는 점임
다른 곳에서 관리되거나 저장된 객체들에 대한 “additional” storage를 제공하는 역할임을 생각해야 함
Summary
| code | description |
|---|---|
| WeakMap | |
weakMap.get(key) |
weakMap에 key에 해당하는 값 리턴 |
weakMap.set(key, value) |
weakMap에 key, value를 추가하고 weakMap 리턴 |
weakMap.delete(key) |
weakMap에서 key가 존재하면 삭제 후 true, 아니면 false 리턴 |
weakMap.has(key) |
weakMap에 key가 존재하는지 판별 |
| WeakSet | |
weakSet.add(value) |
value를 추가하고 weakSet 리턴 |
weakSet.has(value) |
value가 존재하는지 판별 |
weakSet.delete(value) |
value가 존재하면 삭제하고 true, 아니면 false 리턴 |
WeakMap과WeakSet은 secondary storage로서, primary storage에서 객체가 지워지면 이 자료구조들에서도 자동으로 지워짐
Tasks
- 객체 안에 property를 추가할 수 있다면, symbolic property를 추가해서 다른 사람들은 접근할 수 없는 property를 만들어서
WeakSet,WeakMap의 역할을 대신할 수 있음- 구조적인 관점에서 보면
WeakSet이나WeakMap을 사용하는게 나음(secondary storage라는 semantic role이 있기 때문)
- 구조적인 관점에서 보면
Object.keys, values, entries
keys/values/entries()는 모든 자료 구조들에 대해서 사용 가능하도록 약속되어 있음
=> 자료구조를 만들게 된다면 이 메소드들도 구현해야 함
Map, Set, Array에 대해서는 이미 배움(iterable을 리턴함)
Object에 대해서도 사용 가능하지만, array를 리턴함!
Object.keys(obj): keys의 array를 리턴Object.values(obj): values의 array를 리턴Object.entries(obj):[key, value]의 array를 리턴- 호출 방법이
map.keys()와 다른 것에 유의
∵obj가keys라는 method를 가질 수도 있기 때문
- 호출 방법이
※
Object.keys/values/entries는 symbolic properties를 무시함!!Object.getOwnPropertySymbols(obj)가 symbolic keys만 나열된 array를 리턴
Reflect.ownKeys(obj)가 모든 key가 나열된 array 리턴
Transforming objects
객체에는 array의 map, filter와 같은 method들이 없음
하지만 아래와 같이 property들을 순회할 수 있는 방법이 존재:
Object.entries(obj)를 사용해서obj로부터 key/value pair의 배열을 얻음- 얻은 배열에 배열의 method를 적용
- 반환된 배열을
Object.fromEntries(array)로 다시 객체로 만듦
1
2
3
4
5
6
7
8
9
10
11
12
let prices = {
banana: 1,
orange: 2,
meat: 4,
};
let doublePrices = Object.fromEntries(
// convert to array, map, and then fromEntries gives back the object
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
alert(doublePrices.meat); // 8
Summary
| code | description |
|---|---|
Object.keys(obj)Object.values(obj)Object.entries(obj) |
obj의 key들의 array 리턴obj의 value들의 array 리턴obj의 [key, value]들의 array 리턴 |
Object.fromEntries(array) |
entry들로 이루어진 array를 바탕으로 객체를 생성하고 리턴 |
Object.keys/values/entries는Map이나 다른 객체들의 method와 같이 iterable이 아닌Array를 리턴함에 주의!
1
2
3
4
5
6
7
let arr = ['a', , 'c'];
let sparseKeys = Object.keys(arr);
let denseKeys = [...arr.keys()];
alert(sparseKeys); // ['0', '2']
alert(denseKeys); // [0, 1, 2]
alert(arr[1]);
Object.keys(arr)은 arr의 arr[n]의 값이undefined면 무시하지만,arr.keys()는 무시하지 않고 모두 출력함!
Destructuring assignment
Destructuring assignment는 배열이나 객체를 변수들로 분리하는 문법임
Destructuring은 수많은 파라미터, 기본값을 가지는 복잡한 함수에도 알맞음
Array destructuring
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1
let [firstName, surname] = "John Smith".split(' ');
alert(surname); // Smith
// 2
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
// 3
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
// 4
let user = {
name: "John",
age: 30
};
// loop over keys-and-values
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, then age:30
}
// 5
[guest, admin] = [admin, guest];
"John Smith".split(' ')와 같이 배열을 리턴하는 method도 “destructurize” 가능함,를 사용해서 원소를 무시할 수 있음- 오른쪽에는 모든 iterable이 올 수 있음
- destructuring을 이용해서 entries를 순회할 수 있음
Map,Set도 가능함
- 손쉽게 swap을 구현할 수 있음
※ Destructuring은 destructive와 다른 의미임
객체를 분해하지만, 객체 자체에 영향을 주지는 않음
The rest ...
1
2
3
4
5
6
7
8
9
10
11
let arr = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
let [name1, name2] = arr;
alert(name1); // Julius
alert(name2); // Caesar
let [name1, name2, ...rest] = arr;
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
- 길이가 4인 배열을
[a1, a2]로 destructurize하면 배열의 3, 4번째 원소들은 무시됨 ...을 사용하면 남는 원소들을 한꺼번에 저장할 수 있음...다음에 남는 원소들을 저장할 변수의 이름을 넣음- destructuring assignment에서만 사용 가능
- 항상 left-side의 마지막에 사용해야 함
Default values
1
2
3
4
5
6
7
8
9
10
11
// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (from array)
alert(surname); // Anonymous (default used)
// runs only prompt for surname
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];
alert(name); // Julius (from array)
alert(surname); // whatever prompt gets
- destructuring을 사용할 때 left-side에 default value 설정 가능
prompt를 사용해서 부족한 값을 입력받을 수 있음
Object destructuring
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let {var1, var2} = {var1:..., var2:...};
/*-------------example--------------*/
let options = {
title: "Menu",
width: 100,
height: 200
};
// 1
let {title, width, height} = options;
// 2
let {width, title, height} = options;
// 3
let {width: w, height: h, title} = options;
// 4
let {width = 100, height = prompt("height?"), title} = options;
// 5
let {width: w = 100, height: h = 200, title} = options;
- destructurize할 객체의 key를 left-side에 넣어야 함
=>title,width,height라는 변수 안에 property의 값이 저장됨 - left-side의 변수의 순서들은 상관없음
- left-side에서
sourceProperty: targetVariable과 같이 이름을 다시 설정 가능함 - array destructuring 같이 default value를 설정 가능함
:와=를 동시에 사용 가능함
The rest pattern ...
array destructuring과 같이 남는 property들을 rest pattern ...을 사용해서 다른 객체에 저장 가능함:
1
2
3
4
5
6
7
8
9
let options = {
title: "Menu",
height: 200,
width: 100
};
let {title, ...rest} = options;
alert(rest.height); // 200
- 나머지 property들은
rest라는 객체 안에 저장됨- old IE의 경우 Babel 등으로 polyfill 해야함
※
let없이 destructuring을 사용하기 위해선 아래와 같이 괄호로 묶어야 함!
({title, width, height} = {title: "Menu", width: 200, height: 100});
∵ JS는{...}을 code block으로 인식하기 때문
Nested destructuring
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
let {
size: {
width,
height
},
items: [item1, item2],
title = "Menu"
} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut
- left-side pattern을 알맞게 설정해서 중첩된 객체도 destructurize 가능
Smart function parameters
함수의 parameter에도 destructuring을 적용 가능:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function({
incomingProperty: varName = defaultValue
...
}) { ... }
/*-------------example--------------*/
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
function showMenu({
title = "Untitled",
width: w = 100, // width goes to w
height: h = 200, // height goes to h
items: [item1, item2] // items first element goes to item1, second to item2
} = {}) {
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
- 함수의 parameter가 많을 때 객체 하나로 담아서 보낸 후 분해해서 사용가능하게 만듦
- destructuring은 항상 argument가 존재한다고 가정하기 때문에 destructuring에도 기본값(
= {})을 넣어줘야 함!
Summary
| code | description |
|---|---|
let [...] = array; |
array destructuring |
let {...} = obj; |
object destructuring |
... |
rest pattern 객체일 경우 새로운 객체 안에, 배열일 경우 새로운 배열을 저장 공간으로 사용함 |
- 기존에 존재하는 변수에 대해 수행할 경우 전체 문장을 괄호로 묶어야 함
Date and time
Creation
1
2
3
4
5
6
7
8
9
10
11
new Date()
new Date(milliseconds)
new Date(datestring)
new Date(year, month, date, hours, minutes, seconds, ms)
/*-------------example--------------*/
let now = new Date();
let Jan02_1970 = new Date(24 * 3600 * 1000);
let date = new Date("2017-01-26");
let date = new Date(2011, 0, 1);
- argument 없이 호출하면 현재 시간을 저장
- milliseconds는 1970.01.01 UTC 기준, 음수도 가능
- datestring은 GMT 기준 0시로 설정된 후 실행되는 환경의 timezone을 따라서 수정됨
- 직접 입력하는 경우 끝쪽부터 연속으로 0인 부분은 생략 가능
Access date components
date.getFullYear(): 4자리로date의 연도 리턴date.getMonth():[0, 11]으로date의 달 리턴date.getDate():[1, 31]으로date의 일 리턴date.getHours()/.getMinutes()/.getSeconds()/.getMilliseconds():date의 시/분/초/밀리초 리턴date.getDay():[0, 6]으로date의 요일 리턴(0이 일요일)
※ 위의 메소드들은 모두 실행 환경의 timezone이 기준임
get뒤에 UTC를 붙여서 UTC 기준으로 시간을 출력할 수도 있음
date.getTime(): 1970.01.01 UTC으로부터 현재까지 지난 시간을 ms단위로 리턴date.getTimezoneOffset(): UTC와 현재 timezone의 차이를 분 단위로 리턴
timezone이 UTC-1이면60이 리턴됨(UTC+0이 기준임)
※ UTC+1이면 UTC보다 1시간 빠름
Setting date components
date.setFullYear(year[, month[, date]])date.setMonth(month[, date])date.setDate(date)date.setHours(hour[, min[, sec[, ms]]])date.setMinutes(min[, sec[, ms]])date.setSeconds(sec[, ms])date.setMilliseconds(ms)date.setTime(milliseconds):date의 시간을 1970.01.01 UTC 기준으로millisecondsms 만큼 지난 시간으로 설정
setTime을 제외한 메소드들은 UTC-variant를 가짐
e.g. setUTCHours
※ argument 이외의 부분은 수정되지 않음!
Autocorrection
윤년이나 범위를 넘어선 값들은 알아서 계산해서 대입함:
1
2
3
4
5
6
let date = new Date(2013, 0, 32); // 1 Feb 2013
let date = new Date(2016, 1, 28); // 28 Feb 2016
date.setDate(date.getDate() + 2); // 1 Mar 2016
date.setDate(0); // 최소가 1이기 때문에 이전 달의 말일으로 변경됨 = 29 Feb 2016
- 달의 경우
[0, 11]으로 표현됨에 주의 - 일은
[1, 31]로 표현됨
Date to number, date diff
Date 객체가 Number로 변환되면 date.getTime()과 같은 결과가 나옴
=> 1970.01.01부터 현재까지 경과한 ms를 반환함
=> 아래와 같이 실행시간 측정에 사용 가능
1
2
3
4
5
6
7
8
9
let start = new Date();
// do the job
for (let i = 0; i < 100000; i++) {
let doSomething = i * i * i;
}
let end = new Date();
alert( `The loop took ${end - start} ms` );
Date.now()
시간을 측정하기 위해서 Date 객체를 선언할 필요는 없음
Date.now() method를 사용하면 현재 timestamp(1970.01.01부터 경과한 ms)를 알 수 있음
new Date().getTime()과 같은 기능이지만 객체를 생성하지 않기 때문에 더 빠름
1
2
3
4
5
6
7
8
9
let start = Date.now();
// do the job
for (let i = 0; i < 100000; i++) {
let doSomething = i * i * i;
}
let end = Date.now();
alert( `The loop took ${end - start} ms` );
Benchmarking
1
2
3
4
5
6
7
function diffSubtract(date1, date2) {
return date2 - date1;
}
function diffGetTime(date1, date2) {
return date2.getTime() - date1.getTime();
}
- 각 함수를 100000번씩 돌리면
diffGetTime이 더 빠름- type conversion을 하지 않기 때문
- multi-process OS에서는 병렬로 작업을 처리되는 작업이 존재할 수 있으므로 각 함수들을 번갈아가면서 여러 번 측정해야 더 신뢰할 수 있는 결과를 얻을 수 있음
1 2 3 4 5 6 7 8 9
// added for "heating up" prior to the main loop bench(diffSubtract); bench(diffGetTime); // now benchmark for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); }
- JS의 engine은 자주 실행되는 ‘hot code’만을 최적화하기 때문에 벤치마킹할 코드를 미리 실행시켜 heat-up하는게 좋음
- 최신의 JS engine들은 최적화를 많이 하기 때문에 연산자나 내장 함수 등을 측정하는 것은 테스트와 실제 환경에서 많이 다를 수 있음
Date.parse from a string
Date.parse(str)을 사용해서 String으로부터 timestamp를 파싱할 수 있음
str의 format은 YYYY-MM-DDTHH:mm:ss.sssZ이어야 함:
YYYY-MM-DD: year-month-day"T": delimiter(character)HH:mm:ss.sss: hour-minute-second-millisecondZ: timezone(+-hh:mm)Z만 사용하면 UTC+0을 나타냄
Example
1
2
3
4
5
let ms = Date.parse('2012-01-26T13:51:50.417-07:00');
alert(ms); // 1327611110417 (timestamp)
let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
alert(date);
- 입력 포맷이 잘못되면
NaN리턴 - 결과값을 바로
Date의 생성자에 넣어서Date객체 생성 가능
Summary
| code | description |
|---|---|
date.getFullYear() |
date의 연도를 4자리로 리턴 |
date.getMonth() |
date의 달을 [0, 11]으로 리턴 |
date.getDate() |
date의 일을 [1, 31]으로 리턴 |
date.getHours()date.getMinutes()date.getSeconds()date.getMilliseconds() |
date의 시/분/초/밀리초 리턴 |
date.getDay() |
date의 요일을 [0, 6]으로 리턴0이 일요일 |
date.getTime() |
1970.01.01 UTC+0부터 현재까지 경과한 시간을 ms 단위로 리턴 |
date.getTimezoneOffset() |
UTC+0과 현재 timezone의 차이를 분 단위로 리턴 UTC+0 기준임 |
date.setFullYear(year[, month[, date]])date.setMonth(month[, date])date.setDate(date)date.setHours(hour[, min[, sec[, ms]]])date.setMinutes(min[, sec[, ms]])date.setSeconds(sec[, ms])date.setMilliseconds(ms) |
date의 시간 설정 |
date.setTime(milliseconds) |
date의 시간을 1970.01.01 UTC 기준으로 milliseconds ms 만큼 지난 시간으로 설정 |
Date.now() |
new Date().getTime()과 같음 |
Date.parse(str) |
YYYY-MM-DDTHH:mm:ss.sssZ 포맷의 str을 파싱해서 timestamp 리턴str의 포맷이 잘못된 경우 NaN 리턴 |
- 이름에 “Time”이 들어가지 않은 method들은
get/set다음에UTC를 붙여서 UTC+0 기준으로 설정 가능(= UTC-variant) set*메소드들은 argument 이외의 정보들은 수정되지 않음
리턴값은 설정된 시간의 timestamp임- Month은
[0, 11], Day는[0, 6], Date는[1, 31]로 표현됨에 주의 Date를Number로 변환하면date.getTime()과 같은 결과임- JS는 마이크로초로 시간을 측정해주는 메소드를 지원하지 않음
browser가performance.now()로 페이지가 로딩될 때부터 경과된 시간을 마이크로초로 측정하는 메소드 지원
Node.js는microtime모듈로 지원함
Tasks
new Date(year, month+1, 0).getDate()로year.month의 마지막 날을 알 수 있음
JSON methods, toJSON
복잡한 객체를 전송/로깅 등의 이유로 String으로 바꿔야 한다고 가정하자.
toString() method를 구현하는 방법은 property가 바뀔 때마다 갱신해줘야 하고, 중첩된 객체가 있을 경우도 고려해야 하므로 비효율적임
=> JSON을 이용해서 표현 가능
JSON.stringify
JSON(JavaScript Object Notation) : 값과 객체를 나타내는 범용적인 format
원래는 JS를 위해서 만들어졌지만, 다른 언어들도 JSON을 처리하는 라이브러리를 가짐
따라서 client가 JS를 사용하면 JSON을 사용해서 데이터를 교환하는게 편리함
JS는 아래 메소드들을 지원함:
JSON.stringify(obj):obj를 JSON으로 변환JSON.parse: JSON을 객체로 변환
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json = JSON.stringify(student);
alert(typeof json); // string
alert(json);
/*
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"wife": null
}
*/
- JSON은
typeof로 조사하면String으로 나옴 - JSON을 사용해서 도출된 string
json은 JSON-encoded/serialized/stringified/marshalled object라고 불림 - JSON-encoded object는 object literal과 몇 가지 차이점이 존재함:
String은 오직 큰 따옴표로 표현됨- property name도 큰 따옴표로 표현됨
1
2
3
4
5
6
7
alert( JSON.stringify(1) ) // 1
alert( JSON.stringify('test') ) // "test"
alert( JSON.stringify(true) ); // true
alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON.stringify는 primitive에도 적용 가능함- JSON은 아래와 같은 data types를 지원함:
- Objects
{ ... } - Arrays
[ ... ] - Primitives(strings, numbers, booleans,
null)
- Objects
※ JSON은 data-only, language-independent하기 때문에 JS-specific한 properties는
JSON.stringify가 무시함
e.g. methods, symbolic properties,undefined를 저장하고 있는 properties
중요한 점은 nested object를 지원한다는 것임!
하지만 circular reference가 있으면 에러가 남
1
2
3
4
5
6
7
8
9
10
11
12
13
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: ["john", "ann"]
};
meetup.place = room; // meetup references room
room.occupiedBy = meetup; // room references meetup
JSON.stringify(meetup); // Error: Converting circular structure to JSON
Excluding and transforming: replacer
JSON.stringify의 full syntax는 아래와 같음:
1
let json = JSON.stringify(value[, replacer[, space]]);
value: 인코딩할 값replacer: 인코딩할 property가 저장된 배열 또는 mapping functionfunction(key, value)- mapping function은 인코딩하지 않을 property에 대해서
undefined를 리턴하면 됨
- mapping function은 인코딩하지 않을 property에 대해서
space: indent 설정
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
room.occupiedBy = meetup; // room references meetup
// 1
alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}
// 2
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
// 3
alert( JSON.stringify(meetup, function replacer(key, value) {
alert(`${key}: ${value}`);
return (key == 'occupiedBy') ? undefined : value;
}));
/* key:value pairs that come to replacer:
: [object Object]
title: Conference
participants: [object Object],[object Object]
0: [object Object]
name: John
1: [object Object]
name: Alice
place: [object Object]
number: 23
occupiedBy: [object Object]
*/
- nested object에도
replacer가 적용됨
=> nested object도 포함시켜야 정상적으로 인코딩됨
배열로 나열하기 너무 길 경우 함수를 사용하면 됨 replacer로 사용하는 함수에서의this는 현재 property를 가지는 object를 가리킴- 첫 번째
alert가 key가 없는 이유 : wrapper object이기 때문(key가 없고 value는 object 전체를 가리킴)
Formatting: space
JSON.stringify(value, replacer, space)의 space는 인덴팅을 조절하기 위해서 사용됨
숫자 대신 string을 넣으면 그 string을 사용해서 indentation을 수행함
오직 logging 또는 출력을 예쁘게 하기 위해서 사용(전송할 때는 indent가 없어도 됨)
Custom “toJSON”
toString을 구현해서 객체가 String으로 변환되는 것을 조절하는 것처럼,
toJSON을 구현해서 객체가 JSON으로 변환되는 것을 조절함(toJSON이 존재하면 JSON.stringify가 자동적으로 toJSON을 호출함)
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let room = {
number: 23,
toJSON() {
return this.number;
}
};
let meetup = {
title: "Conference",
date: new Date(Date.UTC(2017, 0, 1)),
room
};
alert( JSON.stringify(meetup) );
/*
{
"title":"Conference",
"date":"2017-01-01T00:00:00.000Z", // (1)
"room": 23 // (2)
}
*/
- (1)
Date객체는 날짜를 string으로 반환하는 built-intoJSON메소드가 존재함 - (2) object
room은toJSON메소드가 구현되어 있으므로meetup이stringify될 때도 propertyroom의 value가23으로 설정됨
JSON.parse
JSON-string을 decode할 때 사용됨:
1
let value = JSON.parse(str[, reviver]);
str: 파싱할 JSON-stringreviver:(key, value)를 parameter로 받아서 property의 value를 계산하는 함수function(key, value)- JSON은 주석을 허용하지 않음!
Using reviver
1
2
3
4
5
6
7
8
9
10
11
12
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
// 1
let meetup = JSON.parse(str);
alert( meetup.date.getDate() ); // Error!
// 2
let meetup = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( meetup.date.getDate() ); // now works!
-
date의 value는stringify에서 이미String으로 바뀜
=>// 1과 같이date를 다른 property와 같이 처리하면Datetype으로 들어가지 않음// 2와 같이 reviver를 사용해서dateproperty는Date객체로 선언해야 함 -
replacer와 같이 reviver도 nested object에 대해서 잘 작동함
Summary
| code | description |
|---|---|
JSON.stringify(value[, replacer[, space]]) |
value를 JSON으로 변환함replacer로 포함할 property 선택space로 인덴팅 설정 |
JSON.parse(str[, reviver]) |
str을 object로 변환함reviver로 Date와 같은 객체를 decode함 |
obj.toJSON() |
obj의 JSON으로의 변환 구현(= custom stringify) |
- JSON은 plain objects, arrays, strings, numbers, booleans,
null을 지원함
JS-specific values(methods,undefined, symbolic properties)는 무시됨 - property에 circular reference가 존재하면 에러남!
Tasks
- transformer functions(
replacer,reviver)가 호출되면 wrapper object(('', this))도 argument로 들어오는 것에 주의!- object가 트리 형태라고 생각하면,
replacer는 root(object의 wrapper object)부터 argument로 들어옴(= preorder)
reviver는 leaf부터 argument로 들어옴(= postorder)
e.g.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
let room = { number: 23 }; let meetup = { title: "Conference", occupiedBy: [{name: "John"}, {name: "Alice"}], place: room }; room.occupiedBy = meetup; meetup.self = meetup; afterstringify=JSON.stringify(meetup, function replacer(key, value) { alert(`${key} and ${value}`); return (key!='' && value===meetup)?undefined:value; }, 2); /* and [object Object] title and Conference occupiedBy and [object Object],[object Object] 0 and [object Object] name and John 1 and [object Object] name and Alice place and [object Object] number and 23 occupiedBy and [object Object] self and [object Object] */ alert(JSON.parse(afterstringify, function reviver(key, value) { alert(`${key} and ${value}`); return value; })); /* title and Conference name and John 0 and [object Object] name and Alice 1 and [object Object] occupiedBy and [object Object],[object Object] number and 23 place and [object Object] and [object Object] */
- object가 트리 형태라고 생각하면,
Leave a comment