development

자바 스크립트에서 배열을 복제하는 가장 빠른 방법-슬라이스 대 'for'루프

big-blog 2020. 10. 3. 11:19
반응형

자바 스크립트에서 배열을 복제하는 가장 빠른 방법-슬라이스 대 'for'루프


JavaScript에서 배열을 복제하려면 다음 중 사용하는 것이 더 빠릅니까?

슬라이스 방법

var dup_array = original_array.slice();

For 고리

for(var i = 0, len = original_array.length; i < len; ++i)
   dup_array[i] = original_array[i];

두 가지 방법 모두 얕은 복사 만 수행한다는 것을 알고 있습니다 . original_array에 개체에 대한 참조가 포함되어 있으면 개체는 복제되지 않지만 참조 만 복사되므로 두 배열 모두 동일한 개체에 대한 참조를 갖게됩니다. 그러나 이것은이 질문의 요점이 아닙니다.

나는 속도에 대해서만 묻는 것입니다.


어레이를 복제하는 방법 은 최소한 5 개 (!)입니다.

  • 고리
  • 일부분
  • Array.from ()
  • 연결
  • 확산 연산자 (FASTEST)

다음 정보를 제공 하는 huuuge BENCHMARKS 스레드가 있습니다.

  • 대한 깜박임 브라우저 slice(), 가장 빠른 방법입니다 concat()조금 느린이며, while loop2.4 배는 느립니다.

  • 다른 브라우저의 while loop경우 slice및에 대한 내부 최적화 기능이 없기 때문에 가장 빠른 방법 concat입니다.

2016 년 7 월에도 마찬가지입니다.

다음은 브라우저의 콘솔에 복사하여 붙여넣고 여러 번 실행하여 그림을 볼 수있는 간단한 스크립트입니다. 밀리 초를 출력하고 낮을수록 좋습니다.

while 루프

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = Array(n); 
i = a.length;
while(i--) b[i] = a[i];
console.log(new Date() - start);

일부분

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = a.slice();
console.log(new Date() - start);

이러한 메서드는 Array 개체 자체를 복제하지만 배열 내용은 참조로 복사되며 딥 복제되지 않습니다.

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true

기술적 slice 으로 가장 빠른 방법입니다. 그러나0 시작 색인 을 추가하면 더 빠릅니다 .

myArray.slice(0);

보다 빠릅니다

myArray.slice();

http://jsperf.com/cloning-arrays/3


es6 방식은 어떻습니까?

arr2 = [...arr1];

어레이 또는 객체를 딥 클론하는 가장 쉬운 방법 :

var dup_array = JSON.parse(JSON.stringify(original_array))

var cloned_array = [].concat(target_array);

간단한 데모를 만들었습니다 : http://jsbin.com/agugo3/edit

Internet Explorer 8에서 내 결과는 156, 782 및 750으로, slice이 경우 훨씬 더 빠릅니다.


a.map(e => e)이 직업의 또 다른 대안입니다. 오늘 현재 Firefox에서는 .map()매우 빠르지 .slice(0)만 Chrome에서는 그렇지 않습니다.

반면에 배열이 다차원 인 경우 배열은 객체이고 객체는 참조 유형이므로 slice 또는 concat 메서드 중 어느 것도 치료할 수 없습니다. 따라서 배열을 복제하는 적절한 방법 중 하나는 Array.prototype.clone()as 다음과 같습니다.

Array.prototype.clone = function(){
  return this.map(e => Array.isArray(e) ? e.clone() : e);
};

var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ],
    brr = arr.clone();
brr[4][2][1] = "two";
console.log(JSON.stringify(arr));
console.log(JSON.stringify(brr));


어레이를 복제하는 가장 빠른 방법

배열을 복제하는 데 걸리는 시간을 테스트하기 위해이 매우 단순한 유틸리티 함수를 만들었습니다. 100 % 신뢰할 수는 없지만 기존 어레이를 복제하는 데 걸리는 시간에 대한 대량 정보를 제공 할 수 있습니다.

function clone(fn) {
    const arr = [...Array(1000000)];
    console.time('timer');
    fn(arr);
    console.timeEnd('timer');
}

그리고 다른 접근 방식을 테스트했습니다.

