This is a simple article about some tips on using JavaScript arrays. We will use different methods to combine/merge two JS arrays, as well as discuss the advantages/disadvantages of each method.
Let us first consider the following situation:
var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var b = [ "foo", "bar", "baz", "bam", "bun", "fun" ];
Obviously the simplest combination result should be:
[
1, 2, 3, 4, 5, 6, 7, 8, 9,
"foo", "bar", "baz", "bam" "bun", "fun"
]
concat(..)
This is the most common approach:
var c = a.concat( b );
a; // [1,2,3,4,5,6,7,8,9]
b; // ["foo","bar","baz","bam","bun","fun"]
c; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
As you can see, C is a brand new array, representing the combination of two arrays a and b, leaving A and B unchanged. Simple right?
But what if a has 10,000 elements and b also has 10,000 elements? C will have 20,000 elements, so the memory usage of a and b will double.
“No problem!”, you say. Let them be garbage collected, set A and B to null, problem solved!
a = b = null; // 'a' and 'b' are recycled
Haha. For small arrays with only a few elements, this is no problem. But for large arrays, or in systems with limited memory that need to repeat this process frequently, it actually has a lot of room for improvement.
Loop insertion
Okay, let’s copy the contents of one array to another, using: Array#push(..)
// `b` onto `a`
for (var i=0; i < b.length; i ) {
a.push( b[i] );
}
a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
b = null;
Now, array a has the contents of array b.
Seems to have a better memory footprint.
But what if array a is smaller? For memory and speed reasons, you may want to put the smaller a in front of b. No problem, just replace push(..) with unshift(..):
// `a` into `b`:
for (var i=a.length-1; i >= 0; i--) {
b.unshift( a[i] );
}
b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
Functional Tips
However, the for loop is indeed ugly and difficult to maintain. Can we do better?
This is our first attempt, using Array#reduce:
// `b` onto `a`:
a = b.reduce( function(coll,item){
coll.push( item );
return coll;
}, a );
a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
// or `a` into `b`:
b = a.reduceRight( function(coll,item){
coll.unshift(item);
return coll;
}, b );
b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
Array#reduce(..) and Array#reduceRight(..) are nice, but they are a little clunky. ES6=>'s arrow functions will reduce the amount of code some, but it still requires a function that needs to be called once for each element, which is not perfect.
How about this:
// `b` onto `a`:
a.push.apply( a, b );
a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
// or `a` into `b`:
b.unshift.apply( b, a );
b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
이게 훨씬 낫지 않나요? 특히 unshift(..) 메소드는 여기서 이전의 역정렬에 대해 걱정할 필요가 없기 때문에 더욱 그렇습니다. ES6의 범위 연산은 더 아름다워질 것입니다: a.push( ...b) 또는 b.unshift( ...a
최대 배열 길이 제한
첫 번째 주요 문제는 메모리 사용량이 두 배로 늘어났고(물론 일시적입니다!) 추가되는 것은 기본적으로 함수 호출을 통해 요소를 스택에 복사하는 것입니다. 또한 다양한 JS 엔진에는 복사되는 데이터 길이에 제한이 있습니다.
따라서 배열에 백만 개의 요소가 있는 경우 push(...) 또는 unshift(...)에 허용되는 호출 스택 제한을 확실히 초과하게 됩니다. 아쉽게도 수천 개의 요소로 훌륭한 작업을 수행할 수 있지만 합리적인 길이 제한을 초과하지 않도록 주의해야 합니다.
참고: push(...) 및 unshift(...)와 동일한 문제가 있는 splice(...)를 시도해 볼 수 있습니다.
이 최대 길이 제한을 피하는 방법이 있습니다.
함수 CombineInto(a,b) {
var len = a.길이;
for (var i=0; i
b.unshift.apply( b, a.slice( i, i 5000 ) );
}
}
잠깐만요. 가독성이 뒤떨어져 있어요. 그렇죠, 바뀌면 더 심해질 수도 있겠네요, 하하.