Làm cách nào để sao chép chính xác một đối tượng JavaScript?


3078

Tôi có một đối tượng x. Tôi muốn sao chép nó dưới dạng đối tượng y, sao cho thay đổi để ykhông sửa đổi x. Tôi nhận ra rằng việc sao chép các đối tượng xuất phát từ các đối tượng JavaScript tích hợp sẽ dẫn đến các thuộc tính bổ sung, không mong muốn. Đây không phải là một vấn đề, vì tôi đang sao chép một trong những đối tượng được xây dựng theo nghĩa đen của riêng tôi.

Làm cách nào để sao chép chính xác một đối tượng JavaScript?


30

256
Đối với JSON, tôi sử dụngmObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh.

67
Tôi thực sự không hiểu tại sao không ai đề nghị Object.create(o), nó làm mọi thứ mà tác giả yêu cầu?
froginvasion

44
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; Sau khi làm điều này, y.deep.keycũng sẽ là 2, do đó Object.create KHÔNG THỂ ĐƯỢC SỬ DỤNG để nhân bản ...
Ruben Stolk

17
@ r3wt sẽ không hoạt động ... Vui lòng chỉ đăng sau khi thực hiện kiểm tra cơ bản về giải pháp ..
akshay 16/2/2016

Câu trả lời:


1561

Để làm điều này cho bất kỳ đối tượng nào trong JavaScript sẽ không đơn giản hoặc đơn giản. Bạn sẽ gặp phải vấn đề nhặt nhầm các thuộc tính từ nguyên mẫu của đối tượng nên để lại trong nguyên mẫu và không được sao chép sang thể hiện mới. Ví dụ, nếu bạn đang thêm một clonephương thức vào Object.prototype, như một số câu trả lời mô tả, bạn sẽ cần phải bỏ qua rõ ràng thuộc tính đó. Nhưng nếu có các phương pháp bổ sung khác được thêm vào Object.prototypehoặc các nguyên mẫu trung gian khác mà bạn không biết thì sao? Trong trường hợp đó, bạn sẽ sao chép các thuộc tính bạn không nên, vì vậy bạn cần phát hiện các thuộc tính không lường trước, không cục bộ bằng hasOwnPropertyphương thức.

Ngoài các thuộc tính không thể đếm được, bạn sẽ gặp phải một vấn đề khó khăn hơn khi bạn cố gắng sao chép các đối tượng có thuộc tính ẩn. Ví dụ, prototypelà một thuộc tính ẩn của hàm. Ngoài ra, nguyên mẫu của một đối tượng được tham chiếu với thuộc tính __proto__, cũng bị ẩn và sẽ không được sao chép bởi một vòng lặp for / in lặp trên các thuộc tính của đối tượng nguồn. Tôi nghĩ rằng __proto__có thể là cụ thể cho trình thông dịch JavaScript của Firefox và nó có thể là một cái gì đó khác biệt trong các trình duyệt khác, nhưng bạn có được hình ảnh. Không phải tất cả mọi thứ là vô số. Bạn có thể sao chép một thuộc tính ẩn nếu bạn biết tên của nó, nhưng tôi không biết cách nào để tự động khám phá nó.

Tuy nhiên, một trở ngại khác trong quá trình tìm kiếm một giải pháp tao nhã là vấn đề thiết lập kế thừa nguyên mẫu một cách chính xác. Nếu nguyên mẫu của đối tượng nguồn của bạn là Object, thì chỉ cần tạo một đối tượng chung mới {}sẽ hoạt động, nhưng nếu nguyên mẫu của nguồn là một số hậu duệ Object, thì bạn sẽ thiếu các thành viên bổ sung từ nguyên mẫu đó mà bạn bỏ qua bằng hasOwnPropertybộ lọc hoặc là trong nguyên mẫu, nhưng không thể đếm được ở nơi đầu tiên. Một giải pháp có thể là gọi thuộc constructortính của đối tượng nguồn để lấy đối tượng sao chép ban đầu và sau đó sao chép qua các thuộc tính, nhưng sau đó bạn vẫn không nhận được các thuộc tính không thể đếm được. Ví dụ: một Dateđối tượng lưu trữ dữ liệu của nó dưới dạng một thành viên ẩn:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

Chuỗi ngày d1sẽ chậm hơn 5 giây d2. Một cách để làm cho cái này Dategiống với cái khác là gọi setTimephương thức, nhưng đó là cụ thể cho Datelớp. Tôi không nghĩ rằng có một giải pháp chung chống đạn cho vấn đề này, mặc dù tôi sẽ rất vui khi được sai!

Khi tôi đã phải thực hiện sâu chung sao chép tôi đã kết thúc ảnh hưởng bằng cách giả sử rằng tôi sẽ chỉ cần phải sao chép một đồng bằng Object, Array, Date, String, Number, hoặc Boolean. 3 loại cuối cùng là bất biến, vì vậy tôi có thể thực hiện một bản sao nông và không lo nó thay đổi. Tôi cũng giả định rằng bất kỳ yếu tố nào có trong Objecthoặc Arraycũng sẽ là một trong 6 loại đơn giản trong danh sách đó. Điều này có thể được thực hiện với mã như sau:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Hàm trên sẽ hoạt động đầy đủ cho 6 loại đơn giản mà tôi đã đề cập, miễn là dữ liệu trong các đối tượng và mảng tạo thành một cấu trúc cây. Đó là, không có nhiều hơn một tham chiếu đến cùng một dữ liệu trong đối tượng. Ví dụ:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

