คัดลอกอาร์เรย์ตามค่า


1745

เมื่อคัดลอกอาร์เรย์ใน JavaScript ไปยังอาร์เรย์อื่น:

var arr1 = ['a','b','c'];
var arr2 = arr1;
arr2.push('d');  //Now, arr1 = ['a','b','c','d']

ฉันรู้ว่าarr2หมายถึงอาร์เรย์เดียวกันกับarr1แทนที่จะเป็นอาร์เรย์อิสระใหม่ ฉันจะคัดลอกอาร์เรย์เพื่อรับสองอาร์เรย์อิสระได้อย่างไร


3
ดูเหมือนว่าขณะนี้ใน Chrome 53 และ Firefox 48 เรามีประสิทธิภาพการทำงานที่เย็นsliceและspliceการดำเนินงานและผู้ประกอบการแพร่กระจายใหม่และArray.fromมีการดำเนินการช้ามาก ดูperfjs.fnfo
Pencroff

jsben.ch/#/wQ9RU <= เกณฑ์มาตรฐานนี้ให้ภาพรวมเกี่ยวกับวิธีการต่างๆในการคัดลอกอาร์เรย์
EscapeNetscape


สำหรับอาร์เรย์นี้ (หนึ่งที่มีสายดั้งเดิม) คุณสามารถใช้var arr2 = arr1.splice();เพื่อคัดลอกลึก แต่เทคนิคนี้จะไม่ทำงานหากองค์ประกอบในอาร์เรย์ของคุณประกอบด้วยโครงสร้างตัวอักษร (เช่น[]หรือ{}) หรือวัตถุต้นแบบ (เช่นfunction () {}, newฯลฯ ) ดูคำตอบของฉันด้านล่างสำหรับวิธีแก้ไขเพิ่มเติม
tfmontague

16
นั่นเป็นปี 2560 ดังนั้นคุณอาจลองใช้คุณสมบัติ ES6: let arr2 = [...arr1]; developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Hinrich

คำตอบ:


2701

ใช้สิ่งนี้:

var newArray = oldArray.slice();

โดยทั่วไปแล้ว slice()ดำเนินการโคลนอาร์เรย์และส่งกลับการอ้างอิงไปยังอาร์เรย์ใหม่

โปรดทราบว่า:

สำหรับการอ้างอิงสตริงและตัวเลข (ไม่ใช่วัตถุจริง) ให้slice()คัดลอกการอ้างอิงวัตถุไปยังอาร์เรย์ใหม่ ทั้งอาร์เรย์ดั้งเดิมและใหม่อ้างอิงถึงวัตถุเดียวกัน หากการอ้างอิงวัตถุเปลี่ยนแปลงการเปลี่ยนแปลงจะสามารถมองเห็นได้ทั้งอาร์เรย์ใหม่และอาร์เรย์ดั้งเดิม

ดั้งเดิมเช่นสตริงและตัวเลขไม่เปลี่ยนรูปดังนั้นการเปลี่ยนแปลงสตริงหรือตัวเลขจึงเป็นไปไม่ได้


9
เกี่ยวกับประสิทธิภาพการทดสอบ jsPerf ต่อไปนี้แสดงให้เห็นว่า var arr2 = arr1.slice () เร็วพอ ๆ กับ var arr2 = arr1.concat (); JSPerf: jsperf.com/copy-array-slice-vs-concat/5และjsperf.com/copy-simple-array ผลของการjsperf.com/array-copy/5ทำให้ฉันประหลาดใจจนถึงจุดที่ฉันสงสัยว่ารหัสทดสอบนั้นถูกต้องหรือไม่
โคเฮน

94
แม้ว่าสิ่งนี้จะได้รับการโหวตขึ้นมากมายแล้ว แต่สมควรได้รับอีกเพราะมันอธิบายการอ้างอิงใน JS อย่างถูกต้องซึ่งเป็นของหายากน่าเสียดาย
Wayne

34
@Gáborฉันต้องการเพิ่มห้องสมุดทั้งหมดเพื่อให้สามารถอ่านได้หรือไม่ จริงๆ? ฉันแค่เพิ่มความคิดเห็นหากฉันเป็นห่วงเรื่องความสามารถในการอ่าน โปรดดู: var newArray = oldArray.slice (); // Clone oldArray เป็น newArray
dudewad

12
@Gáborฉันกำลังรับแน่นอน แต่การตอบปัญหาทางวิศวกรรมที่เฉพาะเจาะจงโดยการรวมทั้งห้องสมุดในความคิดของฉันไม่เป็นประโยชน์มันเป็นการออกแบบที่ขยายตัว ฉันเห็นนักพัฒนาทำมากและจากนั้นคุณก็จบลงด้วยโครงการที่รวมกรอบงานทั้งหมดเพื่อแทนที่ไม่ต้องเขียนฟังก์ชันเดียว เพียงแค่ MO ของฉัน
dudewad

5
บทเรียนที่ได้รับ: อย่าสับสน.slice()กับการ.splice()ที่ช่วยให้คุณอาเรย์ที่ว่างเปล่า แตกต่างใหญ่
Crazypeter

531

ใน Javascript เทคนิคการทำสำเนาลึกนั้นขึ้นอยู่กับองค์ประกอบในอาเรย์ เริ่มกันเลย

องค์ประกอบสามประเภท

องค์ประกอบสามารถเป็นได้: ค่าตามตัวอักษร, โครงสร้างตัวอักษรหรือต้นแบบ

// Literal values (type1)
const booleanLiteral = true;
const numberLiteral = 1;
const stringLiteral = 'true';

// Literal structures (type2)
const arrayLiteral = [];
const objectLiteral = {};

