คุณโคลน Array of Objects ใน Javascript ได้อย่างไร


421

... ที่แต่ละวัตถุมีการอ้างอิงถึงวัตถุอื่นภายในอาเรย์เดียวกันหรือไม่

เมื่อฉันแรกเกิดขึ้นกับปัญหานี้ฉันแค่คิดอะไรบางอย่างเช่น

var clonedNodesArray = nodesArray.clone()

จะมีอยู่และค้นหาข้อมูลเกี่ยวกับวิธีการโคลนวัตถุใน javascript ฉันหาคำถามเกี่ยวกับ StackOverflow (ตอบโดย @JohnResig เดียวกัน) และเขาชี้ให้เห็นว่าด้วย jQuery คุณสามารถทำได้

var clonedNodesArray = jQuery.extend({}, nodesArray);

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

nodesArray[0].value = "red"
clonedNodesArray[0].value = "green"

ค่าของ nodesArray ทั้งสอง [0] และ clonedNodesArray [0] จะกลายเป็น "สีเขียว" จากนั้นฉันก็ลอง

var clonedNodesArray = jQuery.extend(true, {}, nodesArray);

ซึ่งลึกลงไปคัดลอก Object แต่ฉันได้รับข้อความ " เรียกซ้ำมากเกินไป " และ " control stack overflow " จาก Firebug และ Opera Dragonfly ตามลำดับ

คุณจะทำอย่างไร นี่เป็นสิ่งที่ไม่ควรทำใช่ไหม มีวิธีนำมาใช้ซ้ำได้ใน Javascript หรือไม่

คำตอบ:


106

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

สาเหตุที่สำเนาลึกของคุณมีปัญหาอยู่เนื่องจากคุณกำลังลงท้ายด้วยการอ้างอิงวัตถุแบบวงกลม Deep จะลึกเท่าที่จะทำได้และถ้าคุณมีวงกลมมันจะไปเรื่อย ๆ จนกระทั่งเบราว์เซอร์เป็นลม

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

หากคุณต้องการสำเนาของ Array of Objects ที่มีการอ้างอิงแบบวงกลมฉันเชื่อว่าคุณจะต้องเขียนโค้ดวิธีการของคุณเองเพื่อจัดการโครงสร้างข้อมูลพิเศษของคุณเช่นว่ามันเป็นโคลนแบบมัลติพาส:

  1. ในรอบที่หนึ่งทำโคลนของวัตถุทั้งหมดที่ไม่ได้อ้างอิงวัตถุอื่น ๆ ในอาร์เรย์ ติดตามจุดกำเนิดของวัตถุแต่ละชิ้น
  2. ในรอบสองเชื่อมโยงวัตถุเข้าด้วยกัน

1
ลิงก์ถาวรสำหรับ @PatrickdeKleijn คำตอบ: web.archive.org/web/20140222022056/http://my.opera.com/…
Mike Szyndel

531

ตราบใดที่ออบเจ็กต์ของคุณมีเนื้อหาที่ปรับให้เป็นแบบ JSON (ไม่มีฟังก์ชั่นไม่Number.POSITIVE_INFINITYฯลฯ ) ก็ไม่จำเป็นต้องมีลูปในการโคลนอาร์เรย์หรือวัตถุ นี่คือวิธีการแก้ปัญหาแบบบรรทัดเดียววานิลลาบริสุทธิ์

var clonedArray = JSON.parse(JSON.stringify(nodesArray))

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


118
สิ่งนี้อาจใช้งานได้กับข้อมูล JSON แต่ถ้าอาร์เรย์ของคุณมีฟังก์ชั่นหรืออินสแตนซ์ของวัตถุที่มีวิธีการบอกลาพวกเขา
sp0rkyd0rky

12
ระวังถ้าคุณมีอาร์เรย์ที่มีค่าอินฟินิตี้ ค่านี้จะสูญหาย (เป็นค่าว่างหลังจากนั้น) ( jsfiddle.net/klickagent/ehm4bd3s )
klickagent.ch

