Cách sao chép sâu trong javascript


105

Làm cách nào để bạn nhân bản sâu một đối tượng Javascript?

Tôi biết có nhiều chức năng khác nhau dựa trên các khuôn khổ như JSON.parse(JSON.stringify(o))$.extend(true, {}, o)nhưng tôi không muốn sử dụng một khuôn khổ như vậy.

Cách thanh lịch hoặc hiệu quả nhất để tạo một bản sao sâu là gì.

Chúng tôi quan tâm đến các trường hợp cạnh như mảng nhân bản. Không phá vỡ chuỗi nguyên mẫu, đối phó với tự tham chiếu.

Chúng tôi không quan tâm đến việc hỗ trợ sao chép các đối tượng DOM và like vì .cloneNodetồn tại vì lý do đó.

Vì tôi chủ yếu muốn sử dụng các bản sao sâu trong node.jsviệc sử dụng các tính năng ES5 của động cơ V8 có thể chấp nhận được.

[Biên tập]

Trước khi bất kỳ ai gợi ý, hãy để tôi đề cập đến có một sự khác biệt rõ ràng giữa việc tạo một bản sao bằng cách kế thừa nguyên mẫu từ đối tượng và nhân bản nó. Cái thứ nhất làm cho chuỗi nguyên mẫu trở nên lộn xộn.

[Chỉnh sửa thêm]

Sau khi đọc câu trả lời của bạn, tôi phát hiện ra rằng nhân bản toàn bộ vật thể là một trò chơi rất nguy hiểm và khó khăn. Lấy ví dụ về đối tượng dựa trên bao đóng sau

var o = (function() {
     var magic = 42;

     var magicContainer = function() {
          this.get = function() { return magic; };
          this.set = function(i) { magic = i; };
     }

      return new magicContainer;
}());

var n = clone(o); // how to implement clone to support closures

Có cách nào để viết một hàm clone sao chép đối tượng, có cùng trạng thái tại thời điểm sao chép nhưng không thể thay đổi trạng thái của onếu không viết trình phân tích cú pháp JS trong JS.

Không cần thế giới thực cho một chức năng như vậy nữa. Đây chỉ là sở thích học thuật.


2
Trước khi nó được đánh dấu là trùng lặp, tôi đã xem xét stackoverflow.com/questions/122102/… và không tìm thấy bất kỳ câu trả lời nào giải quyết tất cả các trường hợp cạnh.
Raynos

Không thể đạt được yêu cầu trong phần "chỉnh sửa thêm" nếu không có "sự trợ giúp" từ chính đối tượng, vì các biến private như vậy thực sự là private và do đó không thể truy cập được bởi một clonehàm chung . Đối tượng được đề cập nên trình bày clonephương pháp thiết kế riêng của nó .
trincot

Tôi đã đọc về điều này rất nhiều tối nay và trong số các tài nguyên tôi tìm thấy là bài đăng blog trông xấu xí này bao gồm một vài thủ thuật để truy cập vào thuật toán sao chép có cấu trúc trong trình duyệt: dassur.ma/things/deep-copy
Cat

Câu trả lời:


67