// Prototypes (type3)
const booleanPrototype = new Bool(true);
const numberPrototype = new Number(1);
const stringPrototype = new String('true');
const arrayPrototype = new Array();
const objectPrototype = new Object(); // or `new function () {}`

จากองค์ประกอบเหล่านี้เราสามารถสร้างอาร์เรย์สามประเภท

// 1) Array of literal-values (boolean, number, string) 
const type1 = [true, 1, "true"];

// 2) Array of literal-structures (array, object)
const type2 = [[], {}];

// 3) Array of prototype-objects (function)
const type3 = [function () {}, function () {}];

เทคนิคการทำสำเนาแบบลึกนั้นขึ้นอยู่กับชนิดของอาเรย์ทั้งสามชนิด

ขึ้นอยู่กับประเภทขององค์ประกอบในอาเรย์เราสามารถใช้เทคนิคต่าง ๆ เพื่อทำสำเนาลึก

จาวาสคริปต์เทคนิคการคัดลอกลึกตามประเภทองค์ประกอบ

  • อาร์เรย์ของตัวอักษรค่า (type1) , , และเทคนิคสามารถใช้ในการคัดลอกอาร์เรย์ลึกที่มีค่าที่แท้จริง (บูลจำนวนและสตริง) เท่านั้น ตำแหน่งที่ตัวดำเนินการ Spread มีประสิทธิภาพที่ดีที่สุด ( https://measurethat.net/Benchmarks/Show/4281/0/spread-array-performance-vs-slice-splice-concat )
    [...myArray]myArray.splice(0)myArray.slice()myArray.concat()[...myArray]

  • อาร์เรย์ของตัวอักษรค่า (type1) และตัวอักษรโครงสร้าง (type2)เทคนิคสามารถนำมาใช้เป็นค่าที่แท้จริงสำเนาลึก (บูลจำนวนสตริง) และโครงสร้างตัวอักษร (อาร์เรย์วัตถุ) แต่ไม่ต้นแบบวัตถุ
    JSON.parse(JSON.stringify(myArray))

  • อาร์เรย์ทั้งหมด (type1, type2, type3)สามารถใช้เทคนิค
    jQuery ใน$.extend(myArray)การคัดลอกอาเรย์ทุกประเภท ไลบรารี่อย่างUnderscoreและLo-dashนำเสนอฟังก์ชั่นสำเนาที่คล้ายกันกับjQuery $.extend()แต่มีประสิทธิภาพต่ำกว่า เพิ่มเติมน่าแปลกใจที่$.extend()มีประสิทธิภาพที่สูงกว่าJSON.parse(JSON.stringify(myArray))เทคนิคhttp://jsperf.com/js-deep-copy/15
    และสำหรับนักพัฒนาที่ไม่ได้อยู่ในห้องสมุดบุคคลที่สาม (เช่น jQuery) คุณสามารถใช้ฟังก์ชั่นที่กำหนดเองดังต่อไปนี้ ซึ่งมีประสิทธิภาพสูงกว่า $ .extend และคัดลอกอาร์เรย์ทั้งหมดลึก

function copy(aObject) {
  if (!aObject) {
    return aObject;
  }

  let v;
  let bObject = Array.isArray(aObject) ? [] : {};
  for (const k in aObject) {
    v = aObject[k];
    bObject[k] = (typeof v === "object") ? copy(v) : v;
  }

  return bObject;
}

ดังนั้นเพื่อตอบคำถาม ...

คำถาม

var arr1 = ['a','b','c'];
var arr2 = arr1;

ฉันรู้ว่า arr2 หมายถึงอาร์เรย์เดียวกันกับ arr1 แทนที่จะเป็น array ใหม่ที่เป็นอิสระ ฉันจะคัดลอกอาร์เรย์เพื่อรับสองอาร์เรย์อิสระได้อย่างไร

ตอบ

เนื่องจากarr1เป็นอาร์เรย์ของค่าตัวอักษร (บูลีน, ตัวเลขหรือสตริง) คุณสามารถใช้เทคนิคการทำสำเนาแบบลึกที่กล่าวถึงข้างต้นซึ่งผู้ประกอบการสเปรด...มีประสิทธิภาพสูงสุด

// Highest performance for deep copying literal values
arr2 = [...arr1];

// Any of these techniques will deep copy literal values as well,
//   but with lower performance.
arr2 = arr1.slice();
arr2 = arr1.splice(0);
arr2 = arr1.concat();
arr2 = JSON.parse(JSON.stringify(arr1));
arr2 = $.extend(true, [], arr1); // jQuery.js needed
arr2 = _.extend(arr1); // Underscore.js needed
arr2 = _.cloneDeep(arr1); // Lo-dash.js needed
arr2 = copy(arr1); // Custom-function needed - as provided above

1
วิธีการเหล่านี้หลายวิธีทำงานได้ไม่ดี arr1การใช้ตัวดำเนินการกำหนดวิธีการที่คุณจะต้องโอนค่าตัวอักษรเดิมของ มันยากมากที่จะเป็นอย่างนั้น ใช้spliceobliterates arr1ดังนั้นจึงไม่ใช่การคัดลอกเลย การใช้JSONจะล้มเหลวหากค่าใด ๆ ในอาร์เรย์เป็นฟังก์ชั่นหรือมีต้นแบบ (เช่น a Date)
Dancrumb

การใช้รอยต่อเป็นวิธีแก้ปัญหาบางส่วน มันจะล้มเหลวภายใต้กรณีมากกว่า JSON Splice สร้างสำเนาและสตริงที่มีความลึกเมื่อย้ายค่า - ไม่เคยบอกว่ามันจะส่งคืนสำเนา
tfmontague

1
ทำไมต้องประกบกัน (0) มันไม่ควรจะฝาน () ฉันคิดว่ามันไม่ควรที่จะแก้ไขอาเรย์ดั้งเดิม @JamesMontagne
helpse

2
ประกบกันจะสร้างตัวชี้ไปยังองค์ประกอบในอาร์เรย์เดิม (คัดลอกตื้น) splice (0) จะจัดสรรหน่วยความจำใหม่ (สำเนาลึก) สำหรับองค์ประกอบในอาร์เรย์ซึ่งเป็นตัวเลขหรือสตริงและสร้างพอยน์เตอร์สำหรับองค์ประกอบองค์ประกอบอื่น ๆ ทั้งหมด (คัดลอกตื้น) ด้วยการส่งค่าเริ่มต้นเป็นศูนย์ไปยังฟังก์ชั่นการต่อรอยมันจะไม่ต่อกันองค์ประกอบใด ๆ จากอาร์เรย์เดิมและดังนั้นจึงไม่ได้แก้ไข
tfmontague

1
อันที่จริงมีอาร์เรย์ประเภทเดียวเท่านั้น: อาร์เรย์ของ "somethings" ไม่มีความแตกต่างระหว่าง[0,"1",{2:3},function random() {return 4;}, [[5,6,7],[8,9,10],[11,12,13]]]และอาเรย์อื่น ๆ
wizzwizz4

189

คุณสามารถใช้การแพร่กระจายอาร์เรย์...เพื่อคัดลอกอาร์เรย์

const itemsCopy = [...items];

นอกจากนี้หากต้องการสร้างอาร์เรย์ใหม่โดยที่มีอยู่เป็นส่วนหนึ่งของมัน:

var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];