Nó sẽ không thể xử lý bất kỳ đối tượng JavaScript nào, nhưng nó có thể đủ cho nhiều mục đích miễn là bạn không cho rằng nó sẽ chỉ hoạt động cho mọi thứ bạn ném vào nó.


5
hầu như hoạt động tốt trong một nútjs - chỉ cần thay đổi dòng cho (var i = 0, var len = obj.length; i <len; ++ i) {thành for (var i = 0; i <obj.length; ++ i) {
Trindaz

5
Đối với các nhân viên Google trong tương lai: cùng một bản sao sâu, chuyển các tham chiếu theo cách đệ quy thay vì sử dụng các câu lệnh 'return' tại gist.github.com/2234277
Trindaz

8
Ngày nay JSON.parse(JSON.stringify([some object]),[some revirer function])sẽ là một giải pháp?
KooiInc

5
Trong đoạn trích đầu tiên, bạn có chắc là không var cpy = new obj.constructor()?
cyon

8
Việc gán đối tượng dường như không tạo ra một bản sao thực sự trong Chrome, duy trì tham chiếu đến đối tượng ban đầu - cuối cùng đã sử dụng JSON.opesify và JSON.parse để sao chép - hoạt động hoàn hảo
1owk3y

974

Nếu bạn không sử dụng Dates, hàm, không xác định, regExp hoặc Infinity trong đối tượng của mình, một lớp lót rất đơn giản là JSON.parse(JSON.stringify(object)):

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

Điều này hoạt động cho tất cả các loại đối tượng có chứa các đối tượng, mảng, chuỗi, booleans và số.

Xem thêm bài viết này về thuật toán nhân bản có cấu trúc của các trình duyệt được sử dụng khi đăng thông báo đến và từ một nhân viên. Nó cũng chứa một chức năng để nhân bản sâu.


42
Lưu ý rằng điều này chỉ có thể được sử dụng để thử nghiệm. Thứ nhất, nó không phải là tối ưu về thời gian và mức tiêu thụ bộ nhớ. Thứ hai, không phải tất cả các trình duyệt đều có phương pháp này.
Nux

2
Bạn luôn có thể bao gồm JSON2.js hoặc JSON3.js. Bạn sẽ cần chúng cho ứng dụng của bạn nào. Nhưng tôi đồng ý rằng đây có thể không phải là giải pháp tốt nhất, vì JSON.opesify không bao gồm các thuộc tính được kế thừa.
Tim Hồng

78
@Nux, Tại sao không tối ưu về thời gian và bộ nhớ? MiJyn nói: "Lý do tại sao phương thức này chậm hơn so với sao chép nông (trên một đối tượng sâu) là phương thức này, theo định nghĩa, các bản sao sâu. Nhưng vì JSON được triển khai trong mã gốc (trong hầu hết các trình duyệt), nên điều này sẽ nhanh hơn đáng kể hơn là sử dụng bất kỳ giải pháp sao chép sâu dựa trên javascript nào khác, và đôi khi có thể nhanh hơn kỹ thuật sao chép nông dựa trên javascript (xem: jsperf.com/claming-an-object/79). " stackoverflow.com/questions/122102/
Mạnh

16
Tôi chỉ muốn thêm một bản cập nhật cho bản này vào tháng 10 năm 2014. Chrome 37+ nhanh hơn với JSON.parse (JSON.opesify (oldObject)); Lợi ích của việc sử dụng này là rất dễ dàng cho một công cụ javascript để xem và tối ưu hóa thành thứ gì đó tốt hơn nếu nó muốn.
mirhagk

17
Cập nhật năm 2016: Điều này phải hoạt động ở hầu hết mọi trình duyệt đang được sử dụng rộng rãi. (xem Tôi có thể sử dụng ... ) Câu hỏi chính bây giờ sẽ là liệu nó có đủ hiệu suất hay không.
James Foster

783

Với jQuery, bạn có thể sao chép nông với phần mở rộng :

var copiedObject = jQuery.extend({}, originalObject)

những thay đổi tiếp theo copiedObjectsẽ không ảnh hưởng đến originalObjectvà ngược lại.

Hoặc để tạo một bản sao sâu :

var copiedObject = jQuery.extend(true, {}, originalObject)

164
hoặc thậm chí:var copiedObject = jQuery.extend({},originalObject);
Cấp cho McLean

82
Cũng hữu ích để xác định đúng là thông số đầu tiên cho bản sao sâu:jQuery.extend(true, {}, originalObject);
Sẽ cạo

6
Có, tôi thấy liên kết này hữu ích (cùng giải pháp với Pascal) stackoverflow.com/questions/122102/iêu
Garry English

3
Chỉ cần một lưu ý, điều này không sao chép hàm tạo proto của đối tượng ban đầu
Sam Jones

1
Theo stackoverflow.com/questions/184710/ , có vẻ như "bản sao nông" chỉ sao chép tham chiếu của bản gốcObject, vậy tại sao ở đây lại nói như vậy ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa.... Xin lỗi rằng tôi đã thực sự bối rối.
Carr

687

Trong ECMAScript 6 có phương thức Object.assign , sao chép các giá trị của tất cả các thuộc tính riêng có thể đếm được từ đối tượng này sang đối tượng khác. Ví dụ:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Nhưng hãy lưu ý rằng các đối tượng lồng nhau vẫn được sao chép làm tham chiếu.


Vâng, tôi tin rằng đó Object.assignlà con đường để đi. Thật dễ dàng để polyfill nó quá: gist.github.com/rafaelrinaldi/43813e707970bd2d77fa

22
Cũng cần lưu ý rằng điều này sẽ sao chép qua các "phương thức" được xác định thông qua các đối tượng bằng chữ (vì chúng là vô số ) nhưng không phải là các phương thức được thách thức thông qua cơ chế "lớp" (vì chúng không thể đếm được).
Marcus Junius Brutus

16
Tôi nghĩ nên được đề cập rằng điều này không được hỗ trợ bởi IE ngoại trừ Edge. Một số người vẫn sử dụng cái này.
Saulius

1
Điều này giống như câu trả lời của @EugeneTiurin.
Héo

5
Phát biểu từ kinh nghiệm ở đây ... KHÔNG sử dụng điều này. Các đối tượng được thiết kế theo thứ bậc và bạn sẽ chỉ sao chép cấp độ đầu tiên, dẫn đến việc bạn chỉ định toàn bộ đối tượng đã sao chép. Tin tôi đi, nó có thể giúp bạn tiết kiệm được nhiều ngày.
ow3n

232

Mỗi MDN :

  • Nếu bạn muốn sao chép nông, sử dụng Object.assign({}, a)
  • Đối với bản sao "sâu", sử dụng JSON.parse(JSON.stringify(a))

Không cần thư viện bên ngoài nhưng trước tiên bạn cần kiểm tra khả năng tương thích của trình duyệt .


8
JSON.parse (JSON.opesify (a)) trông rất đẹp, nhưng trước khi sử dụng, tôi khuyên bạn nên điểm chuẩn thời gian cần thiết cho bộ sưu tập mong muốn của bạn. Tùy thuộc vào kích thước đối tượng, đây có thể không phải là tùy chọn nhanh nhất.
Edza

3
Phương thức JSON tôi nhận thấy chuyển đổi các đối tượng ngày thành chuỗi nhưng không quay lại ngày. Phải đối phó với sự thú vị của múi giờ trong Javascript và tự sửa bất kỳ ngày nào. Có thể là trường hợp tương tự cho các loại khác ngoài ngày
Steve Seeger

cho ngày, tôi sẽ sử dụng khoảnh khắc.js vì nó có clonechức năng. xem thêm tại đây khoảnh khắcjs.com/docs/#/parsing/moment
Tareq

Điều này là tốt nhưng hãy cẩn thận nếu đối tượng có chức năng bên trong
lesolorzanov

Một vấn đề khác với phân tích cú pháp json là tài liệu tham khảo tròn. Nếu có bất kỳ tham chiếu tròn nào bên trong đối tượng, điều này sẽ bị phá vỡ
philoj

133

Có nhiều câu trả lời, nhưng không có câu trả lời nào đề cập đến Object.create từ ECMAScript 5, điều này thừa nhận không cung cấp cho bạn một bản sao chính xác, nhưng đặt nguồn làm nguyên mẫu của đối tượng mới.

Vì vậy, đây không phải là một câu trả lời chính xác cho câu hỏi, nhưng nó là một giải pháp một dòng và do đó thanh lịch. Và nó hoạt động tốt nhất cho 2 trường hợp:

  1. Trường hợp thừa kế như vậy là hữu ích (duh!)
  2. Trường hợp đối tượng nguồn sẽ không được sửa đổi, do đó làm cho mối quan hệ giữa 2 đối tượng không thành vấn đề.

Thí dụ:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

Tại sao tôi coi giải pháp này là vượt trội? Đó là bản địa, do đó không lặp, không đệ quy. Tuy nhiên, các trình duyệt cũ hơn sẽ cần một polyfill.


Lưu ý: Object.create không phải là một bản sao sâu (itpastorn đã đề cập không có đệ quy). Bằng chứng:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts

103
Đây là sự kế thừa nguyên mẫu, không phải nhân bản. Đây là những điều hoàn toàn khác nhau. Đối tượng mới không có bất kỳ thuộc tính riêng nào của nó, nó chỉ trỏ đến các thuộc tính của nguyên mẫu. Điểm nhân bản là tạo ra một đối tượng mới không tham chiếu bất kỳ thuộc tính nào trong đối tượng khác.
d13

7
Tôi hoàn toàn đồng ý với bạn. Tôi cũng đồng ý rằng đây không phải là nhân bản như có thể là 'dự định'. Nhưng thôi nào mọi người, hãy nắm lấy bản chất của JavaScript thay vì cố gắng tìm các giải pháp tối nghĩa không được chuẩn hóa. Chắc chắn, bạn không thích nguyên mẫu và tất cả chúng đều "blah" với bạn, nhưng thực tế chúng rất hữu ích nếu bạn biết bạn đang làm gì.
froginvasion

4
@RobG: Bài viết này giải thích sự khác biệt giữa tham chiếu và nhân bản: en.wikipedia.org/wiki/Claming_(programming) . Object.createchỉ đến các thuộc tính của cha mẹ thông qua các tài liệu tham khảo. Điều đó có nghĩa là nếu giá trị tài sản của cha mẹ thay đổi, đứa trẻ cũng sẽ thay đổi. Điều này có một số tác dụng phụ đáng ngạc nhiên với các mảng và đối tượng lồng nhau có thể dẫn đến các lỗi khó tìm trong mã của bạn nếu bạn không biết về chúng: jsbin.com/EKivInO/2 . Đối tượng nhân bản là một đối tượng hoàn toàn mới, độc lập, có cùng thuộc tính và giá trị với cha mẹ, nhưng không được kết nối với cha mẹ.
d13

1
Điều này đánh lừa mọi người ... Object.create () có thể được sử dụng như một phương tiện để thừa kế nhưng nhân bản không ở đâu gần nó.
Prajnavantha

128

Một cách thanh lịch để sao chép một đối tượng Javascript trong một dòng mã

Một Object.assignphương thức là một phần của tiêu chuẩn ECMAScript 2015 (ES6) và thực hiện chính xác những gì bạn cần.

var clone = Object.assign({}, obj);

Phương thức Object.assign () được sử dụng để sao chép các giá trị của tất cả các thuộc tính riêng có thể đếm được từ một hoặc nhiều đối tượng nguồn vào một đối tượng đích.

Đọc thêm...

Các polyfill để hỗ trợ trình duyệt cũ hơn:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

Xin lỗi cho câu hỏi ngớ ngẩn, nhưng tại sao lại Object.assignmất hai tham số khi valuehàm trong polyfill chỉ lấy một tham số?
Qwertie

@Qwertie ngày hôm qua Tất cả các đối số được lặp lại và hợp nhất thành một đối tượng, ưu tiên các thuộc tính từ đối số được thông qua gần nhất
Eugene Tiurin

Ồ tôi hiểu rồi, cảm ơn (trước đây tôi không quen thuộc với argumentsđối tượng này.) Tôi gặp khó khăn khi tìm kiếm Object()thông qua Google ... đó là một kiểu chữ, phải không?
Qwertie

44
điều này sẽ chỉ thực hiện một "nhân bản" nông
Marcus Junius Brutus

1
Câu trả lời này giống hệt như sau: stackoverflow.com/questions/122102/ nam Tôi biết đó là của cùng một người nhưng bạn nên tham khảo thay vì chỉ sao chép câu trả lời.
lesolorzanov

87

Có một số vấn đề với hầu hết các giải pháp trên internet. Vì vậy, tôi quyết định thực hiện theo dõi, bao gồm, tại sao câu trả lời được chấp nhận không nên được chấp nhận.

tình huống bắt đầu

Tôi muốn sao chép sâu một Javascript Objectvới tất cả trẻ em và con của chúng, v.v. Nhưng vì tôi không loại một nhà phát triển bình thường, tôi Objectbình thường properties , circular structuresvà thậm chí cả nested objects.

Vì vậy, hãy tạo một circular structurenested objectđầu tiên.

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Chúng ta hãy mang mọi thứ lại với nhau trong một Objectcái tên a.

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

Tiếp theo, chúng tôi muốn sao chép avào một biến có tên bvà biến đổi nó.

var b = a;

b.x = 'b';
b.nested.y = 'b';

Bạn biết những gì đã xảy ra ở đây bởi vì nếu không bạn thậm chí sẽ không đáp ứng câu hỏi tuyệt vời này.

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

Bây giờ hãy tìm một giải pháp.

JSON

Nỗ lực đầu tiên tôi đã thử là sử dụng JSON.

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

Đừng lãng phí quá nhiều thời gian cho nó, bạn sẽ nhận được TypeError: Converting circular structure to JSON.

Bản sao đệ quy ("câu trả lời" được chấp nhận)

Chúng ta hãy xem câu trả lời được chấp nhận.

function cloneSO(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Có vẻ tốt, heh? Đó là một bản sao đệ quy của đối tượng và xử lý các loại khác, như Date, nhưng đó không phải là một yêu cầu.

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

Đệ quy và circular structureskhông làm việc tốt với nhau ...RangeError: Maximum call stack size exceeded

giải pháp bản địa

Sau khi tranh cãi với đồng nghiệp của tôi, sếp của tôi đã hỏi chúng tôi chuyện gì đã xảy ra và anh ta đã tìm ra một giải pháp đơn giản sau một vài cuộc cãi vã . Nó được gọi là Object.create.

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

Giải pháp này đã được thêm vào Javascript một thời gian trước và thậm chí xử lý circular structure.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

... Và bạn thấy đấy, nó không hoạt động với cấu trúc lồng nhau bên trong.

polyfill cho giải pháp bản địa

Có một polyfill dành cho Object.createtrình duyệt cũ hơn như IE 8. Nó giống như được khuyến nghị bởi Mozilla, và tất nhiên, nó không hoàn hảo và dẫn đến vấn đề tương tự như giải pháp gốc .

function F() {};
function clonePF(o) {
    F.prototype = o;
    return new F();
}

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

Tôi đã đặt Fngoài phạm vi để chúng tôi có thể xem những gì instanceofcho chúng tôi biết.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

Vấn đề tương tự như giải pháp tự nhiên , nhưng đầu ra tồi tệ hơn một chút.

giải pháp tốt hơn (nhưng không hoàn hảo)

Khi đào xung quanh, tôi đã tìm thấy một câu hỏi tương tự ( Trong Javascript, khi thực hiện một bản sao sâu, làm cách nào để tránh một chu kỳ, do một thuộc tính là "cái này"? ) Cho câu hỏi này, nhưng với một giải pháp tốt hơn.

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

Và hãy nhìn vào đầu ra ...

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

Các yêu cầu được kết hợp, nhưng vẫn còn một số vấn đề nhỏ hơn, bao gồm cả việc thay đổi instancecủa nestedcircđến Object.

Cấu trúc của những cây chia sẻ một chiếc lá sẽ không được sao chép, chúng sẽ trở thành hai chiếc lá độc lập:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

phần kết luận

Giải pháp cuối cùng sử dụng đệ quy và bộ đệm, có thể không phải là tốt nhất, nhưng đó là một bản sao sâu thực sự của đối tượng. Nó xử lý đơn giản properties, circular structuresnested object, nhưng nó sẽ gây rối cho trường hợp của chúng trong khi nhân bản.

jsfiddle


12
Vì vậy, kết luận là để tránh vấn đề đó :)
mikus

@mikus cho đến khi có một đặc điểm kỹ thuật thực sự bao gồm nhiều hơn chỉ các trường hợp sử dụng cơ bản, vâng.
Fabio Poloni

2
Một phân tích ổn về các giải pháp được cung cấp ở trên nhưng kết luận được rút ra bởi tác giả cho thấy rằng không có giải pháp nào cho câu hỏi này.
Amir Mog

2
Thật xấu hổ khi JS không bao gồm chức năng nhân bản gốc.
l00k

1
Trong số tất cả các câu trả lời hàng đầu, tôi cảm thấy điều này gần với câu trả lời đúng.
KTU

77

Nếu bạn ổn với một bản sao nông, thư viện underscore.js có phương thức sao chép .

y = _.clone(x);

hoặc bạn có thể mở rộng nó như

copiedObject = _.extend({},originalObject);

2
Cảm ơn. Sử dụng kỹ thuật này trên máy chủ sao băng.
Turbo

Để bắt đầu nhanh chóng với lodash, tôi khuyên bạn nên học npm, Browserify, cũng như lodash. Tôi đã nhân bản để làm việc với 'npm i --save lodash.clone' và sau đó 'var clone = Yêu cầu (' lodash.clone ');' Để có được yêu cầu làm việc, bạn cần một cái gì đó như browserify. Khi bạn cài đặt nó và tìm hiểu cách thức hoạt động của nó, bạn sẽ sử dụng 'browserify yourfile.js> bundle.js; bắt đầu chrome index.html' mỗi khi bạn chạy mã của mình (thay vì truy cập trực tiếp vào Chrome). Điều này tập hợp tệp của bạn và tất cả các tệp bạn yêu cầu từ mô-đun npm vào bundle.js. Bạn có thể có thể tiết kiệm thời gian và tự động hóa bước này với Gulp.
Aaron Bell

65

OK, hãy tưởng tượng bạn có đối tượng này bên dưới và bạn muốn sao chép nó:

let obj = {a:1, b:2, c:3}; //ES6

hoặc là

var obj = {a:1, b:2, c:3}; //ES5

câu trả lời chủ yếu phụ thuộc vào ECMAscript mà bạn sử dụng, trong đó ES6+, bạn có thể chỉ cần sử dụng Object.assignđể thực hiện bản sao:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

hoặc sử dụng toán tử trải rộng như thế này:

let cloned = {...obj}; //new {a:1, b:2, c:3};

Nhưng nếu bạn sử dụng ES5, bạn có thể sử dụng một vài phương thức, nhưng JSON.stringify, chỉ cần đảm bảo rằng bạn không sử dụng cho một khối dữ liệu lớn để sao chép, nhưng nó có thể là một cách tiện dụng trong nhiều trường hợp, như thế này:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

Bạn có thể vui lòng cho ví dụ về những gì big chunk of datasẽ tương đương? 100kb? 100 MB? Cảm ơn!
dùng1063287

Có, @ user1063287, về cơ bản là dữ liệu lớn hơn, hiệu suất kém hơn ... vì vậy nó thực sự phụ thuộc, không phải là kb, mb hay gb, đó là về số lần bạn muốn làm điều đó ... Ngoài ra, nó sẽ không hoạt động cho các chức năng và những thứ khác ...
Alireza

3
Object.assigntạo một bản sao nông (giống như sự lây lan, @Alizera)
Bogdan D

Bạn không thể sử dụng let in es5: ^) @Alireza
SensationSama

40

Một giải pháp đặc biệt không phù hợp là sử dụng mã hóa JSON để tạo các bản sao sâu của các đối tượng không có phương thức thành viên. Phương pháp là để mã hóa JSON đối tượng mục tiêu của bạn, sau đó bằng cách giải mã nó, bạn sẽ có được bản sao mà bạn đang tìm kiếm. Bạn có thể giải mã bao nhiêu lần bạn muốn tạo bao nhiêu bản sao bạn cần.

Tất nhiên, các hàm không thuộc về JSON, vì vậy điều này chỉ hoạt động cho các đối tượng không có các phương thức thành viên.

Phương pháp này hoàn hảo cho trường hợp sử dụng của tôi, vì tôi đang lưu trữ các đốm màu JSON trong kho lưu trữ khóa-giá trị và khi chúng được hiển thị dưới dạng đối tượng trong API JavaScript, mỗi đối tượng thực sự chứa một bản sao trạng thái ban đầu của đối tượng, vì vậy chúng tôi có thể tính toán delta sau khi người gọi đã đột biến đối tượng tiếp xúc.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

Tại sao các hàm không thuộc về JSON? Tôi đã thấy họ được chuyển dưới dạng JSON nhiều hơn một lần ...
the_drow

5
Các hàm không phải là một phần của đặc tả JSON vì chúng không phải là một cách an toàn (hoặc thông minh) để truyền dữ liệu, đó là những gì JSON được tạo ra. Tôi biết bộ mã hóa JSON gốc trong Firefox chỉ đơn giản là bỏ qua các hàm được truyền cho nó, nhưng tôi không chắc về hành vi của người khác.
Kris Walker

1
@mark: { 'foo': function() { return 1; } }là một đối tượng được xây dựng theo nghĩa đen.
abarnert

Hàm @abarnert không phải là dữ liệu. "Hàm chữ" là một cách viết sai - vì các hàm có thể chứa mã tùy ý, bao gồm các bài tập và tất cả các loại "không tuần tự hóa".
vemv

35

Bạn chỉ có thể sử dụng thuộc tính lây lan để sao chép một đối tượng mà không cần tham chiếu. Nhưng hãy cẩn thận (xem bình luận), 'bản sao' chỉ ở mức đối tượng / mảng thấp nhất. Tài sản lồng nhau vẫn là tài liệu tham khảo!


Bản sao hoàn chỉnh:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

Nhân bản với các tài liệu tham khảo ở cấp độ thứ hai:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript thực sự không hỗ trợ nhân bản sâu nguyên bản. Sử dụng một chức năng tiện ích. Ví dụ: Ramda:

http://ramdajs.com/docs/#clone


1
Điều này không hoạt động ... nó có thể hoạt động khi x sẽ là một mảng ví dụ x = ['ab', 'cd', ...]
Kamil Kiełczewski

3
Điều này hoạt động, nhưng hãy nhớ rằng đây là một bản sao SHALLOW, do đó, bất kỳ tài liệu tham khảo sâu nào cho các đối tượng khác vẫn là tài liệu tham khảo!
Bọ Bunny

Một bản sao một phần cũng có thể xảy ra theo cách này:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Cristian Traìna

25

Đối với những người sử dụng AngularJS, cũng có phương pháp trực tiếp để nhân bản hoặc mở rộng các đối tượng trong thư viện này.

var destination = angular.copy(source);

hoặc là

angular.copy(source, destination);

Thêm trong tài liệu angular.copy ...


2
Đây là một bản sao sâu FYI.
zamnuts

22

Đây là một chức năng bạn có thể sử dụng.

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

10
Câu trả lời này khá gần, nhưng không hoàn toàn chính xác. Nếu bạn thử nhân bản một đối tượng Ngày, bạn sẽ không nhận được cùng một ngày vì lệnh gọi hàm xây dựng Ngày khởi tạo Ngày mới với ngày / giờ hiện tại. Giá trị đó không thể đếm được và sẽ không được sao chép bởi vòng lặp for / in.
A. Levy

Không hoàn hảo, nhưng tốt đẹp cho những trường hợp cơ bản. Ví dụ, cho phép nhân bản đơn giản một đối số có thể là Đối tượng, Mảng hoặc Chuỗi cơ bản.
james_womack

Upvote để gọi chính xác các nhà xây dựng bằng cách sử dụng new. Câu trả lời được chấp nhận không.
GetFree

hoạt động trên nút mọi thứ khác! vẫn để lại các liên kết tham chiếu
user956584

Suy nghĩ đệ quy là tuyệt vời. Nhưng nếu giá trị là mảng, nó sẽ hoạt động?
Q10 Đi xe đạp

22

Câu trả lời của A.Levy gần như đã hoàn tất, đây là đóng góp nhỏ của tôi: có một cách để xử lý các tài liệu tham khảo đệ quy , xem dòng này

if(this[attr]==this) copy[attr] = copy;

Nếu đối tượng là phần tử XML DOM, chúng ta phải sử dụng cloneNode thay thế

if(this.cloneNode) return this.cloneNode(true);

Lấy cảm hứng từ nghiên cứu toàn diện của A.Levy và phương pháp tạo mẫu của Calvin, tôi đưa ra giải pháp này:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

Xem thêm ghi chú của Andy Burke trong câu trả lời.


3
Date.prototype.clone = function() {return new Date(+this)};
RobG

22

Từ bài viết này: Cách sao chép mảng và đối tượng trong Javascript của Brian Huisman:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var 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;
};