Nó thực sự phụ thuộc vào những gì bạn muốn sao chép. Đây có phải là một đối tượng JSON thực sự hay chỉ là bất kỳ đối tượng nào trong JavaScript? Nếu bạn muốn thực hiện bất kỳ bản sao nào, nó có thể khiến bạn gặp rắc rối. Rắc rối nào? Tôi sẽ giải thích nó dưới đây, nhưng trước tiên, một ví dụ mã sao chép các ký tự đối tượng, bất kỳ nguyên thủy nào, các mảng và các nút DOM.

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testing that this is DOM
            if (item.nodeType && typeof item.cloneNode == "function") {
                result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                if (item instanceof Date) {
                    result = new Date(item);
                } else {
                    // it is an object literal
                    result = {};
                    for (var i in item) {
                        result[i] = clone( item[i] );
                    }
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}

var copy = clone({
    one : {
        'one-one' : new String("hello"),
        'one-two' : [
            "one", "two", true, "four"
        ]
    },
    two : document.createElement("div"),
    three : [
        {
            name : "three-one",
            number : new Number("100"),
            obj : new function() {
                this.name = "Object test";
            }   
        }
    ]
})

Và bây giờ, hãy nói về những vấn đề bạn có thể gặp phải khi bắt đầu nhân bản các đối tượng THỰC. Bây giờ tôi đang nói về các đối tượng mà bạn tạo ra bằng cách làm những việc như

var User = function(){}
var newuser = new User();

Tất nhiên bạn có thể sao chép chúng, đó không phải là vấn đề, mọi đối tượng đều hiển thị thuộc tính của phương thức khởi tạo và bạn có thể sử dụng nó để sao chép các đối tượng, nhưng không phải lúc nào nó cũng hoạt động. Bạn cũng có thể làm đơn giản for invới đối tượng này, nhưng nó đi theo cùng một hướng - rắc rối. Tôi cũng đã bao gồm chức năng sao chép bên trong mã, nhưng nó bị loại trừ bởi if( false )tuyên bố.

Vì vậy, tại sao nhân bản có thể là một nỗi đau? Trước hết, mọi đối tượng / thể hiện có thể có một số trạng thái. Bạn không bao giờ có thể chắc chắn rằng các đối tượng của mình không có biến private, và nếu trường hợp này xảy ra, bằng cách nhân bản đối tượng, bạn chỉ cần phá vỡ trạng thái.

Hãy tưởng tượng không có trạng thái, điều đó tốt. Sau đó, chúng tôi vẫn có một vấn đề khác. Nhân bản thông qua phương thức "constructor" sẽ cho chúng ta một trở ngại khác. Đó là một phụ thuộc đối số. Bạn không bao giờ có thể chắc chắn rằng ai đó đã tạo ra đối tượng này, đã không làm, một số loại

new User({
   bike : someBikeInstance
});

Nếu đúng như vậy, bạn thật không may mắn, một sốBikeInstance có thể đã được tạo ra trong một ngữ cảnh nào đó và ngữ cảnh đó không được xác định cho phương thức nhân bản.

Vậy lam gi? Bạn vẫn có thể làmfor in giải pháp và xử lý các đối tượng như vậy giống như các ký tự đối tượng bình thường, nhưng có lẽ đó là ý tưởng không sao chép các đối tượng như vậy và chỉ chuyển tham chiếu của đối tượng này?

Một giải pháp khác là - bạn có thể đặt một quy ước rằng tất cả các đối tượng phải được nhân bản phải tự thực hiện phần này và cung cấp phương thức API thích hợp (như cloneObject). Điều gì đó cloneNodeđang làm cho DOM.

Bạn quyết định.


Tôi đã gặp phải trở ngại trong việc xử lý các đối tượng sử dụng bao đóng để tự ẩn trạng thái. Làm thế nào người ta có thể sao chép một đối tượng và toàn bộ trạng thái của nó nhưng vẫn đảm bảo rằng bản sao không thể tự thay đổi trạng thái của bản gốc. Một điểm couinter result = new item.constructor();không tốt là với hàm tạo và đối tượng item, bạn sẽ có thể RE bất kỳ tham số nào được truyền vào hàm tạo.
Raynos

7
@Raynos: nếu các đối tượng sử dụng bao đóng để ẩn trạng thái, thì bạn không thể sao chép chúng. Do đó thuật ngữ 'đóng cửa'. Như nemisj đã nói ở phần cuối, cách tốt nhất là triển khai phương thức API để nhân bản (hoặc tuần tự hóa / giải mã hóa) nếu đó là một tùy chọn.
Michiel Kalkman

@MichielKalkman Tôi đã có cảm giác đó là trường hợp. Mặc dù ai đó có thể đã có một giải pháp thực sự thông minh cho điều này.
Raynos

Tác dụng của là false && item.constructorgì? Điều đó không phải là ifvô ích sao?
Gabriel Petrovay

2
@GabrielPetrovay Điều đó iflà 'vô dụng' từ góc độ chức năng, bởi vì nó sẽ không bao giờ chạy, nhưng nó có mục đích học thuật là hiển thị một triển khai giả định mà người ta có thể cố gắng sử dụng, mà tác giả không khuyên vì lý do được giải thích sau. Vì vậy, có, nó sẽ kích hoạt elsemệnh đề mỗi khi điều kiện được đánh giá, và có lý do để mã ở đó.
Gui Imamura

154

Cách rất đơn giản, có thể quá đơn giản:

var cloned = JSON.parse(JSON.stringify(objectToClone));

12
Tuyệt vời trừ khi một giá trị đối tượng là một hàm, lúc đó bạn sẽ phải sử dụng một cái gì đó giống như câu trả lời được chấp nhận. Hoặc sử dụng một chức năng trợ giúp như cloneDeeptrong Lodash.
matthoiland

31
Nếu một giá trị đối tượng là một hàm, thì đối tượng đó không phải là JSON.
Jos de Jong

5
Trường hợp sử dụng nào có thể biện minh cho việc sao chép một hàm thay vì chỉ sử dụng nó?
G. Ghez

3
Nếu tôi nhớ không nhầm thì điều này cũng chuyển đổi ngày tháng thành chuỗi.
Peter

3
@ G.Ghez nếu bạn sao chép một đối tượng có chứa một hàm chức năng sẽ bị mất ..
Peter

38

Sự JSON.parse(JSON.stringify())kết hợp với các đối tượng Javascript sao chép sâu là một cách tấn công không hiệu quả, vì nó dành cho dữ liệu JSON. Nó không hỗ trợ các giá trị của undefinedhoặc function () {}và sẽ đơn giản bỏ qua chúng (hoặc nullchúng) khi "xâu chuỗi" (marshalling) đối tượng Javascript thành JSON.

Một giải pháp tốt hơn là sử dụng chức năng sao chép sâu. Hàm bên dưới sao chép sâu các đối tượng và không yêu cầu thư viện của bên thứ 3 (jQuery, LoDash, v.v.).

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

7
Ngoại trừ khi aObject (hoặc một đối tượng khác mà nó chứa) chứa một tham chiếu đến chính nó ... stackoverflow ™!
e2-e4

@ ringø - Bạn có thể cung cấp một số trường hợp thử nghiệm "tự tham khảo" không?
tfmontague

4
var o = { a:1, b:2 } ; o["oo"] = { c:3, m:o };
e2-e4

4
Tôi thích giải pháp này. Chỉ sửa chữa đối với tôi là đối phó với các giá trị null: bObject[k] = (v === null) ? null : (typeof v === "object") ? copy(v) : v;
David Kirkland

2
Chức năng này đơn giản, dễ hiểu và sẽ bắt được hầu hết các trường hợp. Trong thế giới JavaScript, điều đó hoàn hảo đến mức bạn có thể có được.
icc97,

21

Đây là một hàm ES6 cũng sẽ hoạt động cho các đối tượng có tham chiếu tuần hoàn:

function deepClone(obj, hash = new WeakMap()) {
    if (Object(obj) !== obj) return obj; // primitives
    if (hash.has(obj)) return hash.get(obj); // cyclic reference
    const result = obj instanceof Set ? new Set(obj) // See note about this!
                 : obj instanceof Map ? new Map(Array.from(obj, ([key, val]) => 
                                        [key, deepClone(val, hash)])) 
                 : obj instanceof Date ? new Date(obj)
                 : obj instanceof RegExp ? new RegExp(obj.source, obj.flags)
                 // ... add here any specific treatment for other classes ...
                 // and finally a catch-all:
                 : obj.constructor ? new obj.constructor() 
                 : Object.create(null);
    hash.set(obj, result);
    return Object.assign(result, ...Object.keys(obj).map(
        key => ({ [key]: deepClone(obj[key], hash) }) ));
}

// Sample data
var p = {
  data: 1,
  children: [{
    data: 2,
    parent: null
  }]
};
p.children[0].parent = p;

var q = deepClone(p);

console.log(q.children[0].parent.data); // 1

Lưu ý về Bộ và Bản đồ

Làm thế nào để xử lý các khóa của Bộ và Bản đồ còn gây tranh cãi: những khóa đó thường là nguyên thủy (trong trường hợp này thì không có gì phải bàn cãi), nhưng chúng cũng có thể là đối tượng. Trong trường hợp đó, câu hỏi trở thành: có nên sao chép các khóa đó không?

Người ta có thể lập luận rằng điều này nên được thực hiện, để nếu các đối tượng đó bị đột biến trong bản sao, các đối tượng trong bản gốc không bị ảnh hưởng, và ngược lại.

Mặt khác, người ta muốn rằng nếu Đặt / Ánh xạ hasmột khóa, điều này phải đúng trong cả bản gốc và bản sao - ít nhất là trước khi thực hiện bất kỳ thay đổi nào đối với một trong hai. Sẽ rất lạ nếu bản sao là một Tập hợp / Bản đồ có các khóa chưa từng xuất hiện trước đây (vì chúng được tạo ra trong quá trình sao chép): chắc chắn điều đó không hữu ích lắm cho bất kỳ mã nào cần biết liệu một đối tượng nhất định có phải là key trong Set / Map đó hay không.

Như bạn nhận thấy, tôi theo quan điểm thứ hai: các khóa của Bộ và Bản đồ là các giá trị (có thể là tham chiếu ) nên được giữ nguyên.

Những lựa chọn như vậy thường sẽ xuất hiện với các đối tượng khác (có thể tùy chỉnh). Không có giải pháp chung nào, vì phần lớn phụ thuộc vào cách đối tượng nhân bản dự kiến ​​sẽ hoạt động như thế nào trong trường hợp cụ thể của bạn.


không xử lý cập nhật và regexp
mkeremguc

1
@mkeremguc, cảm ơn bạn đã nhận xét. Tôi đã cập nhật mã để hỗ trợ ngày và regexp.
trincot

Giải pháp tốt đẹp bao gồm cả bản đồ. Chỉ thiếu hỗ trợ cho bộ es6 nhân bản.
Robert Biggs

Nếu tôi không nhầm, có lẽ bạn có thể thêm hỗ trợ cho Bộ với:if (object instanceof Set) Array.from(object, val => result.add(deepClone(val, hash)));
Robert Biggs

@RobertBiggs, đó là một khả năng, nhưng theo tôi nếu một Bộ có một khóa nào đó, thì điều đó cũng phải đúng trong phiên bản nhân bản của Bộ đó. Với mã đề xuất của bạn, điều này sẽ không đúng nếu các khóa là đối tượng. Do đó, tôi đề nghị không sao chép các phím - tôi thực sự nghĩ rằng sau đó nó sẽ hoạt động tốt hơn như mong đợi. Xem cập nhật câu trả lời của tôi về khía cạnh đó.
trincot

10

Các Underscore.js thư viện contrib thư viện có một chức năng gọi là ảnh chụp mà nhái sâu một đối tượng

đoạn mã từ nguồn:

snapshot: function(obj) {
  if(obj == null || typeof(obj) != 'object') {
    return obj;
  }

  var temp = new obj.constructor();

  for(var key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = _.snapshot(obj[key]);
    }
  }

  return temp;
}