13
นี่เป็นเพียงวิธีการที่ไม่ดีเว้นแต่อาร์เรย์ของคุณจะมีเพียงข้อมูลพื้นฐานเท่านั้นและ / หรือวัตถุที่มีตัวอักษรดั้งเดิมเท่านั้นที่เป็นสตริง / หมายเลข / บูลีน (แม้nullและundefinedจะมีปัญหาเนื่องจาก JSON ไม่สนับสนุนพวกเขา) นอกจากนี้ยังเป็นการทำงานที่มีประสิทธิภาพน้อยกว่าold_array.slice(0);ซึ่งควรจะทำงานได้ดีขึ้นและเร็วขึ้น
XML

2
ถ้า object of array มี DateTime สตริงจะถูกส่งคืนแทน DateTime! new Date! == JSON.parse (JSON.stringify (วันที่ใหม่))
MarkosyanArtur

2
กุญแจสำคัญในคำถามของ OP ซึ่งคำตอบนี้ไม่สนใจทั้งหมด: ... ที่แต่ละวัตถุยังมีการอ้างอิงถึงวัตถุอื่นภายในอาเรย์เดียวกัน?
XML

288

ฉันแก้ไขการโคลนอาร์เรย์ของวัตถุด้วยObject.assign

const newArray = myArray.map(a => Object.assign({}, a));

หรือแม้แต่สั้นลงด้วยไวยากรณ์การแพร่กระจาย

const newArray = myArray.map(a => ({...a}));

15
แต่ถ้า myArray มี Dinosaurs อยู่จำนวนหนึ่ง NewArray จะมี Objects จำนวนมาก งั้นคุณไม่เห็นด้วยเหรอ?
Matthew James Davis

3
วิธีที่ดีที่สุดในขณะที่มันช่วยให้วัตถุทำงานได้อย่างมีชีวิตชีวาแล้วจึงทำให้พวกมันหายไปด้วย JSON.parse (JSON.stringify (nodesArray))
scipper

14
@MatthewJamesDavis คุณสามารถแก้ปัญหานี้โดยการแทนที่ด้วย{} new Dinosaur()
Agargara

5
สำเนาตื้นไม่คัดลอกลึก
สุลต่าน

1
มันใช้งานได้ดีสำหรับอาเรย์ของวัตถุหากวัตถุเหล่านั้นมีคุณสมบัติดั้งเดิมเท่านั้น ... ซึ่งเป็นสิ่งที่ฉันต้องการขอบคุณ
โมฮาวี

154

หากสิ่งที่คุณต้องการคือสำเนาที่ตื้นวิธีง่าย ๆ คือ:

new_array = old_array.slice(0);

6
ฉันไม่คิดว่าคุณจะต้องผ่าน0คุณสามารถโทรหา.slice()อย่างน้อยก็ในโครมได้เลย
เลฟ

112
มันใช้งานไม่ได้จริงเหรอ? ฉันหมายความว่ามันไม่ใช่คำตอบสำหรับคำถามที่ว่าจะโคลนอาร์เรย์ของวัตถุได้อย่างไร นี่เป็นวิธีการโคลนอาร์เรย์แบบง่าย ๆ
bozdoz

35
อันที่จริงสิ่งนี้จะไม่ทำงานสำหรับอาร์เรย์วัตถุ อาร์เรย์ที่ส่งคืนโดยsliceจะเป็นอาร์เรย์ใหม่ แต่จะมีการอ้างอิงไปยังวัตถุอาร์เรย์เดิม
Sergio A.

4
สิ่งนี้จะใช้ได้เฉพาะกับ "generics" int สตริง ฯลฯ แต่ไม่ใช่สำหรับอาร์เรย์ของวัตถุ
Stefan Michev

5
สำหรับอาร์เรย์ของออบเจ็กต์ที่ไม่ได้คัดลอกจริงการอัปเดตเป็น new_array จะอัปเดต old_array ด้วย
Anas

44

วิธีที่ดีที่สุดและทันสมัยที่สุดในการทำโคลนนี้มีดังนี้:

การใช้ตัว...ดำเนินการกระจาย ES6