1)   5.79ms -> clone(arr => Object.values(arr));
2)   7.23ms -> clone(arr => [].concat(arr));
3)   9.13ms -> clone(arr => arr.slice());
4)  24.04ms -> clone(arr => { const a = []; for (let val of arr) { a.push(val); } return a; });
5)  30.02ms -> clone(arr => [...arr]);
6)  39.72ms -> clone(arr => JSON.parse(JSON.stringify(arr)));
7)  99.80ms -> clone(arr => arr.map(i => i));
8) 259.29ms -> clone(arr => Object.assign([], arr));
9) Maximum call stack size exceeded -> clone(arr => Array.of(...arr));

업데이트 :
참고 : 그중에서 배열을 딥 클론하는 유일한 방법은 JSON.parse(JSON.stringify(arr)).

즉, 배열에를 반환하므로 함수가 포함될 수있는 경우 위를 사용하지 마십시오 null.
이 업데이트에 대해 @GilEpshtain에게 감사드립니다 .


@Dan이 "이 답변은 빨리 구식이됩니다. 벤치 마크사용 하여 실제 상황을 확인하십시오"라고 말했듯이 jsperf에서 자체 답변이없는 특정 답변이 하나 있습니다. while :

var i = a.length;
while(i--) { b[i] = a[i]; }

960,589 ops / sec, 준우승 a.concat()은 578,129 ops / sec, 즉 60 %입니다.

최신 Firefox (40) 64 비트입니다.


@aleclarson은 새롭고 더 안정적인 벤치 마크를 만들었습니다.


보세요 : link . 속도가 아니라 편안함에 관한 것입니다. 보시 다시피 기본 유형 에서만 slice (0)사용할 수 있습니다 .

배열에 대한 참조의 복사본이 아니라 배열의 독립적 인 복사본을 만들려면 배열 슬라이스 메서드를 사용할 수 있습니다.

예:

배열에 대한 참조의 복사본이 아니라 배열의 독립적 인 복사본을 만들려면 배열 슬라이스 메서드를 사용할 수 있습니다.

var oldArray = ["mip", "map", "mop"];
var newArray = oldArray.slice();

객체 복사 또는 복제하기 :

function cloneObject(source) {
    for (i in source) {
        if (typeof source[i] == 'source') {
            this[i] = new cloneObject(source[i]);
        }
        else{
            this[i] = source[i];
  }
    }
}

var obj1= {bla:'blabla',foo:'foofoo',etc:'etc'};
var obj2= new cloneObject(obj1);

출처 : 링크


브라우저에 따라 다릅니다. 블로그 게시물 Array.prototype.slice 대 수동 어레이 생성 을 살펴보면 각각의 성능에 대한 대략적인 가이드가 있습니다.

여기에 이미지 설명 입력

결과 :

여기에 이미지 설명 입력


훨씬 더 깨끗한 솔루션이 있습니다.

var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);

Array생성자가 정확히 하나의 인수로 호출 될 때 다르게 동작 하기 때문에 길이 확인이 필요합니다 .


.slice ()는 2 차원 배열에서는 작동하지 않습니다. 다음과 같은 기능이 필요합니다.

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}

어레이의 길이에 따라 다릅니다. 배열 길이가 <= 1,000,000이면 sliceconcat메서드는 거의 같은 시간이 걸립니다. 그러나 더 넓은 범위를 제공하면 concat방법이 이깁니다.

예를 들어, 다음 코드를 시도하십시오.

var original_array = [];
for(var i = 0; i < 10000000; i ++) {
    original_array.push( Math.floor(Math.random() * 1000000 + 1));
}

function a1() {
    var dup = [];
    var start = Date.now();
    dup = original_array.slice();
    var end = Date.now();
    console.log('slice method takes ' + (end - start) + ' ms');
}

function a2() {
    var dup = [];
    var start = Date.now();
    dup = original_array.concat([]);
    var end = Date.now();
    console.log('concat method takes ' + (end - start) + ' ms');
}

function a3() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with push method takes ' + (end - start) + ' ms');
}

function a4() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup[i] = original_array[i];
    }
    var end = Date.now();
    console.log('for loop with = method takes ' + (end - start) + ' ms');
}

function a5() {
    var dup = new Array(original_array.length)
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with = method and array constructor takes ' + (end - start) + ' ms');
}