4
Điều này gần, nhưng không hoạt động cho bất kỳ đối tượng. Hãy thử nhân bản một đối tượng Date bằng cái này. Không phải tất cả các thuộc tính đều có thể đếm được, vì vậy chúng sẽ không hiển thị trong vòng lặp for / in.
A. Levy

Thêm vào nguyên mẫu đối tượng như thế này đã phá vỡ jQuery cho tôi. Ngay cả khi tôi đổi tên thành clone2.
iPadDeveloper2011

3
@ iPadDeveloper2011 Đoạn mã trên có một lỗi trong đó nó đã tạo ra một biến toàn cục gọi là 'i' '(đối với tôi trong này)', thay vì '(cho var i trong này)'. Tôi có đủ nghiệp lực để chỉnh sửa và sửa nó nên tôi đã làm.
mikemaccana

1
@Calvin: điều này nên được tạo ra một thuộc tính không thể đếm được, nếu không 'bản sao' sẽ xuất hiện trong 'cho' các vòng lặp.
mikemaccana

2
Tại sao không phải var copiedObj = Object.create(obj);là một cách tuyệt vời là tốt?
Dan P.

19

Trong ES-6, bạn chỉ cần sử dụng Object.assign (...). Ví dụ:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

Một tài liệu tham khảo tốt có ở đây: https://googlechrome.github.io/samples/object-assign-es6/