นี่คือตัวอย่างที่ง่ายที่สุด:

var clonedObjArray = [...oldObjArray];

วิธีนี้เราจะกระจายอาเรย์เป็นค่าแต่ละค่าและใส่ในอาเรย์ใหม่ด้วยตัวดำเนินการ []

นี่คือตัวอย่างอีกต่อไปที่แสดงวิธีการทำงานต่าง ๆ

let objArray = [ {a:1} , {b:2} ];

let refArray = objArray; // this will just point to the objArray
let clonedArray = [...objArray]; // will clone the array

console.log( "before:" );
console.log( "obj array" , objArray );
console.log( "ref array" , refArray );
console.log( "cloned array" , clonedArray );

objArray[0] = {c:3};

console.log( "after:" );
console.log( "obj array" , objArray ); // [ {c:3} , {b:2} ]
console.log( "ref array" , refArray ); // [ {c:3} , {b:2} ]
console.log( "cloned array" , clonedArray ); // [ {a:1} , {b:2} ]


2
คำตอบที่ทันสมัยดีไม่สามารถใช้ได้กับเบราว์เซอร์รุ่นเก่า (เช่น IE 11)
Jealie

1
@Jealie ฉันจะเดาว่า KingpinEX กำลังตั้งเป้าหมายคำตอบนี้ไว้สำหรับคนที่เปลี่ยน es6 ไปยังสิ่งที่มีประโยชน์กับ Babel หรือคุณมีอะไรบ้าง
ruffin

61
นี่เป็นเพียงการคัดลอกอาเรย์ไม่ใช่วัตถุแต่ละอย่างในอาเรย์
Toivo Säwén

31
หากต้องการติดตามสิ่งที่ @ ToivoSäwénพูดสิ่งนี้จะไม่คัดลอกวัตถุในอาร์เรย์อย่างลึกซึ้ง มันจะยังคงอ้างอิงออบเจ็กต์ดั้งเดิมดังนั้นหากคุณทำการเปลี่ยนแปลงมันจะส่งผลต่ออาร์เรย์เดิมด้วย
Joel Kinzel

3
มันใช้งานได้กับระบบพื้นฐานเท่านั้น ลองนี้: objArray [0] .a = 3; และคุณจะเห็นการอ้างอิงของวัตถุยังคงเหมือนเดิมใน clonedArray
Sergio Correa

25

สิ่งนี้ใช้ได้กับฉัน:

var clonedArray = $.map(originalArray, function (obj) {
                      return $.extend({}, obj);
                  });

และถ้าคุณต้องการสำเนาวัตถุในอาร์เรย์:

var clonedArray = $.map(originalArray, function (obj) {
                      return $.extend(true, {}, obj);
                  });

1
ดูเหมือนว่าจะใช้งานได้ ฉันพยายามหลีกเลี่ยงการใช้ jQuery อย่างกว้างขวางดังนั้นฉันจะไม่ใช้มันในสถานการณ์ของฉัน แต่สำหรับลูปและสำหรับ ... ในจะทำงานได้
bozdoz

19
$.evalJSON($.toJSON(origArray));

2
คุณจะต้องใช้ปลั๊กอิน jquery json เพื่อใช้code.google.com/p/jquery-json
wmitchell

32
ไม่มี JQ (ใช้ได้กับเบราว์เซอร์รุ่นใหม่):JSON.parse(JSON.stringify(origArray));
forresto

ฉันพบความคิดเห็นนี้มีประโยชน์ ในการใช้งานของฉันฉันต้องทำสำเนาของอาร์เรย์ของวัตถุที่มีคุณสมบัติที่สามารถสังเกตเห็นได้ KnockoutJS การคัดลอกต้องการเพียงค่าเท่านั้นไม่ใช่การสังเกตได้ หากต้องการทำสำเนาเพียงแค่ค่าฉันใช้ JSON.parse (ko.toJSON (origArray)) หรือ ko.utils.parseJson (ko.toJSON (origArray)) เพียงแค่ 2 เซ็นต์ของฉันและขอขอบคุณที่ช่วยให้ฉันมาถึงทางออกของฉัน
wavedrop