ตอนนี้สเปรด Array ได้รับการสนับสนุนในเบราว์เซอร์หลัก ๆ ทั้งหมดแต่ถ้าคุณต้องการการสนับสนุนที่เก่ากว่าให้ใช้ typescript หรือ babel และคอมไพล์กับ ES5

ข้อมูลเพิ่มเติมเกี่ยวกับสเปรด


1
วิธีนี้ใช้ไม่ได้กับการทำสำเนาลึก ลึกโคลนอาร์เรย์ใน JavaScript
SNag

151

ไม่จำเป็นต้อง jQuery ... ตัวอย่างการทำงาน

var arr2 = arr1.slice()

สิ่งนี้จะคัดลอกอาร์เรย์จากตำแหน่งเริ่มต้น0จนถึงจุดสิ้นสุดของอาร์เรย์

มันเป็นสิ่งสำคัญที่จะต้องทราบว่ามันจะทำงานได้ตามที่คาดไว้สำหรับประเภทดั้งเดิม (สตริงจำนวน ฯลฯ ) และยังอธิบายพฤติกรรมที่คาดหวังสำหรับประเภทการอ้างอิง ...

Objectถ้าคุณมีอาร์เรย์ของชนิดการอ้างอิงการพูดประเภท อาร์เรย์จะถูกคัดลอก แต่ทั้งสองของอาร์เรย์จะมีการอ้างอิงเดียวกันObject's ดังนั้นในกรณีนี้ดูเหมือนว่าอาร์เรย์จะถูกคัดลอกโดยอ้างอิงถึงแม้ว่าอาร์เรย์จะถูกคัดลอกจริง


12
ไม่นี้จะไม่เป็นสำเนาลึก
jondavidjohn

ลองสิ่งนี้ var arr2 = JSON.stringify(arr1); arr2 = JSON.parse(arr2);
pradeep1991singh

2
ความแตกต่างระหว่างคำตอบนี้กับคำตอบที่ยอมรับคืออะไร?
Isaac Pak

รับข้อผิดพลาดในคอนโซลสำหรับตัวอย่างของคุณ "TypeError: window.addEvent ไม่ใช่ฟังก์ชัน"
Ravi Sharma

72

อีกทางเลือกหนึ่งsliceคือconcatซึ่งสามารถใช้ได้ 2 วิธี ครั้งแรกของสิ่งเหล่านี้อาจอ่านได้มากขึ้นเนื่องจากพฤติกรรมที่ต้องการชัดเจนมาก:

var array2 = [].concat(array1);

วิธีที่สองคือ:

var array2 = array1.concat();

โคเฮน (ในความคิดเห็น) ชี้ให้เห็นว่าวิธีหลังนี้มีประสิทธิภาพที่ดีขึ้น

วิธีการทำงานนี้เป็นconcatวิธีที่สร้างอาร์เรย์ใหม่ประกอบด้วยองค์ประกอบในวัตถุที่มันถูกเรียกตามด้วยองค์ประกอบของอาร์เรย์ใด ๆ ที่ส่งผ่านไปเป็นอาร์กิวเมนต์ ดังนั้นเมื่อไม่มีข้อโต้แย้งใด ๆ มันก็แค่คัดลอกอาร์เรย์

ลี Penkman ยังอยู่ในความคิดเห็นที่ชี้ให้เห็นว่าถ้ามีโอกาสarray1ก็คือundefinedคุณสามารถกลับอาร์เรย์ที่ว่างดังนี้

var array2 = [].concat(array1 || []);

หรือสำหรับวิธีที่สอง:

var array2 = (array1 || []).concat();

โปรดทราบว่าคุณยังสามารถทำเช่นนี้กับ:slicevar array2 = (array1 || []).slice();