12
Nó không nhân bản sâu đối tượng.
ngày

Đó là một nhiệm vụ, không phải là một bản sao. clone.Title = "chỉ là một bản sao" có nghĩa là obj.Title = "chỉ là một bản sao".
Hold OfferHunger

@Hold OfferHunger Bạn đang nhầm. Kiểm tra nó trong bảng điều khiển JS của trình duyệt ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
sụp đổ

@collapsar: Đó chính xác là những gì tôi đã kiểm tra, sau đó console.log (người) sẽ là "Whazzup", không phải "Thor Odinson". Xem bình luận của tháng Tám.
Hold OfferHunger

1
@Hold OfferHunger Không xảy ra trong Chrome 60.0.3112.113 cũng như trong Edge 14,14393; Nhận xét của tháng 8 không áp dụng vì các giá trị của các loại objthuộc tính nguyên thủy thực sự được nhân bản. Các giá trị thuộc tính là đối tượng sẽ không được sao chép.
sụp đổ

18

Trong ECMAScript 2018

let objClone = { ...obj };

Xin lưu ý rằng các đối tượng lồng nhau vẫn được sao chép như một tài liệu tham khảo.


1
Cảm ơn gợi ý rằng các đối tượng lồng nhau vẫn được sao chép làm tài liệu tham khảo! Tôi gần như phát điên khi gỡ lỗi mã của mình vì tôi đã sửa đổi các thuộc tính lồng nhau trên "bản sao" nhưng bản gốc đã được sửa đổi.
Benny Neugebauer

