Arrays

Creating Arrays

Spread operator를 통한 배열 생성은 모든 이터러블 객체에서 가능해요:

console.log([...'0123456789ABCDEF']);

// 중복 원소 없애기
console.log([...new Set([...'hello world'])]);

Array 생성자에 하나의 숫자를 전달하면 length 속성이 해당 숫자로 설정된 array(?)가 반환됩니다:

let a = new Array(10);
console.log(a.length, a[0], 0 in a);

// 이럴 때 말고 언제 쓰는지는 저는 잘 모르겠네요 ㅎ,,,
console.log([...Array(10).keys()].join(' '));

Array.of는 전달된 인자들을 가지는 배열을 반환하는 팩토리 매서드에요:

console.log(Array.of(1));
console.log(Array.of(1, 2, 3));

Array.form은 이터러블이나 유사 배열을 배열로 변환하는 팩토리 메서드로 스프레드 연산자와 유사하게 동작해요. Web API에서 종종 유사배열을 반환하기에 이 때 유용하게 사용돼요. 두번째 인자로 함수를 건네줄 수 있는데 상황에 따라 배열의 생성과 매핑을 한번에 할 수 있어 배열 생성 후 별도로 map을 하는 것보다 효율적입니다:

let arraylike = { 0: 1, 1: 2, 2: 3, length: 3 };
console.log(Array.from(arraylike, (x) => 2 * x));

Reading and Writing Array Elements

배열 인덱스(array index)와 객체의 프로퍼티명(object property name)을 구분하는 것이 도움이 됩니다. 모든 배열 인덱스는 프로퍼티명이지만, 프로퍼티명 중에 0이상 2^32-1미만의 정수만 인덱스로 기능할 수 있어요.

stackoverflow.comMaximum size of an Array in JavascriptContext: I'm building a little site that reads an rss feed, and updates/checks the feed in the background. I have one array to store data to display, and another which stores ID's of records that h...

Sparse Arrays

배열 표현식에서 쉼표 사이가 비어있으면 희소 배열이 됩니다. undefined인 요소가 있는 것과 희소 배열인 것은 달라요:

let a = [undefined, , undefined];
console.log(0 in a, 1 in a);
console.log(a[0], a[1]);

희소 배열의 length 프로퍼티 값은 배열 요소의 개수보다 큽니다:

let a = new Array(5); // 희소 배열입니다.
console.log(a.length);

a = [];
console.log(a.length);

a[1000] = 0;
console.log(a.length);

희소 배열이 희소(sparse)할수록 느리지만 메모리 효율적인 방식으로 처리됩니다.

Array Length

Invariant: 배열 요소의 인덱스는 항상 length값 미만입니다.

a = [1, 2, 3, 4, 5];
a.length = 3;
console.log(a);

Adding and Deleting Array Elements

재미로만 보는 push와 a[a.length]의 성능 테스트:

let ITER = 123456;

let arr = new Array();
let t = performance.now();
for (let i = 0; i < ITER; i++) {
  arr[arr.length] = i;
}

console.log(performance.now() - t);

arr = new Array();
t = performance.now();
for (let i = 0; i < ITER; i++) {
  arr.push(i);
}

console.log(performance.now() - t);

stackoverflow.comUsing the push method or .length when adding to array?What are the downsides to doing: var myArray = []; myArray[myArray.length] = val1; myArray[myArray.length] = val2; instead of: var myArray = []; myArray.push(val1); myArray.push(val2); I'm sure...

요소를 delete한다고 길이가 달라지지는 않습니다:

let a = [1, 2, 3];
delete a[2];
console.log(2 in a, a[2], a.length);

Iterating Arrays

맨날 Object.entries(arr)로 썼는데 배열이면 아래처럼 바로 쓸 수도 있네요:

for (const [k, v] of [...'abcdef'].entries()) console.log(k, v);