khi thư viện được liên kết với dự án của bạn, hãy gọi hàm chỉ cần sử dụng

_.snapshot(object);

4
giải pháp tốt, chỉ cần một điểm cần nhớ: bản sao và bản gốc chia sẻ cùng một nguyên mẫu. nếu đó là vấn đề thì có thể thêm "temp .__ proto__ = .snapshot (obj .__ proto_ );" ngay trên "trở lại tạm thời", và để hỗ trợ xây dựng trong lớp học với tính chất đánh dấu là 'không enumerate' bạn có thể lặp trên getOwnPropertyNames () thay vì "for (var quan trọng trong obj)"
Ronen Ness

3

Đây là phương pháp nhân bản sâu mà mình sử dụng, mình nghĩ là Tuyệt vời, mong các bạn góp ý

function deepClone (obj) {
    var _out = new obj.constructor;

    var getType = function (n) {
        return Object.prototype.toString.call(n).slice(8, -1);
    }

    for (var _key in obj) {
        if (obj.hasOwnProperty(_key)) {
            _out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
        }
    }
    return _out;
}

3

Như những người khác đã lưu ý về điều này và các câu hỏi tương tự, sao chép một "đối tượng", theo nghĩa chung, là không rõ ràng trong JavaScript.

Tuy nhiên, có một lớp đối tượng, mà tôi gọi là đối tượng "dữ liệu", tức là những đối tượng được xây dựng đơn giản từ các { ... }ký tự và / hoặc các phép gán thuộc tính đơn giản hoặc được giải mã hóa từ JSON mà việc sao chép là hợp lý. Mới hôm nay, tôi muốn tăng giả tạo dữ liệu nhận được từ máy chủ lên 5 lần để kiểm tra điều gì xảy ra với một tập dữ liệu lớn, nhưng đối tượng (một mảng) và con của nó phải là các đối tượng riêng biệt để mọi thứ hoạt động chính xác. Nhân bản cho phép tôi làm điều này để nhân tập dữ liệu của mình:

return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));

Một nơi khác mà tôi thường kết thúc sao chép các đối tượng dữ liệu là để gửi dữ liệu trở lại máy chủ lưu trữ, nơi tôi muốn tách các trường trạng thái khỏi đối tượng trong mô hình dữ liệu trước khi gửi nó. Ví dụ: tôi có thể muốn tách tất cả các trường bắt đầu bằng "_" khỏi đối tượng khi nó được nhân bản.

Đây là đoạn mã tôi đã viết để thực hiện việc này một cách chung chung, bao gồm các mảng hỗ trợ và một bộ chọn để chọn các thành viên cần sao chép (sử dụng chuỗi "đường dẫn" để xác định ngữ cảnh):

function clone(obj,sel) {
    return (obj ? _clone("",obj,sel) : obj);
    }

function _clone(pth,src,sel) {
    var ret=(src instanceof Array ? [] : {});

    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }

        var val=src[key], sub;

        if(sel) {
            sub+=pth+"/"+key;
            if(!sel(sub,key,val)) { continue; }
            }

        if(val && typeof(val)=='object') {
            if     (val instanceof Boolean) { val=Boolean(val);        }
            else if(val instanceof Number ) { val=Number (val);        }
            else if(val instanceof String ) { val=String (val);        }
            else                            { val=_clone(sub,val,sel); }
            }
        ret[key]=val;
        }
    return ret;
    }