31
ที่จริงคุณสามารถทำได้: var array2 = array1.concat (); มันเร็วขึ้นมากเกี่ยวกับประสิทธิภาพ (JSPerf: jsperf.com/copy-simple-arrayและjsperf.com/copy-array-slice-vs-concat/5
โคเฮน

5
มูลค่า noting ว่าถ้า array1 ไม่ได้เป็นอาเรย์แล้ว[].concat(array1)ส่งกลับเช่นถ้าไม่ได้กำหนดของคุณจะได้รับ[array1] [undefined]บางครั้งฉันก็ทำvar array2 = [].concat(array1 || []);
ลีเพ็ญมัน

60

นี่คือวิธีที่ฉันทำหลังจากพยายามหลายวิธี:

var newArray = JSON.parse(JSON.stringify(orgArray));

สิ่งนี้จะสร้างสำเนาลึกใหม่ที่ไม่เกี่ยวข้องกับสำเนาแรก (ไม่ใช่สำเนาตื้น)

สิ่งนี้เห็นได้ชัดว่าจะไม่ลอกแบบเหตุการณ์และฟังก์ชั่น แต่สิ่งที่ดีที่คุณสามารถทำได้ในหนึ่งบรรทัดและมันสามารถใช้กับวัตถุประเภทใดก็ได้ (อาร์เรย์, สตริง, ตัวเลข, วัตถุ ... )


4
นี่คือสิ่งที่ดีที่สุด ฉันใช้วิธีเดียวกันเมื่อนานมาแล้วและคิดว่าไม่มีความรู้สึกเพิ่มเติมในลูปเวียนของโรงเรียนเก่า
Vladimir Kharlampidi

1
โปรดทราบว่าตัวเลือกนี้ไม่สามารถจัดการโครงสร้างคล้ายกราฟได้ดี: ขัดข้องเมื่อมีรอบและไม่เก็บการอ้างอิงที่ใช้ร่วมกันไว้
Ruben

1
สิ่งนี้ก็ล้มเหลวสำหรับสิ่งต่าง ๆ เช่นDateหรืออะไรก็ตามที่มีต้นแบบ นอกจากนี้ยังundefinedได้รับการแปลงnullเป็น
Dancrumb

7
ไม่มีใครกล้าพอที่จะแสดงความคิดเห็นเกี่ยวกับความไร้ประสิทธิภาพขั้นต้นทั้งในซีพียูและหน่วยความจำในการซีเรียลไลซ์เป็นข้อความแล้วแยกวิเคราะห์กลับไปที่วัตถุหรือไม่?
Lawrence Dol

3
วิธีนี้เป็นวิธีเดียวที่ทำงานได้ การใช้ slice () เป็นวิธีแก้ปัญหาที่แท้จริง

20

วิธีการที่กล่าวถึงบางวิธีทำงานได้ดีเมื่อทำงานกับชนิดข้อมูลอย่างง่ายเช่นตัวเลขหรือสตริง แต่เมื่ออาร์เรย์มีวัตถุอื่นวิธีการเหล่านี้ล้มเหลว เมื่อเราพยายามที่จะส่งผ่านวัตถุใด ๆ จากอาร์เรย์หนึ่งไปยังอีกมันจะถูกส่งเป็นข้อมูลอ้างอิงไม่ใช่วัตถุ

เพิ่มรหัสต่อไปนี้ในไฟล์ JavaScript ของคุณ:

Object.prototype.clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (i in this) {
        if (i == 'clone') 
            continue;
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        } 
        else 
            newObj[i] = this[i]
    } return newObj;
};

และก็ใช้

var arr1 = ['val_1','val_2','val_3'];
var arr2 = arr1.clone()

มันจะทำงาน.


2
ฉันได้รับข้อผิดพลาดนี้เมื่อฉันเพิ่มรหัสนี้ในหน้าของฉัน 'Uncaught RangeError: เกินขนาดสแต็กการโทรสูงสุด'
sawe

1
ฉันขอโทษข้อผิดพลาดนี้เกิดขึ้นในโครเมี่ยมถ้าไม่ได้ประกาศ arr1 ดังนั้นฉันคัดลอกโค้ดข้างต้นและฉันได้รับข้อผิดพลาดอย่างไรก็ตามถ้าฉันประกาศอาร์เรย์ arr1 แล้วฉันจะไม่ได้รับข้อผิดพลาด คุณสามารถปรับปรุงคำตอบโดยการประกาศ arr1 ที่อยู่เหนือ arr2 ฉันเห็นว่ามีบางส่วนของ 'เรา' อยู่ที่นั่นซึ่งไม่รู้จักว่าเราต้องประกาศ arr1 (ส่วนหนึ่งเป็นเพราะเมื่อฉันประเมินคำตอบของคุณฉันกำลังเร่งรีบ) และต้องการสิ่งที่ 'เพิ่งได้ผล')
เลื่อย

.slice()ยังคงทำงานได้ดีแม้ว่าคุณจะมีวัตถุในอาร์เรย์ของคุณ: jsfiddle.net/edelman/k525g
Jason

7
@ Jason แต่วัตถุยังคงชี้ไปที่วัตถุเดียวกันดังนั้นการเปลี่ยนแปลงจะเปลี่ยนอีกอัน jsfiddle.net/k525g/1
Samuel