Đây là ES2016, không phải 2018 và câu trả lời này đã được đưa ra hai năm trước đó .
Dan Dascalescu

vậy nếu tôi muốn sao chép tài sản lồng nhau
Sunil Garg

1
@SunilGarg Để sao chép tài sản lồng nhau, bạn cũng có thể sử dụng const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

16

Sử dụng Lodash:

var y = _.clone(x, true);

5
OMG nó sẽ là điên rồ để phát minh lại nhân bản. Đây là câu trả lời lành mạnh duy nhất.
Dan Ross

5
Tôi thích _.cloneDeep(x)vì về cơ bản nó giống như trên, nhưng đọc tốt hơn.
garbanzio

14

Quan tâm đến việc nhân bản các đối tượng đơn giản:

JSON.parse(JSON.stringify(json_original));

Nguồn: Làm thế nào để sao chép đối tượng JavaScript sang biến mới KHÔNG bằng tham chiếu?


Rất đẹp - đơn giản.
Matt H

@MattH: câu trả lời này đã được đưa ra vào năm 2012 . bạn có thấy nó không? Mohammed, bạn đã kiểm tra các câu trả lời hiện có trước khi sao chép một trong số chúng chưa?
Dan Dascalescu

đó là một cách. ty không bao giờ nghĩ về điều đó
1-14x0r