6
JSON.parse(JSON.stringify(origArray));เป็นทางออกที่ง่ายที่สุดแน่นอน
yorkw

jQuery มักไม่จำเป็น youmightnotneedjquery.com
ADJenks

9

แผนที่จะสร้างอาร์เรย์ใหม่จากอันเก่า (โดยไม่มีการอ้างอิงไปยังอันเก่า) และภายในแผนที่ที่คุณสร้างวัตถุใหม่และวนซ้ำคุณสมบัติ (คีย์) และกำหนดค่าจากออบเจ็กต์ Array เก่าไปยังคุณสมบัติที่สอดคล้องกับวัตถุใหม่

สิ่งนี้จะสร้างอาร์เรย์ของวัตถุเดียวกัน

let newArray = oldArray.map(a => {
               let newObject = {};
               Object.keys(a).forEach(propertyKey => {
                    newObject[propertyKey] = a[propertyKey];
               });
               return newObject ;
});

8

ฉันอาจมีวิธีง่ายๆในการทำสิ่งนี้โดยไม่ต้องเรียกซ้ำแบบเจ็บปวดและไม่ทราบรายละเอียดทั้งหมดของวัตถุที่เป็นปัญหา การใช้ jQuery เพียงแปลงออบเจ็กต์ของคุณเป็น JSON โดยใช้ jQuery $.toJSON(myObjectArray)จากนั้นนำสตริง JSON ของคุณและประเมินกลับเป็นออบเจ็กต์ BAM! ทำแล้วเสร็จ! แก้ไขปัญหา. :)

var oldObjArray = [{ Something: 'blah', Cool: true }];
var newObjArray = eval($.toJSON(oldObjArray));

21
เบราว์เซอร์ที่ทันสมัยบางแห่งมีวิธีการ JSON ในตัวดังนั้นคุณสามารถทำได้: JSON.parse (JSON.stringify (MY_ARRAY)) ซึ่งควรจะเร็วกว่า คำแนะนำที่ดี
Nicolas R

1
และถ้าพวกเขาไม่ได้ใช้json2evalไม่
kamranicus

มีประสิทธิภาพที่แย่มาก แต่น่าเสียดายที่เป็นคำตอบที่ดีที่สุดที่ฉันเคยเห็น: /
Dvid Silva

อย่าเปิดเผยข้อมูลใด ๆ กับข้อมูลผู้ใช้ ไม่ควรใช้งานeval()ทุกขนาด มันเสี่ยงต่อความปลอดภัย
ADJenks

8

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

function deepCopy (arr) {
    var out = [];
    for (var i = 0, len = arr.length; i < len; i++) {
        var item = arr[i];
        var obj = {};
        for (var k in item) {
            obj[k] = item[k];
        }
        out.push(obj);
    }
    return out;
}

// test case

var original = [
    {'a' : 1},
    {'b' : 2}
    ];

var copy = deepCopy(original);

// change value in copy
copy[0]['a'] = 'not 1';

// original[0]['a'] still equals 1

โซลูชันนี้วนซ้ำค่าอาร์เรย์จากนั้นทำซ้ำคีย์วัตถุบันทึกหลังไปยังวัตถุใหม่แล้วผลักวัตถุใหม่นั้นไปยังอาร์เรย์ใหม่

ดูjsfiddle หมายเหตุ: ง่าย ๆ.slice()หรือ[].concat()ไม่เพียงพอสำหรับวัตถุภายในอาร์เรย์


ขอบคุณสำหรับคำตอบ แต่คุณควรเน้นข้อบกพร่องของคำตอบ มันไม่ทำงานเมื่อวัตถุมีวัตถุอยู่ ..
Harsh

มันจะสร้างสำเนาตื้น ไม่ลึก
สุลต่าน

คุณต้องเพิ่มการสอบถามซ้ำบางแห่ง
DGoiko

6

JQuery ส่วนขยายทำงานได้ดีเพียงคุณต้องระบุว่าคุณโคลนอาร์เรย์มากกว่าวัตถุ ( หมายเหตุ [] แทน {} เป็นพารามิเตอร์ของวิธีการขยาย ):