รหัสที่ยอดเยี่ยม หนึ่งคำถามที่ฉันมีฉันพยายามที่จะคัดลอกหนึ่งอาร์เรย์ไปยังอีกแบบเช่น var arr1 = new Array () และจากนั้น var arr2 = arr1; ถ้าฉันเปลี่ยนบางสิ่งใน arr2 การเปลี่ยนแปลงก็เกิดขึ้นกับ arr1 ด้วย อย่างไรก็ตามถ้าฉันใช้โคลนต้นแบบที่คุณสร้างขึ้นมันจะสร้างอินสแตนซ์ใหม่ที่สมบูรณ์ของอาร์เรย์นั้นหรือกล่าวอีกนัยหนึ่งว่าคัดลอกมัน นี่เป็นปัญหาเบราว์เซอร์หรือไม่ หรือจาวาสคริปต์โดยค่าเริ่มต้นตั้งค่าตัวแปรสองตัวโดยชี้ไปที่อีกอันด้วยการใช้พอยน์เตอร์เมื่อมีคนทำ var arr2 = arr1 และทำไมไม่เกิดขึ้นกับตัวแปรจำนวนเต็ม? ดูjsfiddle.net/themhz/HbhtA
themhz


17

โดยส่วนตัวแล้วฉันคิดว่าArray.fromเป็นวิธีแก้ปัญหาที่อ่านง่ายกว่า ยังไงก็ตามจงระวังการสนับสนุนเบราว์เซอร์

//clone
let x = [1,2,3];
let y = Array.from(x);

//deep clone
let clone = arr => Array.from(arr,item => Array.isArray(item) ? clone(item) : item);
let x = [1,[],[[]]];
let y = clone(x);

1
ใช่มันอ่านง่ายมาก การ.slice()แก้ปัญหานั้นไม่ได้ใช้งานง่ายอย่างสมบูรณ์ ขอบคุณสำหรับสิ่งนี้.
Banago

15

สิ่งสำคัญ!

ส่วนใหญ่ของคำตอบที่นี่ทำงานสำหรับกรณีเฉพาะ

หากคุณไม่สนใจเกี่ยวกับการใช้วัตถุ / ที่ซ้อนกันและอุปกรณ์ประกอบฉาก ( ES6 ):

let clonedArray = [...array]

แต่ถ้าคุณต้องการโคลนแบบลึกให้ใช้สิ่งนี้แทน:

let cloneArray = JSON.parse(JSON.stringify(array))


สำหรับผู้ใช้ lodash:

let clonedArray = _.clone(array) เอกสาร

และ

let clonedArray = _.cloneDeep(array) เอกสาร


11

หากคุณอยู่ในสภาพแวดล้อมของECMAScript 6 การใช้Spread Operatorคุณสามารถทำได้ดังนี้:

var arr1 = ['a','b','c'];
var arr2 = [...arr1]; //copy arr1
arr2.push('d');

console.log(arr1)
console.log(arr2)
<script src="http://www.wzvang.com/snippet/ignore_this_file.js"></script>


9

การเพิ่มโซลูชันของarray.slice (); โปรดทราบว่าหากคุณมีอาร์เรย์ย่อยหลายมิติจะถูกคัดลอกโดยการอ้างอิง สิ่งที่คุณสามารถทำได้คือการวนและแบ่ง () แต่ละแถวย่อยแต่ละแถว

var arr = [[1,1,1],[2,2,2],[3,3,3]];
var arr2 = arr.slice();

arr2[0][1] = 55;
console.log(arr2[0][1]);
console.log(arr[0][1]);

function arrCpy(arrSrc, arrDis){
 for(elm in arrSrc){
  arrDis.push(arrSrc[elm].slice());
}
}

var arr3=[];
arrCpy(arr,arr3);

arr3[1][1] = 77;

console.log(arr3[1][1]);
console.log(arr[1][1]);

สิ่งเดียวกันไปที่อาร์เรย์ของวัตถุพวกเขาจะถูกคัดลอกโดยอ้างอิงคุณต้องคัดลอกด้วยตนเอง


คำตอบนี้สมควรได้รับจุดที่อยู่ด้านบนของหน้า! ฉันทำงานกับอาร์เรย์ย่อยหลายมิติและไม่สามารถติดตามได้ว่าทำไมอาร์เรย์ภายในถูกคัดลอกโดยอ้างอิงเสมอและไม่ใช่วาล ตรรกะง่ายๆนี้แก้ปัญหาของฉันได้ ฉันจะให้ +100 ถ้าเป็นไปได้!
Mac

8

ค่าดั้งเดิมจะส่งผ่านตามค่าของมันเสมอ (คัดลอก) อย่างไรก็ตามค่า Compound จะถูกส่งผ่านโดยการอ้างอิง

แล้วเราจะคัดลอก arr นี้ได้อย่างไร

let arr = [1,2,3,4,5];

คัดลอก Array ใน ES6

let arrCopy = [...arr]; 

คัดลอก n Array ใน ES5

let arrCopy = arr.slice(); 
let arrCopy = [].concat(arr);

ทำไม `ให้ arrCopy = arr` ไม่ผ่านตามตัวอักษร?

การส่งผ่านหนึ่ง varible ไปยังอีกค่า Compound เช่น Object / Array ทำงานแตกต่างกัน การใช้ตัวดำเนินการ asign กับค่า copand เราผ่านการอ้างอิงไปยังวัตถุ นี่คือเหตุผลที่ค่าของทั้งสองอาร์เรย์มีการเปลี่ยนแปลงเมื่อลบ / เพิ่มองค์ประกอบ arr

ข้อยกเว้น

arrCopy[1] = 'adding new value this way will unreference';

เมื่อคุณกำหนดค่าใหม่ให้กับตัวแปรคุณกำลังเปลี่ยนการอ้างอิงตัวเองและจะไม่มีผลกับ Object / Array ต้นฉบับ

อ่านเพิ่มเติม


6

อย่างที่เรารู้ใน Javascript อาร์เรย์และวัตถุโดยอ้างอิง แต่วิธีการที่เราสามารถคัดลอกอาร์เรย์โดยไม่ต้องเปลี่ยนอาร์เรย์เดิมในภายหลังได้อย่างไร