a1();
a2();
a3();
a4();
a5();

original_array의 길이를 1,000,000으로 설정하면 slice메서드와 concat메서드가 거의 같은 시간 (난수에 따라 3-4ms)이 걸립니다.

original_array의 길이를 10,000,000으로 설정하면 slice메서드는 60ms concat이상 걸리고 메서드는 20ms 이상 걸립니다.


Spread운영자 와 함께하는 ECMAScript 2015 방식 :

기본 예 :

var copyOfOldArray = [...oldArray]
var twoArraysBecomeOne = [...firstArray, ..seccondArray]

브라우저 콘솔에서 시도하십시오.

var oldArray = [1, 2, 3]
var copyOfOldArray = [...oldArray]
console.log(oldArray)
console.log(copyOfOldArray)

var firstArray = [5, 6, 7]
var seccondArray = ["a", "b", "c"]
var twoArraysBecomOne = [...firstArray, ...seccondArray]
console.log(twoArraysBecomOne);

참고 문헌


간단한 해결책 :

original = [1,2,3]
cloned = original.map(x=>x)

        const arr = ['1', '2', '3'];

         // Old way
        const cloneArr = arr.slice();

        // ES6 way
        const cloneArrES6 = [...arr];

// But problem with 3rd approach is that if you are using muti-dimensional 
 // array, then only first level is copied

        const nums = [
              [1, 2], 
              [10],
         ];

        const cloneNums = [...nums];

// Let's change the first item in the first nested item in our cloned array.

        cloneNums[0][0] = '8';

        console.log(cloneNums);
           // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

        // NOOooo, the original is also affected
        console.log(nums);
          // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

따라서 이러한 시나리오가 발생하지 않도록하려면

        const arr = ['1', '2', '3'];

        const cloneArr = Array.from(arr);

벤치 마크 시간!

function log(data) {
  document.getElementById("log").textContent += data + "\n";
}

benchmark = (() => {
  time_function = function(ms, f, num) {
    var z = 0;
    var t = new Date().getTime();
    for (z = 0;
      ((new Date().getTime() - t) < ms); z++)
      f(num);
    return (z)
  }

  function clone1(arr) {
    return arr.slice(0);
  }

  function clone2(arr) {
    return [...arr]
  }

  function clone3(arr) {
    return [].concat(arr);
  }

  Array.prototype.clone = function() {
    return this.map(e => Array.isArray(e) ? e.clone() : e);
  };

  function clone4(arr) {
    return arr.clone();
  }


  function benchmark() {
    function compare(a, b) {
      if (a[1] > b[1]) {
        return -1;
      }
      if (a[1] < b[1]) {
        return 1;
      }
      return 0;
    }

    funcs = [clone1, clone2, clone3, clone4];
    results = [];
    funcs.forEach((ff) => {
      console.log("Benchmarking: " + ff.name);
      var s = time_function(2500, ff, Array(1024));
      results.push([ff, s]);
      console.log("Score: " + s);

    })
    return results.sort(compare);
  }
  return benchmark;
})()
log("Starting benchmark...\n");
res = benchmark();

console.log("Winner: " + res[0][0].name + " !!!");
count = 1;
res.forEach((r) => {
  log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
});
log("\nWinner code:\n");
log(res[0][0].toString());
<textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"></textarea>

버튼을 클릭하면 벤치 마크가 10 초 동안 실행됩니다.

내 결과 :

Chrome (V8 엔진) :

1. clone1 score: 100% *winner* (4110764)
2. clone3 score: 74.32% speed of winner. (3055225)
3. clone2 score: 30.75% speed of winner. (1264182)
4. clone4 score: 21.96% speed of winner. (902929)

Firefox (SpiderMonkey 엔진) :

1. clone1 score: 100% *winner* (8448353)
2. clone3 score: 16.44% speed of winner. (1389241)
3. clone4 score: 5.69% speed of winner. (481162)
4. clone2 score: 2.27% speed of winner. (192433)

우승자 코드 :

function clone1(arr) {
    return arr.slice(0);
}

우승자 엔진 :

SpiderMonkey (Mozilla / Firefox)

참고 URL : https://stackoverflow.com/questions/3978492/fastest-way-to-duplicate-an-array-in-javascript-slice-vs-for-loop

반응형