13

Bạn có thể sao chép một đối tượng và xóa bất kỳ tham chiếu nào khỏi đối tượng trước đó bằng một dòng mã. Đơn giản chỉ cần làm:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

Đối với các trình duyệt / công cụ hiện không hỗ trợ Object.create, bạn có thể sử dụng polyfill này:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

1
+1 Object.create(...)dường như chắc chắn là con đường để đi.
René Nyffalanger

Câu trả lời hoàn hảo. Có lẽ bạn có thể thêm một lời giải thích cho Object.hasOwnProperty? Bằng cách đó mọi người biết cách ngăn chặn tìm kiếm liên kết nguyên mẫu.
froginvasion

Hoạt động tốt nhưng polyfill hoạt động trong trình duyệt nào?
Ian Lunn

11
Điều này đang tạo obj2 với obj1 như nguyên mẫu của nó. Nó chỉ hoạt động vì bạn đang theo dõi textthành viên trong obj2. Bạn không tạo một bản sao, chỉ chuyển đến chuỗi nguyên mẫu khi không tìm thấy thành viên trên obj2.
Nick Desaulniers

2
Điều này KHÔNG tạo ra nó "không có tài liệu tham khảo", nó chỉ di chuyển tham chiếu đến nguyên mẫu. Nó vẫn là một tài liệu tham khảo. Nếu một thuộc tính thay đổi trong bản gốc thì thuộc tính nguyên mẫu trong "bản sao". Nó hoàn toàn không phải là một bản sao.
Jimbo Jonny