นี่คือวิธีที่จะทำ:

ลองนึกภาพเรามีอาร์เรย์นี้ในรหัสของคุณ:

var arr = [1, 2, 3, 4, 5];

1) วนรอบผ่านอาร์เรย์ในฟังก์ชันและส่งกลับอาร์เรย์ใหม่เช่นนี้

 function newArr(arr) {
      var i=0, res = [];
      while(i<arr.length){
       res.push(arr[i]);
        i++;
       }
   return res;
 }

2) ใช้วิธีการแบ่งส่วนสำหรับหั่นส่วนของอาร์เรย์มันจะฝานบางส่วนของอาร์เรย์ของคุณโดยไม่ต้องสัมผัสต้นฉบับในชิ้นถ้าไม่ได้ระบุจุดเริ่มต้นและจุดสิ้นสุดของอาร์เรย์มันจะฝานทั้งหมด อาเรย์และโดยทั่วไปจะทำสำเนาของอาเรย์ทั้งหมดดังนั้นเราสามารถพูดได้อย่างง่ายดาย:

var arr2 = arr.slice(); // make a copy of the original array

3) นอกจากนี้วิธีการติดต่อนี่คือการรวมสองอาร์เรย์ แต่เราสามารถระบุหนึ่งในอาร์เรย์แล้วทำสำเนาค่าในอาร์เรย์ที่ได้รับการติดต่อใหม่โดยทั่วไป:

var arr2 = arr.concat();

4) วิธี stringify และ parse ไม่แนะนำ แต่อาจเป็นวิธีที่ง่ายในการคัดลอก Array และ Objects:

var arr2 = JSON.parse(JSON.stringify(arr));

5) วิธี Array.from ไม่รองรับอย่างกว้างขวางก่อนใช้งานตรวจสอบการสนับสนุนในเบราว์เซอร์ต่างๆ:

const arr2 = Array.from(arr);

6) วิธี ECMA6, ยังไม่รองรับอย่างเต็มที่, แต่ babelJs สามารถช่วยคุณได้หากคุณต้องการ transpile:

const arr2 = [...arr];

6
let a = [1,2,3];

ตอนนี้คุณสามารถเลือกทำอย่างใดอย่างหนึ่งต่อไปนี้เพื่อสร้างสำเนาของอาร์เรย์

let b = Array.from(a); 

หรือ

let b = [...a];

หรือ

let b = new Array(...a); 

หรือ

let b = a.slice(); 

หรือ

let b = a.map(e => e);

ตอนนี้ถ้าฉันเปลี่ยน

a.push(5); 

จากนั้น a คือ [1,2,3,5] แต่ b ยังคง [1,2,3] เนื่องจากมีการอ้างอิงที่แตกต่างกัน

แต่ฉันคิดว่าในทุกวิธีข้างต้น Array.from ดีกว่าและทำเพื่อคัดลอกอาร์เรย์เป็นหลัก


1
อันไหนเร็วที่สุด?
Marc Frame


4

ในกรณีของฉันโดยเฉพาะฉันจำเป็นต้องตรวจสอบให้แน่ใจว่าอาร์เรย์ยังคงอยู่เหมือนเดิมดังนั้นจึงใช้ได้กับฉัน:

// Empty array
arr1.length = 0;
// Add items from source array to target array
for (var i = 0; i < arr2.length; i++) {
    arr1.push(arr2[i]);
}

2
+1 สำหรับการไม่เพิ่มความคลุมเครือให้กับรหัสของคุณโดยการเรียกใช้ฟังก์ชันที่ทำสิ่งเดียวกัน แต่มีความชัดเจนน้อยกว่า ชิ้นอาจมีประสิทธิภาพมากขึ้นภายใต้ประทุน แต่สำหรับทุกคนที่ทำงานกับรหัสนี้จะแสดงเจตนาของคุณ รวมทั้งทำให้การเพิ่มประสิทธิภาพในภายหลังง่ายขึ้นหากคุณต้องการกรอง (ตัวอย่าง) กรองสิ่งที่คุณกำลังคัดลอก โปรดทราบว่านี่ไม่ได้จัดการกับการทำสำเนาลึกและวัตถุภายในเดียวกันจะถูกส่งไปยังอาร์เรย์ใหม่โดยอ้างอิง นี่อาจเป็นสิ่งที่คุณต้องการทำมันอาจจะไม่
ไนซ์

4

ทำสำเนาอาร์เรย์ / วัตถุหลายมิติ:

function deepCopy(obj) {
   if (Object.prototype.toString.call(obj) === '[object Array]') {
      var out = [], i = 0, len = obj.length;
      for ( ; i < len; i++ ) {
         out[i] = arguments.callee(obj[i]);
      }
      return out;
   }
   if (typeof obj === 'object') {
      var out = {}, i;
      for ( i in obj ) {
         out[i] = arguments.callee(obj[i]);
      }
      return out;
   }
   return obj;
}

ขอบคุณ James Padolsey สำหรับฟังก์ชั่นนี้

ที่มา: ที่นี่


3

แดนไม่จำเป็นต้องใช้ลูกเล่นแฟนซี สิ่งที่คุณต้องทำคือทำสำเนา arr1 โดยทำสิ่งนี้

var arr2 = new Array(arr1);

ตอนนี้arr1และarr2เป็นตัวแปรอาเรย์สองตัวแปรที่จัดเก็บในสแต็กแยก ตรวจสอบเกี่ยวกับเรื่องนี้ jsfiddle


นี่ไม่ได้คัดลอกอาร์เรย์ มันสร้างอาร์เรย์ที่มีองค์ประกอบหนึ่งที่อ้างอิงต้นฉบับ (เช่นvar arr2 = [arr1];)
Timothy003

