2.6k

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?

|
1.4k

Để 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 sao chép sâu chung 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 lắng về việc nó sẽ 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ó.

|
  • 1

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

    – Vũ Thảo Hồng 00:08:39 28/03/2012
  • 1

    Đố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

    – Trịnh Tùng Linh 06:48:43 29/03/2012
  • 1

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

    – Bùi Tiên Phương 07:42:25 20/05/2012
  • 1

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

    – Bùi Thúy Liễu 03:29:28 29/11/2014
  • 1

    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 - kết thúc bằng cách sử dụng JSON.opesify và JSON.parse để sao chép - hoạt động hoàn hảo

    – Tạ Tú Mi 23:12:40 30/11/2017
838

Nếu bạn không sử dụng các chức năng trong đối tượng của mình, một lớp lót rất đơn giản có thể là như sau:

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

Đ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.

|
  • 1

    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.

    – Vũ Thảo Hồng 18:56:38 12/08/2013
  • 1

    @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/

    – Trịnh Tùng Linh 15:27:53 14/05/2014
  • 1

    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.

    – Bùi Tiên Phương 21:08:16 07/10/2014
  • 1

    Nó sẽ tào lao khắp JSON nếu đối tượng có những thứ không thể a = { b: Infinity, c: undefined }

    – Bùi Thúy Liễu 08:42:03 08/10/2014
  • 1

    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.

    – Tạ Tú Mi 12:15:20 10/02/2016
748

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 đối với copyObject sẽ không ảnh hưởng đến bản gốcObject và ngược lại.

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

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

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.

|
165

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 .

|
128

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.

|
  • 1

    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 */;

    – Hồ Ái Như 08:07:04 29/04/2013
  • 1

    Đây là sự kế thừa nguyên mẫu, không 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.

    – Võ Lưu Ly 16:18:16 16/01/2014
  • 1

    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ì.

    – Đỗ Viết Sơn 15:26:30 08/08/2014
  • 1

    @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 độc lập, hoàn toàn mới, 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ẹ.

    – Trần Bích Chiêu 14:08:24 02/12/2014
  • 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ó.

    – Ngô Tuấn Sĩ 08:28:02 11/06/2017
112

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

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

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

    – Võ Lưu Ly 04:30:23 01/04/2015
  • 1

    Người đàn ông Merci! điều đó đã làm công việc cho tôi rất tuyệt vời. Tôi đã sử dụng nó trong một thời gian rồi. bị mắc kẹt trong một dự án vue theo như tôi biết vue không có chức năng tích hợp để sao chép một đối tượng như góc cạnh có (angular.copy)

    – Đỗ Viết Sơn 10:08:39 16/05/2018
  • 1

    Để 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, 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.

    – Trần Bích Chiêu 19:43:49 09/04/2019
74

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 phải là 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 hoạt động 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

|
37

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à để JSON mã hóa đố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ã 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
|
  • 1

    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 ...

    – Hồ Ái Như 20:37:17 29/10/2009
  • 1

    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à 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.

    – Võ Lưu Ly 10:27:00 30/10/2009
  • 1

    IMHO câu trả lời tốt nhất, bởi vì OP tuyên bố các đối tượng "được xây dựng theo nghĩa đen"

    – Đỗ Viết Sơn 11:35:53 04/10/2011
  • 1

    Không có gì không phù hợp hơn việc sử dụng từ không liên quan.

    – Trần Bích Chiêu 20:18:54 26/01/2012
  • 1

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

    – Ngô Tuấn Sĩ 01:58:39 14/08/2012
35

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 dựa 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
|
31

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', ...]

    – Lê Hồng Sơn 08:23:15 14/04/2016
  • 1

    Đ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 đến các đối tượng khác vẫn là tài liệu tham khảo!

    – Trịnh Khánh Thy 13:01:04 17/05/2016
  • 1

    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}

    – Lý Mỹ Oanh 17:37:39 13/08/2018
23

Đố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 ...

|
21

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.

|
18

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

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/

|
  • 1

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

    – Lý Trúc Vân 05:31:46 01/06/2017
  • 1

    Đó 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".

    – Doan Noop 16:05:02 16/08/2017
  • 1

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

    – Hồ Chung Thủy 11:00:04 01/09/2017
  • 1

    @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.

    – Tạ Hải Mỹ 11:40:11 01/09/2017
  • 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.

    – Lý Hồng Vinh 12:50:01 01/09/2017

Câu trả lời của bạn (> 20 ký tự)

Bằng cách click "Đăng trả lời", bạn đồng ý với Điều khoản dịch vụ, Chính sách bảo mật and Chính sách cookie của chúng tôi.

Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ hoặc hỏi câu hỏi của bạn.