Giải pháp nhân bản sâu hợp lý đơn giản nhất, giả sử một đối tượng gốc không phải null và không có lựa chọn thành viên là:

function clone(src) {
    var ret=(src instanceof Array ? [] : {});
    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }
        var val=src[key];
        if(val && typeof(val)=='object') { val=clone(val);  }
        ret[key]=val;
        }
    return ret;
    }

2

Lo-Dash , hiện là một tập hợp siêu của Underscore.js , có một số chức năng nhân bản sâu:

Từ một câu trả lời của chính tác giả :

lodash underscore xây dựng được cung cấp để đảm bảo khả năng tương thích với phiên bản ổn định mới nhất của Underscore.


câu hỏi cho biết "tôi không muốn sử dụng thư viện"
Femi Oni

@FemiOni câu hỏi không có bất cứ điều gì về thư viện (ngay cả trong các chỉnh sửa cũ của nó) ... Một vài câu trả lời khác ở đây cũng đang sử dụng thư viện này hay thư viện khác.
CPHPython

Câu trả lời @FemiOni đã bị phản đối ngày hôm qua. Tôi tự hỏi ... Dù sao thì đây cũng là một nơi học tập và đề phòng trường hợp ai đó thực sự sẽ thực hiện bản thân sâu, mã nguồn lodashbaseClone có thể cung cấp một số ý tưởng.
CPHPython

@FemiOni đối tượng JSON không phải là thư viện cũng không phải là khuôn khổ ... Nếu bạn định triển khai hàm, tôi khuyên bạn nên xem xét một trong các thư viện mã nguồn mở và sử dụng các phần bạn cần (nhiều phần đã được thử nghiệm trong nhiều năm). Nó sẽ tránh được các lỗi và các cân nhắc bị bỏ sót trong dài hạn.
CPHPython

2

Hàm dưới đây là cách hiệu quả nhất để sao chép sâu các đối tượng javascript.

function deepCopy(obj){
    if (!obj || typeof obj !== "object") return obj;

    var retObj = {};

    for (var attr in obj){
        var type = obj[attr];

        switch(true){
            case (type instanceof Date):
                var _d = new Date();
                _d.setDate(type.getDate())
                retObj[attr]= _d;
                break;

            case (type instanceof Function):
                retObj[attr]= obj[attr];
                break;

            case (type instanceof Array):
                var _a =[];
                for (var e of type){
                    //_a.push(e);
                    _a.push(deepCopy(e));
                }
                retObj[attr]= _a;
                break;

            case (type instanceof Object):
                var _o ={};
                for (var e in type){
                    //_o[e] = type[e];
                    _o[e] = deepCopy(type[e]);
                }
                retObj[attr]= _o;
                break;

            default:
                retObj[attr]= obj[attr];
        }
    }
    return retObj;
}