var clonedNodesArray = jQuery.extend([], nodesArray);

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

1
หลังจากเปลี่ยนวัตถุในอาร์เรย์แรกวัตถุในอาร์เรย์ที่สองจะได้รับการแก้ไขดังนั้นจึงไม่เป็นไร
Spikolynn

6

วิธีนี้ง่ายมากและคุณสามารถแก้ไขโคลนของคุณโดยไม่ต้องปรับเปลี่ยนอาร์เรย์เดิม

// Original Array
let array = [{name: 'Rafael'}, {name: 'Matheus'}];

// Cloning Array
let clone = array.map(a => {return {...a}})

// Editing the cloned array
clone[1].name = 'Carlos';


console.log('array', array)
// [{name: 'Rafael'}, {name: 'Matheus'}]

console.log('clone', clone)
// [{name: 'Rafael'}, {name: 'Carlos'}]


1
นี้ไม่ได้สำเนาตื้นที่สองระดับลึกในขณะที่[...oldArray]และoldArray.slice(0)ทำตื้นคัดลอกลึกระดับหนึ่ง ดังนั้นมันจึงมีประโยชน์มาก แต่ไม่ใช่โคลนแบบเต็มจริง
Ben Wheeler

โคลนนิ่งลึกที่แท้จริงสามารถทำได้โดยใช้lodash.clonedeepจาก npm
รู้สึก

5

ดังที่ Daniel Lew พูดถึงกราฟวงกลมมีปัญหาบางอย่าง หากฉันมีปัญหานี้ฉันจะเพิ่มclone()วิธีพิเศษในวัตถุที่มีปัญหาหรือจำวัตถุที่ฉันได้คัดลอกไปแล้ว

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

ยังมีปัญหาหนึ่ง: หน่วยความจำ หากคุณมีการอ้างอิงนี้จากวัตถุหนึ่งไปยังอีกวัตถุหนึ่งอาจเป็นไปได้ว่าเบราว์เซอร์ไม่สามารถทำให้วัตถุเหล่านั้นว่างได้เนื่องจากพวกมันถูกอ้างอิงจากที่อื่นเสมอ คุณต้องผ่านครั้งที่สองที่คุณตั้งค่าการอ้างอิงการคัดลอกทั้งหมดเป็น Null (ถ้าคุณทำเช่นนี้คุณไม่จำเป็นต้องมีcopyCountแต่บูลีนisCopiedจะเพียงพอเนื่องจากคุณสามารถรีเซ็ตค่าในรอบที่สอง)


4

Array.slice สามารถใช้เพื่อคัดลอกอาเรย์หรือส่วนหนึ่งของอาเรย์ .. http://www.devguru.com/Technologies/Ecmascript/Quickref/Slice.html สิ่งนี้จะใช้ได้กับสตริงและตัวเลข .. - การเปลี่ยนสตริงใน อาร์เรย์หนึ่งจะไม่ส่งผลกระทบต่ออีก - วัตถุยังคงเพียงคัดลอกโดยอ้างอิงดังนั้นการเปลี่ยนแปลงวัตถุอ้างอิงในหนึ่งอาร์เรย์จะมีผลกระทบกับอาร์เรย์อื่น

นี่คือตัวอย่างของตัวจัดการการเลิกทำ JavaScript ที่อาจเป็นประโยชน์สำหรับสิ่งนี้: http://www.ridgway.co.za/archive/2007/11/07/simple-javascript-undo-manager-for-dtos.aspx


ฉันรู้ว่า. เหตุผลที่ฉันต้องการใช้สิ่งนี้เป็นเพราะฉันพยายามแก้ไขปัญหา CSP ด้วยการย้อนรอย ฉันคิดว่าวิธีหนึ่งในการนำ backtracking มาใช้อาจเป็นเหมือน "การถ่ายภาพสแนปชอต" สถานะของการกำหนดตัวแปรโดย ... การโคลนสแนปชอตดังกล่าวลงในสแต็ก
wallyqs