3

เมื่อเราต้องการคัดลอกอาเรย์โดยใช้โอเปอเรเตอร์การมอบหมาย ( =) มันไม่สร้างสำเนามันเพียงแค่คัดลอกพอยน์เตอร์ / อ้างอิงไปยังอาเรย์ ตัวอย่างเช่น:

const oldArr = [1,2,3];

const newArr = oldArr;  // now oldArr points to the same place in memory 

console.log(oldArr === newArr);  // Points to the same place in memory thus is true

const copy = [1,2,3];

console.log(copy === newArr);  // Doesn't point to the same place in memory and thus is false

บ่อยครั้งเมื่อเราแปลงข้อมูลที่เราต้องการเก็บโครงสร้างเริ่มต้นของเรา (เช่น Array) เหมือนเดิม เราทำสิ่งนี้โดยการทำสำเนาอาร์เรย์ที่แน่นอนของเราเพื่อให้สามารถเปลี่ยนแปลงได้ในขณะที่ชุดเริ่มต้นยังคงเหมือนเดิม

วิธีการคัดลอกอาเรย์:

const oldArr = [1,2,3];

// Uses the spread operator to spread out old values into the new array literal
const newArr1 = [...oldArr];

// Slice with no arguments returns the newly copied Array
const newArr2 = oldArr.slice();

// Map applies the callback to every element in the array and returns a new array
const newArr3 = oldArr.map((el) => el);

// Concat is used to merge arrays and returns a new array. Concat with no args copies an array
const newArr4 = oldArr.concat();

// Object.assign can be used to transfer all the properties into a new array literal
const newArr5 = Object.assign([], oldArr);

// Creating via the Array constructor using the new keyword
const newArr6 = new Array(...oldArr);

// For loop
function clone(base) {
	const newArray = [];
    for(let i= 0; i < base.length; i++) {
	    newArray[i] = base[i];
	}
	return newArray;
}

const newArr7 = clone(oldArr);

console.log(newArr1, newArr2, newArr3, newArr4, newArr5, newArr6, newArr7);

ระวังเมื่ออาร์เรย์หรือวัตถุซ้อนกัน!:

เมื่ออาร์เรย์ซ้อนกันค่าจะถูกคัดลอกโดยการอ้างอิง นี่คือตัวอย่างของสิ่งที่จะนำไปสู่ปัญหา:

let arr1 = [1,2,[1,2,3]]

let arr2 = [...arr1];

arr2[2][0] = 5;  // we change arr2

console.log(arr1);  // arr1 is also changed because the array inside arr1 was copied by reference

ดังนั้นอย่าใช้วิธีการเหล่านี้เมื่อมีวัตถุหรืออาร์เรย์ภายในอาร์เรย์ที่คุณต้องการคัดลอก เช่นใช้วิธีการเหล่านี้ในอาร์เรย์ของดั้งเดิมเท่านั้น

หากคุณต้องการใช้อาร์เรย์ javascript JSON.parseร่วมกับJSON.stringifyเช่นนี้:

let arr1 = [1,2,[1,2,3]]

let arr2 = JSON.parse(JSON.stringify(arr1)) ;

arr2[2][0] = 5;

console.log(arr1);  // now I'm not modified because I'm a deep clone

ประสิทธิภาพการคัดลอก:

เราเลือกอันไหนเพื่อประสิทธิภาพที่ดีที่สุด ปรากฎว่าวิธีการ verbose ที่สุดforวนรอบมีประสิทธิภาพสูงสุด ใช้forวนซ้ำสำหรับการทำสำเนา CPU อย่างจริงจัง (อาร์เรย์ขนาดใหญ่ / มาก)

หลังจากนั้น.slice()วิธีการนี้ยังมีประสิทธิภาพที่ดีและยังมีความละเอียดที่น้อยลงและง่ายขึ้นสำหรับโปรแกรมเมอร์ที่จะใช้งาน ฉันแนะนำให้ใช้.slice()สำหรับการคัดลอกอาร์เรย์ประจำวันของคุณซึ่งใช้งาน CPU ไม่มาก นอกจากนี้หลีกเลี่ยงการใช้JSON.parse(JSON.stringify(arr))(จำนวนมากของค่าใช้จ่าย) หากไม่จำเป็นต้องทำการโคลนแบบลึกและประสิทธิภาพเป็นปัญหา

การทดสอบประสิทธิภาพที่มา


3

หากอาร์เรย์ของคุณมีองค์ประกอบของชนิดข้อมูลดั้งเดิมเช่นint, char หรือสตริง ฯลฯคุณสามารถใช้หนึ่งในวิธีการเหล่านั้นซึ่งส่งคืนสำเนาของอาร์เรย์เดิมเช่น. slice () หรือ. map () หรือผู้ประกอบการแพร่กระจาย ( ขอบคุณ ES6)

new_array = old_array.slice()

หรือ

new_array = old_array.map((elem) => elem)

หรือ

const new_array = new Array(...old_array);

แต่ถ้าอาร์เรย์ของคุณมีองค์ประกอบที่ซับซ้อนเช่นวัตถุ (หรืออาร์เรย์) หรือวัตถุซ้อนกันมากขึ้นคุณจะต้องตรวจสอบให้แน่ใจว่าคุณกำลังทำสำเนาองค์ประกอบทั้งหมดจากระดับบนสุดไปจนถึงระดับสุดท้าย วัตถุจะถูกใช้และนั่นหมายถึงการเปลี่ยนค่าใน object_elements ใน new_array จะยังคงส่งผลกระทบต่อ old_array คุณสามารถเรียกวิธีการคัดลอกนี้ในแต่ละระดับเพื่อทำDEEP COPY ของ old_array