var obj = {
    string: 'test',
    array: ['1'],
    date: new Date(),
    object:{c: 2, d:{e: 3}},
    function: function(){
        return this.date;
    }
};

var copyObj = deepCopy(obj);

console.log('object comparison', copyObj === obj); //false
console.log('string check', copyObj.string === obj.string); //true
console.log('array check', copyObj.array === obj.array); //false
console.log('date check', copyObj2.date === obj.date); //false
console.log('object check', copyObj.object === obj.object); //false
console.log('function check', copyObj.function() === obj.function()); //true

2
Bạn có bất kỳ lập luận nào để ủng hộ tuyên bố của mình không?
Fabian von Ellerts

Tôi đã đưa ra một ví dụ bên dưới chức năng. Bạn có nghi ngờ gì không?
Kooldandy

Những ví dụ đó cho thấy rằng chức năng đang hoạt động, điều đó thật tuyệt nhưng tại sao nó lại là "cách hiệu quả nhất"?
Fabian von Ellerts

Bởi vì nó là sao chép đệ quy các thuộc tính của đối tượng trong một vòng lặp. Ngoài ra Ngày, Hàm, Đối tượng, Mảng, Số, Chuỗi tất cả các thứ đều được xử lý đúng cách. Bạn có cách nào khác không?
Kooldandy

2

Không cần thế giới thực cho một chức năng như vậy nữa. Đây chỉ là sở thích học thuật.

Đây hoàn toàn là một bài tập, đây là một cách hoạt động hiệu quả hơn. Đó là phần mở rộng của câu trả lời của @ tfmontaguetôi đã đề xuất thêm một khối bảo vệ ở đó. Nhưng khi tôi cảm thấy bị bắt buộc phải sử dụng ES6 và vận hành tất cả mọi thứ, đây là phiên bản tổng hợp của tôi. Nó làm phức tạp logic khi bạn phải ánh xạ trên mảng và giảm bớt đối tượng, nhưng nó tránh bất kỳ đột biến nào.

const cloner = (x) => {
    const recurseObj = x => (typeof x === 'object') ? cloner(x) : x
    const cloneObj = (y, k) => {
        y[k] = recurseObj(x[k])
        return y
    }
    // Guard blocks
    // Add extra for Date / RegExp if you want
    if (!x) {
        return x
    }
    if (Array.isArray(x)) {
        return x.map(recurseObj)
    }
    return Object.keys(x).reduce(cloneObj, {})
}
const tests = [
    null,
    [],
    {},
    [1,2,3],
    [1,2,3, null],
    [1,2,3, null, {}],
    [new Date('2001-01-01')], // FAIL doesn't work with Date
    {x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]},
    {
        obj : new function() {
            this.name = "Object test";
        }
    } // FAIL doesn't handle functions
]
tests.map((x,i) => console.log(i, cloner(x)))


1

Tôi nhận thấy rằng Bản đồ cần được xử lý đặc biệt, do đó với tất cả các đề xuất trong chuỗi này, mã sẽ là:

function deepClone( obj ) {
    if( !obj || true == obj ) //this also handles boolean as true and false
        return obj;
    var objType = typeof( obj );
    if( "number" == objType || "string" == objType ) // add your immutables here
        return obj;
    var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
    if( obj instanceof Map )
        for( var key of obj.keys() )
            result.set( key, deepClone( obj.get( key ) ) );
    for( var key in obj )
        if( obj.hasOwnProperty( key ) )
            result[key] = deepClone( obj[ key ] );
    return result;
}

1

bổ sung của tôi cho tất cả các câu trả lời

deepCopy = arr => {
  if (typeof arr !== 'object') return arr
  if(arr.pop) return [...arr].map(deepCopy)
  const copy = {}
  for (let prop in arr)
    copy[prop] = deepCopy(arr[prop])
  return copy
}

0

Điều này hoạt động đối với mảng, đối tượng và nguyên thủy. Thuật toán đệ quy kép chuyển đổi giữa hai phương thức truyền tải:

const deepClone = (objOrArray) => {

  const copyArray = (arr) => {
    let arrayResult = [];
    arr.forEach(el => {
        arrayResult.push(cloneObjOrArray(el));
    });
    return arrayResult;
  }

  const copyObj = (obj) => {
    let objResult = {};
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        objResult[key] = cloneObjOrArray(obj[key]);
      }
    }
    return objResult;
  }

  const cloneObjOrArray = (el) => {
    if (Array.isArray(el)) {
      return copyArray(el);
    } else if (typeof el === 'object') {
      return copyObj(el);
    } else {
      return el;
    }
  }

  return cloneObjOrArray(objOrArray);
}

0

Chúng ta có thể sử dụng đệ quy để tạo deepCopy. Nó có thể tạo bản sao của mảng, đối tượng, mảng đối tượng, đối tượng có chức năng. nếu muốn, bạn có thể thêm chức năng cho kiểu cấu trúc dữ liệu khác như bản đồ, v.v.

function deepClone(obj) {
         var retObj;
        _assignProps = function(obj, keyIndex, retObj) {
               var subType = Object.prototype.toString.call(obj[keyIndex]);
               if(subType === "[object Object]" || subType === "[object Array]") {
                    retObj[keyIndex] = deepClone(obj[keyIndex]);
               }
               else {
                     retObj[keyIndex] = obj[keyIndex];
               }
        };

        if(Object.prototype.toString.call(obj) === "[object Object]") {
           retObj = {};
           for(key in obj) {
               this._assignProps(obj, key, retObj);
           }
        }
        else if(Object.prototype.toString.call(obj) == "[object Array]") {
           retObj = [];
           for(var i = 0; i< obj.length; i++) {
              this._assignProps(obj, i, retObj);
            }
        };

        return retObj;
    };

0

Sử dụng ImmutableJS

import { fromJS } from 'immutable';

// An object we want to clone
let objA = { 
   a: { deep: 'value1', moreDeep: {key: 'value2'} } 
};

let immB = fromJS(objA); // Create immutable Map
let objB = immB.toJS(); // Convert to plain JS object

console.log(objA); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
console.log(objB); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }

// objA and objB are equalent, but now they and their inner objects are undependent
console.log(objA === objB); // false
console.log(objA.a === objB.a); // false
console.log(objA.moreDeep === objB.moreDeep); // false

Hoặc lodash / hợp nhất

import merge from 'lodash/merge'

var objA = {
    a: [{ 'b': 2 }, { 'd': 4 }]
};
// New deeply cloned object:
merge({}, objA ); 

// We can also create new object from several objects by deep merge:
var objB = {
    a: [{ 'c': 3 }, { 'e': 5 }]
};
merge({}, objA , objB ); // Object { a: [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }

0

Cái này, sử dụng tham chiếu vòng tròn, phù hợp với tôi

 //a test-object with circular reference :
 var n1 = {   id:0,   text:"aaaaa",   parent:undefined} 
 var n2 = {  id:1,   text:"zzzzz",   parent:undefined } 
 var o = { arr:[n1,n2],   parent:undefined } 
 n1.parent = n2.parent = o;
 var obj = {   a:1,   b:2,   o:o }
 o.parent = obj;

 function deepClone(o,output){ 

     if(!output) output = {};  
     if(o.______clone) return o.______clone;
     o.______clone = output.______clone = output;

   for(var z in o){

     var obj = o[z];
     if(typeof(obj) == "object") output[z] = deepClone(obj)
     else output[z] = obj; 
    }

   return output;
}

console.log(deepClone(obj));

0

var newDate = new Ngày (this.oldDate); Tôi đã chuyển oldDate cho hàm và tạo newDate từ this.oldDate, nhưng nó cũng đang thay đổi this.oldDate. Vì vậy, tôi đã sử dụng giải pháp đó và nó đã hoạt động.


0

Giải pháp này sẽ tránh các vấn đề đệ quy khi sử dụng [... target] hoặc {... target}

function shallowClone(target) {
  if (typeof a == 'array') return [...target]
  if (typeof a == 'object') return {...target}
  return target
}

/* set skipRecursion to avoid throwing an exception on recursive references */
/* no need to specify refs, or path -- they are used interally */
function deepClone(target, skipRecursion, refs, path) {
  if (!refs) refs = []
  if (!path) path = ''
  if (refs.indexOf(target) > -1) {
    if (skipRecursion) return null
    throw('Recursive reference at ' + path)
  }
  refs.push(target)
  let clone = shallowCopy(target)
  for (i in target) target[i] = deepClone(target, refs, path + '.' + i)
  return clone
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.