... และก็อาจเป็นความคิดที่แย่มาก
wallyqs

วิธีการดังกล่าวอาจมีภาวะแทรกซ้อนการซิงโครไนซ์อื่น ๆ :) .. คุณจะรู้ได้อย่างไรว่าอาเรย์จะไม่เปลี่ยนไปในขณะที่คุณกำลังถ่ายภาพ
markt

เพิ่มลิงค์ไปยังบทความที่ผู้เขียนใช้งานตัวจัดการเลิกทำอย่างง่ายโดยใช้ javascript ..
markt

4

แนวทางของฉัน:

var temp = { arr : originalArray };
var obj = $.extend(true, {}, temp);
return obj.arr;

ให้ฉันดีสะอาดโคลนลึกของอาร์เรย์เดิม - ไม่มีวัตถุอ้างอิงถึงกลับไปที่เดิม :-)


นี่เป็นทางออกที่ดีที่สุดในการใช้ jquery สั้นและหวาน
John Henckel

1
ฉันทำการทดสอบประสิทธิภาพและวิธีนี้ดูเหมือนว่าจะเร็วกว่า 2x2 เท่าของโซลูชัน JSON.stringify
meehocz

4

lodash มีcloneDeepฟังก์ชั่นสำหรับวัตถุประสงค์นี้:

var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);

4

หากคุณต้องการที่จะใช้โคลนลึกใช้ JSON.parse (JSON.stringify ({} หรือ []))

const myObj ={
    a:1,
    b:2,
    b:3
}

const deepClone=JSON.parse(JSON.stringify(myObj));
deepClone.a =12;
console.log("deepClone-----"+myObj.a);
const withOutDeepClone=myObj;
withOutDeepClone.a =12;
console.log("withOutDeepClone----"+myObj.a);


3

forget eval () (เป็นคุณลักษณะที่ใช้ในทางที่ผิดมากที่สุดของ JS และทำให้โค้ดทำงานช้า) และ slice (0) (ใช้ได้กับประเภทข้อมูลอย่างง่ายเท่านั้น)

นี่เป็นทางออกที่ดีที่สุดสำหรับฉัน:

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

3

ฉันค่อนข้างผิดหวังกับปัญหานี้ เห็นได้ชัดว่าปัญหาเกิดขึ้นเมื่อคุณส่ง Array สามัญไปยังวิธี $ .extend ดังนั้นเพื่อแก้ไขฉันเพิ่มการตรวจสอบเล็กน้อยและทำงานได้อย่างสมบูรณ์กับอาร์เรย์ทั่วไป, jQuery arrays และวัตถุใด ๆ

jQuery.extend({
    deepclone: function(objThing) {
        // return jQuery.extend(true, {}, objThing);
        /// Fix for arrays, without this, arrays passed in are returned as OBJECTS! WTF?!?!
        if ( jQuery.isArray(objThing) ) {
            return jQuery.makeArray( jQuery.deepclone($(objThing)) );
        }
        return jQuery.extend(true, {}, objThing);
    },
});

เรียกใช้โดย:

var arrNewArrayClone = jQuery.deepclone(arrOriginalArray);
// Or more simply/commonly
var arrNewArrayClone = $.deepclone(arrOriginalArray);

deepclone? ฉันใช้ jquery-1.9.1 และมันไม่รองรับวิธีนี้ มันเป็นรุ่นที่ทันสมัยกว่านี้ไหม?
user5260143

@ user2783091 เขากำลังขยาย JQuery เพื่อเพิ่มฟังก์ชันนั้น ไม่ใช่สิ่งที่ออกมาจากกล่อง
JorgeeFG

3

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