13

Câu trả lời mới cho một câu hỏi cũ! Nếu bạn có niềm vui khi sử dụng ECMAScript 2016 (ES6) với Syntax lây lan , thật dễ dàng.

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

Điều này cung cấp một phương thức sạch cho một bản sao nông của một đối tượng. Tạo một bản sao sâu, nghĩa là makign một bản sao mới của mọi giá trị trong mọi đối tượng lồng nhau đệ quy, đòi hỏi các giải pháp nặng hơn ở trên.

JavaScript tiếp tục phát triển.


2
nó không hoạt động khi bạn có các chức năng được xác định trên các đối tượng
Petr Marek

theo như tôi thấy toán tử lây lan chỉ hoạt động với iterables - developer.mozilla.org nói: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Oleh

@Oleh vì vậy hãy sử dụng `{... obj} thay vì [... obj];`
manutant gautam

@manikantgautam Trước đây tôi đã sử dụng Object.assign (), nhưng bây giờ thực sự cú pháp lây lan đối tượng được hỗ trợ trong Chrome, Firefox mới nhất (vẫn chưa có trong Edge và Safari). Đề xuất ECMAScript của nó ... nhưng Babel không hỗ trợ nó như tôi có thể thấy, vì vậy có lẽ nó an toàn để sử dụng.
Oleh