สำหรับการทำสำเนาลึกคุณสามารถใช้วิธีการดังกล่าวข้างต้นสำหรับชนิดข้อมูลดั้งเดิมในแต่ละระดับขึ้นอยู่กับชนิดของข้อมูลหรือคุณสามารถใช้วิธีการที่มีราคาแพง (กล่าวถึงด้านล่าง)สำหรับการทำสำเนาลึก

var new_array = JSON.parse(JSON.stringify(old_array));

มีวิธีการอื่นอีกมากมายที่คุณสามารถใช้ได้โดยขึ้นอยู่กับความต้องการของคุณ ผมได้กล่าวถึงเพียงบางส่วนของผู้ที่ให้ความคิดทั่วไปของสิ่งที่เกิดขึ้นเมื่อเราพยายามที่จะคัดลอกอาร์เรย์ในอื่น ๆด้วยค่า


ขอบคุณมากคำตอบของคุณคือคนเดียวที่ทำงานในสถานการณ์ของฉัน
อัลเบิร์ต sh

2

หากคุณต้องการทำสำเนาใหม่ของวัตถุหรืออาร์เรย์คุณต้องคัดลอกคุณสมบัติของวัตถุหรือองค์ประกอบของอาร์เรย์อย่างชัดเจน:

var arr1 = ['a','b','c'];
var arr2 = [];

for (var i=0; i < arr1.length; i++) {
   arr2[i] = arr1[i];
}

คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับ Google เกี่ยวกับค่าดั้งเดิมที่ไม่เปลี่ยนรูปแบบและการอ้างอิงวัตถุที่ไม่แน่นอน


1
คุณไม่จำเป็นต้องคัดลอกคุณสมบัติของออบเจ็กต์ของอาเรย์อย่างชัดเจน ดูคำตอบของ Chtiwi Malek
Magne





2

ตัวอย่างด่วน:

  1. ถ้าองค์ประกอบในอาร์เรย์เป็นชนิดดั้งเดิม (สตริง, จำนวน, ฯลฯ )

var arr1 = ['a','b','c'];
// arr1 and arr2 are independent and primitive elements are stored in 
// different places in the memory
var arr2 = arr1.slice(); 
arr2.push('d');
console.log(arr1); // [ 'a', 'b', 'c' ]
console.log(arr2); // [ 'a', 'b', 'c', 'd' ]

  1. หากองค์ประกอบในอาร์เรย์เป็นวัตถุตามตัวอักษรอาร์เรย์อื่น ({}, [])

var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];
// arr1 and arr2 are independent and reference's/addresses are stored in different
// places in the memory. But those reference's/addresses points to some common place
// in the memory.
var arr2 = arr1.slice(); 
arr2.pop();      // OK - don't affect arr1 bcos only the address in the arr2 is
                 // deleted not the data pointed by that address
arr2[0].x = 'z'; // not OK - affect arr1 bcos changes made in the common area 
                 // pointed by the addresses in both arr1 and arr2
arr2[1][0] = 9;	 // not OK - same above reason

console.log(arr1); // [ { x: 'z', y: 'b' }, [ 9, 2 ], [ 3, 4 ] ]
console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]

  1. โซลูชันสำหรับ 2 : การคัดลอกลึกโดยองค์ประกอบโดยองค์ประกอบ

var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];
arr2 = JSON.parse(JSON.stringify(arr1));
arr2.pop();	  // OK - don't affect arr1
arr2[0].x = 'z';  // OK - don't affect arr1
arr2[1][0] = 9;	  // OK - don't affect arr1

console.log(arr1); // [ { x: 'a', y: 'b' }, [ 1, 2 ], [ 3, 4 ] ]
console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]


1

นี่คือตัวแปร:

var arr1=['a', 'b', 'c'];
var arr2=eval(arr1.toSource());
arr2.push('d');
console.log('arr1: '+arr1+'\narr2: '+arr2);
/*
 *  arr1: a,b,c
 *  arr2: a,b,c,d
 */

ไม่ใช่ความคิดที่ไม่ดี แต่ฉันควรใช้ JSON stringify / parse แทน eval และเปรียบเทียบ jsPerf ตัวอื่นจะดีกว่าที่จะลองดูนอกจากนี้หมายเหตุยังtoSourceไม่ได้มาตรฐานและจะไม่ทำงานใน Chrome เช่น
dmi3y

1

มีการแนะนำใหม่Array.fromแต่น่าเสียดายที่ในช่วงเวลาของการเขียนนี้ได้รับการสนับสนุนเฉพาะใน Firefox รุ่นล่าสุด (32 และสูงกว่า) สามารถใช้งานได้ง่ายดังต่อไปนี้:

var arr1 = [1, 2, 3];
console.log(Array.from(arr1)); // Logs: [1, 2, 3]

อ้างอิง: ที่นี่

หรือArray.prototype.mapอาจใช้กับฟังก์ชันข้อมูลประจำตัว:

function identity(param)
{
    return param;
}

var arr1 = [1, 2, 3],
    clone = arr1.map(identity);

อ้างอิง: ที่นี่


+1 สำหรับการกล่าวถึงArray.fromซึ่งขณะนี้รองรับเบราว์เซอร์หลักทั้งหมดยกเว้น Internet Explorer ( แหล่งที่มา ) .
mgthomas99

โปรดระวังArray.fromจะทำให้ข้อมูลกลายพันธุ์ไม่ได้มีการคัดลอกลึก / การโคลนนิ่งลึก
Sudhansu Choudhary

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.