function deepClone (item) {
  if (Array.isArray(item)) {
    var newArr = [];
    for (var i = item.length; i-- > 0;) {
      newArr[i] = deepClone(item[i]);
    }
    return newArr;
  }
  if (typeof item === 'function' && !(/\(\) \{ \[native/).test(item.toString())) {
    var obj;
    eval('obj = '+ item.toString());
    for (var k in item) {
      obj[k] = deepClone(item[k]);
    }
    return obj;
  }
  if (item && typeof item === 'object') {
    var obj = {};
    for (var k in item) {
      obj[k] = deepClone(item[k]);
    }
    return obj;
  }
  return item;
}

3

ฉันใช้วิธี ECMAScript 6 Object.assignใหม่:

let oldObject = [1,3,5,"test"];
let newObject = Object.assign({}, oldObject);

อาร์กิวเมนต์แรกของวิธีนี้คืออาร์เรย์ที่จะอัปเดตเราส่งผ่านวัตถุว่างเปล่าเพราะเราต้องการมีวัตถุใหม่

เราสามารถใช้ไวยากรณ์นี้ซึ่งเหมือนกัน แต่สั้นกว่า:

let newObject = [...oldObject];

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

2

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

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));


2

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

สำเนาลึกหมายถึงการสร้างอาร์เรย์ใหม่และคัดลอกค่าเนื่องจากสิ่งที่เกิดขึ้นกับมันจะไม่ส่งผลกระทบกับต้นกำเนิด

JSON.parseและJSON.stringifyเป็นวิธีที่ดีที่สุดและง่ายที่สุดในการทำสำเนาแบบลึก JSON.stringify()วิธีการแปลงค่า JavaScript กับ JSON สตริงJSON.parse()วิธีการแยกวิเคราะห์สตริง JSON ที่สร้างมูลค่า JavaScript หรือวัตถุอธิบายโดยสตริง

// Deep Clone

let a = [{ x:{z:1} , y: 2}];
let b = JSON.parse(JSON.stringify(a));
b[0].x.z=0

console.log(JSON.stringify(a)); //[{"x":{"z":1},"y":2}]
console.log(JSON.stringify(b)); // [{"x":{"z":0},"y":2}]

สำหรับรายละเอียดเพิ่มเติม: อ่านที่นี่


1
นี่คือทางออกที่ดีที่สุด ขอบคุณ.
Nikolay


1

รหัสต่อไปนี้จะทำการคัดลอกวัตถุและอาร์เรย์แบบวนซ้ำ:

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;
}

แหล่ง


arguments.calleeไม่สามารถใช้งานได้ในโหมดเข้มงวดและมีปัญหาด้านประสิทธิภาพเป็นอย่างอื่น
Brett Zamir

1

วิธีการที่ยอดเยี่ยมสำหรับการโคลนนิ่งใน javascript

https://mootools.net/core/docs/1.6.0/Types/Object

https://scotch.io/bar-talk/copying-objects-in-javascript

1) วิธีการวานิลลา Javascript สำหรับการโคลนวัตถุ

2) การใช้ประโยชน์จากไลบรารี JSON อย่างชาญฉลาดไปยังวัตถุโคลนนิ่งลึก

3) การใช้ฟังก์ชัน $ .extend () ของ jQuery

4) ใช้ฟังก์ชั่น Mootools 'clone () เพื่อโคลนวัตถุ


0

ฉันคิดว่าจัดการเขียนวิธีการทั่วไปของการโคลนโครงสร้าง JavaScript ใด ๆ ที่ใช้เป็นหลักObject.createซึ่งได้รับการสนับสนุนในเบราว์เซอร์ที่ทันสมัยทั้งหมด รหัสเป็นดังนี้:

function deepClone (item) {
  if (Array.isArray(item)) {
    var newArr = [];

    for (var i = item.length; i-- !== 0;) {
      newArr[i] = deepClone(item[i]);
    }

    return newArr;
  }
  else if (typeof item === 'function') {
    eval('var temp = '+ item.toString());
    return temp;
  }
  else if (typeof item === 'object')
    return Object.create(item);
  else
    return item;
}

Object.createจะถือว่าitemเป็นต้นแบบของวัตถุ แต่ที่แตกต่างจากการโคลน หากitemมีการแก้ไขการเปลี่ยนแปลงจะปรากฏใน "โคลน" และในทางกลับกัน วิธีการนี้ใช้ไม่ได้
Brett Zamir

0