12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Giải pháp ES6 nếu bạn muốn (nông) sao chép một thể hiện của lớp và không chỉ là một đối tượng thuộc tính.


Điều này khác với let cloned = Object.assign({}, obj)như thế nào?
ceztko

10

Tôi nghĩ rằng có một câu trả lời đơn giản và làm việc. Trong sao chép sâu có hai mối quan tâm:

  1. Giữ các thuộc tính độc lập với nhau.
  2. Và giữ cho các phương thức sống trên đối tượng nhân bản.

Vì vậy, tôi nghĩ rằng một giải pháp đơn giản sẽ là đầu tiên tuần tự hóa và giải tuần tự hóa và sau đó thực hiện một nhiệm vụ trên nó để sao chép các chức năng.

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

Mặc dù câu hỏi này có nhiều câu trả lời, tôi hy vọng câu hỏi này cũng có ích.


Mặc dù nếu tôi được phép nhập lodash, tôi thích sử dụng lodash hơn cloneDeep.
Tiến hành

2
Tôi đang sử dụng JSON.parse (JSON.opesify (nguồn)). Luôn làm việc.
Misha

2
@Misha, theo cách này bạn sẽ bỏ lỡ các chức năng. Thuật ngữ 'công trình' có nhiều ý nghĩa.
tiến hành

Và hãy nhớ rằng, theo cách tôi đã đề cập, chỉ các chức năng của lớp đầu tiên sẽ được sao chép. Vì vậy, nếu chúng ta có một số đối tượng bên trong nhau, thì cách duy nhất là sao chép trường theo trường đệ quy.
Tiến hành kiểm tra

9

Đối với một bản sao sâu và sao chép, JSON.opesify sau đó JSON.parse đối tượng:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

khá thông minh ... bất kỳ nhược điểm của phương pháp này?
Aleks

8

Đây là sự điều chỉnh mã của A. Levy để xử lý việc nhân bản các hàm và tham chiếu nhiều / chu kỳ - điều này có nghĩa là nếu hai thuộc tính trong cây được nhân bản là tham chiếu của cùng một đối tượng, cây đối tượng được nhân bản sẽ có các thuộc tính trỏ đến một và cùng một bản sao của đối tượng được tham chiếu. Điều này cũng giải quyết trường hợp phụ thuộc theo chu kỳ, nếu không được xử lý, dẫn đến một vòng lặp vô hạn. Độ phức tạp của thuật toán là O (n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

        // Handle Date
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


        throw new Error("Unable to copy obj! Its type isn't supported.");
    }
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

Một số bài kiểm tra nhanh

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

1
Tính đến tháng 9 năm 2016, đây là giải pháp chính xác duy nhất cho câu hỏi.
DomQ

6

Tôi chỉ muốn thêm vào tất cả các Object.creategiải pháp trong bài viết này, rằng điều này không hoạt động theo cách mong muốn với nodejs.

Trong Firefox kết quả của

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

{test:"test"}.

Trong nodejs, nó là

{}

Đây là sự kế thừa nguyên mẫu, không phải nhân bản.
d13

1
@ d13 trong khi đối số của bạn hợp lệ, lưu ý rằng không có cách nào được chuẩn hóa trong JavaScript để sao chép một đối tượng. Đây là sự kế thừa nguyên mẫu, tuy nhiên nó có thể được sử dụng làm bản sao nếu bạn hiểu các khái niệm.
froginvasion

@froginvasion. Vấn đề duy nhất khi sử dụng Object.create là các đối tượng và mảng được lồng chỉ là các tham chiếu con trỏ đến các đối tượng và mảng lồng nhau của nguyên mẫu. jsbin.com/EKivInO/2/edit?js,console . Về mặt kỹ thuật, một đối tượng "nhân bản" nên có các thuộc tính duy nhất của riêng nó mà không được chia sẻ tham chiếu đến các thuộc tính trên các đối tượng khác.
D13

@ d13 được rồi, tôi thấy quan điểm của bạn bây giờ. Nhưng điều tôi muốn nói là có quá nhiều người bị xa lánh với khái niệm thừa kế nguyên mẫu, và với tôi không học được cách nó hoạt động. Nếu tôi không nhầm, ví dụ của bạn có thể được sửa bằng cách gọi Object.hasOwnPropertyđể kiểm tra xem bạn có sở hữu mảng đó hay không. Có, điều này không thêm phức tạp để đối phó với thừa kế nguyên mẫu.
froginvasion

6
function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};

2
if(!src && typeof src != "object"){. Tôi nghĩ rằng nên ||không &&.
MikeM

6

mindeavor đã nói rằng đối tượng được nhân bản là một đối tượng 'được xây dựng theo nghĩa đen', nên một giải pháp có thể chỉ đơn giản là tạo đối tượng nhiều lần thay vì nhân bản một thể hiện của đối tượng:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();

6

Tôi đã viết triển khai của riêng tôi. Không chắc chắn nếu nó được coi là một giải pháp tốt hơn:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

Sau đây là việc thực hiện:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}

không làm việc cho đối tượng của tôi, mặc dù trường hợp của tôi hơi phức tạp một chút.
Sajuuk
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.