배열에 실제로 존재하는 키만 포함하는 Object.keys()와 달리, keys() 반복자는 누락된 속성을 나타내는 빈 공간을 무시하지 않습니다.

developer.mozilla.orgArray.prototype.keys() - JavaScript | MDNArray 인스턴스의 keys() 메서드는 배열의 각 인덱스에 대한 키를 포함하는 새로운 배열 반복자 객체를 반환합니다.

Multidimensional Arrays

희소 배열의 빈 슬롯은 배열 메서드 간에 일관성 없이 동작합니다. 일반적으로 오래된 메서드(forEach 등)는 빈 슬롯을 건너뛰는 반면, 최신 메서드(for/of 등)는 빈 슬롯을 undefined로 처리합니다.

developer.mozilla.orgArray - JavaScript | MDN다른 프로그래밍 언어의 배열과 마찬가지로, Array 객체는 여러 항목의 컬렉션을 단일 변수 이름 아래 저장할 수 있고, 일반적인 배열 연산을 수행하기 위한 멤버가 있습니다.

let a = [1, , 3];
a.forEach((i) => console.log(i));
for (const i of a) console.log(i);

Array Iterator Methods

let a = [1, , 3];
a.forEach(
  // forEach 외에 다른 대부분의 메서드들도 val, idx, arr을 함수에 건네줍니다.
  function (val, idx, arr) {
    console.log(val, idx, arr, this);
  },
  // 다른 대부분의 메서드들도 두번째 인자의 값이 (있다면) 함수 내부의 this가 됩니다.
  { val: 'hello' },
);

forEach와 for문은 유사하지만 for문의 break에 대응되는 문법은 없습니다.

filter 함수는 predicate 함수를 받아요:

stackoverflow.comWhat does 'predicate' mean in the context of computer science?Specifically I've seen it used in the context of text filtering. As if "predicate" == "filter criteria". Is this accurate?

희소 배열의 간격 없애기 예제:

console.log([1, , 2, , , 3, 4, , 5, , , 6, , ,].filter(() => true));

수학적 관습?으로 인해 빈 배열에 대해서 every와 some의 결과는 다음과 같습니다:

console.log([].every((x) => true));
console.log([].some((x) => true));

reduce 함수는 첫번째 인자로 지금까지의 누적된 결과를 전달합니다. 함수에 initial value를 전달해주지 않으면 첫번째 요소을 사용합니다:

let ret = [1, , , 2, , , 3].reduce((acc, val, idx, arr) => {
  console.log(acc, val, idx, arr);
  return acc + val;
}, 0); // 인자 0을 지워보세요

console.log(ret);

[].reduce((acc, val) => acc + val);

reduceRight는 인덱스 내림차순으로 처리합니다. (왜 써본 적이 없지,,,)

reduce/reduceRight는 this를 설정하는 인자가 없습니다. 설정해야된다면 나중에 배울 bind를 쓰면 돼요.

Flattening arrays with flat() and flatMap()

let arr = [1, [2, [3, [4]]]];
console.log(arr.flat());
console.log(arr.flat(1));
console.log(arr.flat(2));
console.log(arr.flat(3));

a.flatMap(f)a.map(f).flat()과 같지만 더 효율적입니다. 입력 배열의 각 요소마다 N(>=0)개의 요소를 대응시킬 수 있도록 일반화된 map으로 생각할 수 있어요:

let phrases = ['hello world!', 'hello visitors!'];
console.log(phrases.flatMap((x) => x.split(' ')));

let numbers = [-2, -1, 0, 1, 2];
console.log(numbers.flatMap((x) => (x < 0 ? [] : Math.sqrt(x))));

Adding arrays with concat()

기존 배열을 바꾸지 않아요.

console.log([1, 2].concat([3, [4]], 5));

Stacks and Queues with push(), pop(), shift(), and unshift()

저는 shift랑 unshift를 자꾸 까먹어요:

let a = [1, 2, 3];
console.log(a.shift());
a.unshift(4, 5);
console.log(a);

Subarrays with slice(), splice(), fill(), and copyWithin()

let a = [1, 2, 3, 4, 5];
console.log(a.slice(1, 3));
console.log(a.slice(1));
console.log(a.slice(-3, -1));

splice는 start index, length, 삽입할 요소들을 인자로 받습니다:

let a = [1, 2, 3, 4, 5];
console.log(a.splice(1, 2), a);
console.log(a.splice(0, 2, [1, 2], 3), a);

let a = new Array(5);
console.log(a.fill(0));
console.log(a.fill(9, 1));
console.log(a.fill(8, 2, -1));

copyWithin은 배열 일부분을 배열의 다른 곳으로 옯겨요. 고성능 메서드로 설계되었고 나중에 배울 typed array에 유용합니다. C의 memmove()를 본땄다고 하네요. 목적지 인덱스, 시작 인덱스(없으면 0), 개수 인덱스(없으면 배열 길이) 순으로 인자를 받아요:

let a = [1, 2, 3, 4, 5];
console.log(a.copyWithin(1));
console.log(a.copyWithin(2, 3));
console.log(a.copyWithin(0, -2));

Array Searching and Sorting Methods

let a = [1, 2, 3, 2, 1];
console.log(a.indexOf(2));
console.log(a.lastIndexOf(2));
console.log(a.indexOf(4));

indexOf의 두번째 인자는 탐색을 시작할 인덱스를 의미합니다:

let findAll = (a, x) => {
  let results = [];
  let pos = 0;
  while (pos < a.length) {
    pos = a.indexOf(x, pos);
    if (pos === -1) break;
    results.push(pos++);
  }
  return results;
};

console.log(findAll([1, 2, 3, 2, 1], 2));

includes 메서드는 NaN 처리를 합니다:

let a = [1, NaN, 3];
console.log(a.indexOf(NaN));
console.log(a.includes(NaN));

stackoverflow.comWhy does [NaN].includes(NaN) return true in JavaScript?I'm familiar with NaN being &quot;weird&quot; in JavaScript, i.e., NaN === NaN always returns false, as described here. So one should not make === comparisons to check for NaN, but use isNaN(..) in...

sort는 요소를 문자열로 바꿔서 사전순으로 정렬합니다. 대신 undefined는 마지막으로 간대요:

console.log([undefined, 9, 10, '200'].sort());
console.log([33, 4, 1111, 222].sort());
console.log([33, 4, 1111, 222].sort((a, b) => a - b));

Array to String Conversions

Array 객체의 toString은 인자 없는 join처럼 동작합니다:

console.log([1, 2, 3].toString());
console.log([, , ,].toString());

let a = { toLocaleString: () => 'aaa' };
let b = { toLocaleString: () => 'bbb' };
console.log([a, b].toString());
console.log([a, b].toLocaleString());

Static Array Functions

Array.isArray()는 프로토타입 체인에 Array.prototype이 있지만 실제 배열이 아닌 객체를 거부합니다. instanceof Array는 이를 허용합니다.

developer.mozilla.orgArray.isArray() - JavaScript | MDNArray.isArray() 정적 메서드는 전달된 값이 Array인지 판단합니다.

Determining with absolute accuracy whether or not a JavaScript object is an array

web.mit.edu

Array-Like Objects

배열 관련된 많은 알고리즘들이 객체가 length 프로퍼티와 0 이상 정수 프로퍼티들을 가지기만해도 잘 동작합니다:

let a = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
console.log(Array.prototype.join.call(a, '+'));

Client-side JS에서 많은 HTML document 메서드들이 유사 배열 객체를 반환합니다.

Strings as Arrays

let a = 'Javascript';
console.log(Array.prototype.join.call(a, ' '));

// string은 immutable합니다
console.log(Array.prototype.push.call(a, '!'));