สำหรับการโคลนวัตถุด้วยฉันจะแนะนำ ECMAScript 6 reduce():

const newArray=myArray.reduce((array, element)=>array.push(Object.assign({}, element)), []);

แต่จริงๆแล้วฉันชอบคำตอบของ @dinodsaurus มากกว่านี้ ฉันแค่วางรุ่นนี้ไว้ที่นี่เป็นตัวเลือกอื่น แต่โดยส่วนตัวแล้วฉันจะใช้map()ตามที่แนะนำโดย @dinodsaurus


0

ขึ้นอยู่กับว่าคุณมี Underscore หรือ Babel ที่นี่เป็นเกณฑ์มาตรฐานของวิธีการที่แตกต่างกันของการโคลนอาร์เรย์

https://jsperf.com/object-rest-spread-vs-clone/2

ดูเหมือนว่าบาเบลเร็วที่สุด

var x = babel({}, obj)

0
       var game_popularity = [
            { game: "fruit ninja", popularity: 78 },
            { game: "road runner", popularity: 20 },
            { game: "maze runner", popularity: 40 },
            { game: "ludo", popularity: 75 },
            { game: "temple runner", popularity: 86 }
        ];
        console.log("sorted original array before clonning");
        game_popularity.sort((a, b) => a.popularity < b.popularity);
        console.log(game_popularity);


        console.log("clone using object assign");
        const cl2 = game_popularity.map(a => Object.assign({}, a));
        cl2[1].game = "clash of titan";
        cl2.push({ game: "logan", popularity: 57 });
        console.log(cl2);


        //adding new array element doesnt reflect in original array
        console.log("clone using concat");
        var ph = []
        var cl = ph.concat(game_popularity);

        //copied by reference ?
        cl[0].game = "rise of civilization";

        game_popularity[0].game = 'ping me';
        cl.push({ game: "angry bird", popularity: 67 });
        console.log(cl);

        console.log("clone using ellipses");
        var cl3 = [...game_popularity];
        cl3.push({ game: "blue whale", popularity: 67 });
        cl3[2].game = "harry potter";
        console.log(cl3);

        console.log("clone using json.parse");
        var cl4 = JSON.parse(JSON.stringify(game_popularity));
        cl4.push({ game: "home alone", popularity: 87 });
        cl4[3].game ="lockhead martin";
        console.log(cl4);

        console.log("clone using Object.create");
        var cl5 = Array.from(Object.create(game_popularity));
        cl5.push({ game: "fish ville", popularity: 87 });
        cl5[3].game ="veto power";
        console.log(cl5);


        //array function
        console.log("sorted original array after clonning");
        game_popularity.sort((a, b) => a.popularity < b.popularity);
        console.log(game_popularity);


        console.log("Object.assign deep clone object array");
        console.log("json.parse deep clone object array");
        console.log("concat does not deep clone object array");
        console.log("ellipses does not deep clone object array");
        console.log("Object.create does not deep clone object array");


        Output:


        sorted original array before clonning
        [ { game: 'temple runner', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'ludo', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 } ]
        clone using object assign
        [ { game: 'temple runner', popularity: 86 },
        { game: 'clash of titan', popularity: 78 },
        { game: 'ludo', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'logan', popularity: 57 } ]
        clone using concat
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'ludo', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'angry bird', popularity: 67 } ]
        clone using ellipses
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'maze runner', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'blue whale', popularity: 67 } ]
        clone using json.parse
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'lockhead martin', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'home alone', popularity: 87 } ]
        clone using Object.create
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'veto power', popularity: 40 },
        { game: 'road runner', popularity: 20 },
        { game: 'fish ville', popularity: 87 } ]
        sorted original array after clonning
        [ { game: 'ping me', popularity: 86 },
        { game: 'fruit ninja', popularity: 78 },
        { game: 'harry potter', popularity: 75 },
        { game: 'veto power', popularity: 40 },
        { game: 'road runner', popularity: 20 } ]

        Object.assign deep clone object array
        json.parse deep clone object array
        concat does not deep clone object array
        ellipses does not deep clone object array
        Object.create does not deep clone object array
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.