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


Tôi có một vật, x. Tôi muốn sao chép nó như là đối tượng y, như vậy sẽ thay đổi thành y không sửa đổi x. Tôi nhận ra rằng việc sao chép các đối tượng bắt nguồn từ các đối tượng JavaScript dựng sẵn sẽ dẫn đến các đặc tính không mong muốn, bổ sung. Đây không phải là vấn đề, vì tôi đang sao chép một trong những vật thể đượ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 đối tượng JavaScript?


2403


gốc


Xem câu hỏi này: stackoverflow.com/questions/122102/… - Niyaz
Đối với JSON, tôi sử dụng mObj=JSON.parse(JSON.stringify(jsonObject)); - Lord Loh.
Tôi thực sự không hiểu tại sao không ai gợi ý Object.create(o)nó làm mọi thứ tác giả hỏi? - froginvasion
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2;  Sau khi làm điều này, y.deep.key cũng sẽ là 2, do đó Object.create KHÔNG THỂ ĐƯỢC SỬ DỤNG để nhân bản ... - Ruben Stolk
@ 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


Các câu trả lời:


Đã cập nhật câu trả lời

Chỉ dùng Object.assign () như đề xuất đây

Nhưng lưu ý rằng điều này làm cho một bản sao nông chỉ. Các đối tượng lồng nhau vẫn được sao chép dưới dạng tham chiếu.


Câu trả lời lỗi thời

Để 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 đề về việc lấy sai các thuộc tính từ nguyên mẫu của đối tượng cần được để trong nguyên mẫu và không được sao chép sang cá thể mới. Ví dụ: nếu bạn đang thêm clone phương pháp để Object.prototype, như một số câu trả lời mô tả, bạn sẽ cần phải bỏ qua thuộc tính đó một cách rõ ràng. Nhưng nếu có những 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? 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 có sẵn, không thuộc tính với hasOwnProperty phương pháp.

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ó các thuộc tính ẩn. Ví dụ, prototype là một thuộc tính ẩn của một hàm. Ngoài ra, nguyên mẫu của đố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 vòng lặp for / in lặp qua thuộc tính của đối tượng nguồn. Tôi nghĩ __proto__ có thể là cụ thể đối với trình thông dịch JavaScript của Firefox và nó có thể khác với các trình duyệt khác, nhưng bạn sẽ có được hình ảnh. Không phải mọi thứ đều có thể đếm được. 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 phát hiện nó.

Tuy nhiên, một snag trong nhiệm vụ cho một giải pháp thanh lịch 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, sau đó chỉ cần tạo một đối tượng chung mới với {} sẽ hoạt động, nhưng nếu nguyên mẫu của nguồn là một số hậu duệ của Object, sau đó 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 cách sử dụng hasOwnProperty bộ lọc, hoặc được trong nguyên mẫu, nhưng không được liệt kê ở nơi đầu tiên. Một giải pháp có thể là gọi đối tượng nguồn constructor tài sản để có được các đối tượng bản sao ban đầu và sau đó sao chép trên các thuộc tính, nhưng sau đó bạn vẫn sẽ không nhận đượ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ó như là 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 cho d1 sẽ mất 5 giây sau d2. Một cách để làm cho một Date giống như người khác là bằng cách gọi setTime phương pháp, nhưng đó là cụ thể cho Date lớp học. 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 bằng cách giả định rằng tôi chỉ cần sao chép một đồng bằng Object, Array, Date, String, Number, hoặc là Boolean. 3 loại cuối cùng là không thay đổi, 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ề nó thay đổi. Tôi tiếp tục giả định rằng bất kỳ yếu tố nào có trong Object hoặc là Array cũ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ẽ làm việc đầy đủ cho 6 kiểu đơ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. Tức 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ẽ làm việc cho bất cứ thứ gì bạn ném vào nó.


1313



hầu hết làm việc tốt trong một nodejs - chỉ cần thay đổi dòng (var i = 0, var len = obj.length; i <len; ++ i) {cho (var i = 0; i <obj.length; + + i) { - Trindaz
Đối với các googlers trong tương lai: cùng một bản sao sâu, chuyển các tham chiếu một cách đệ quy thay vì sử dụng các câu lệnh 'return' tại gist.github.com/2234277 - Trindaz
Ngày nay sẽ JSON.parse(JSON.stringify([some object]),[some revirer function]) là một giải pháp? - KooiInc
Trong đoạn đầu tiên, bạn có chắc chắn không nên var cpy = new obj.constructor()? - cyon
Việc gán đối tượng dường như không tạo bản sao thực sự trong Chrome, duy trì tham chiếu đến đối tượng gốc - kết thúc bằng cách sử dụng JSON.stringify và JSON.parse để sao chép - hoạt động hoàn hảo - 1owk3y


Với jQuery, bạn có thể bản sao nông với mở rộng:

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

các thay đổi tiếp theo đối với tệp copyObject sẽ không ảnh hưởng đến originalObject và ngược lại.

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

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

713



hoặc thậm chí: var copiedObject = jQuery.extend({},originalObject); - Grant McLean
Cũng hữu ích để xác định đúng là tham số đầu tiên cho bản sao sâu: jQuery.extend(true, {}, originalObject); - Will Shaver
Có, tôi thấy liên kết này hữu ích (cùng một giải pháp như Pascal) stackoverflow.com/questions/122102/… - Garry English
@Will Shaver - CÓ! Đó là nó! Nếu không có tùy chọn sao chép sâu nó đã không làm việc cho tôi! - thorinkor
Chỉ cần một lưu ý, điều này không sao chép proto constructor của đối tượng gốc - Sam Jones


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

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

Điều này làm việc 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 sao chép có cấu trúc của 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 cho nhân bản sâu.


692



Lưu ý rằng điều này chỉ có thể được sử dụng để thử nghiệm. Thứ nhất, nó là xa tối ưu về thời gian và tiêu thụ bộ nhớ. Thứ hai, không phải tất cả các trình duyệt đều có phương thức này. - Nux
@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 pháp này chậm hơn copy nông (trên một đối tượng sâu) là phương pháp này, theo định nghĩa, các bản sao sâu. Nhưng vì JSON được thực hiện bằng mã gốc (trong hầu hết các trình duyệt), đ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 nào khác dựa trên javascript và đôi khi có thể nhanh hơn kỹ thuật sao chép nông dựa trên javascript (xem: jsperf.com/cloning-an-object/79). " stackoverflow.com/questions/122102/… - BeauCielBleu
Tôi chỉ muốn thêm bản cập nhật vào tháng 10 năm 2014. Chrome 37+ nhanh hơn với JSON.parse (JSON.stringify (oldObject)); Lợi ích của việc sử dụng này là nó rất dễ dàng cho một công cụ javascript để xem và tối ưu hóa vào một cái gì đó tốt hơn nếu nó muốn. - mirhagk
Nó sẽ crap trên tất cả các JSON nếu đối tượng có un-stringify-có thể những thứ như vô cực, chưa xác định, vv Hãy thử đối tượng này: a = { b: Infinity, c: undefined } - kumar_harsh
Cập nhật năm 2016: Điều này nên bây giờ làm việc trong khá nhiều 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ờ là liệu nó có đủ hiệu quả hay không. - James Foster


Trong ECMAScript 6 có Object.assign method, 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 đối tượng này sang đối tượng khác. Ví dụ:

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

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


509



Vâng, tôi tin rằng Object.assign là con đường để đi. Thật dễ dàng để lấp đầy nó quá: gist.github.com/rafaelrinaldi/43813e707970bd2d77fa - Rafael
Nhưng lưu ý rằng điều này làm cho một bản sao nông chỉ. Các đối tượng lồng nhau vẫn được sao chép dưới dạng tham chiếu! - ohager
Cũng lưu ý rằng điều này sẽ sao chép qua "phương thức" được xác định thông qua các đối tượng literals (vì những là đếm được) nhưng không phải các phương thức đã thách thức thông qua cơ chế "lớp" (vì các phương thức này không phải có thể đếm được). - Marcus Junius Brutus
Tôi nghĩ rằng 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 nó. - Saulius
Điều này cũng giống như @EugeneTiurin câu trả lời của anh ấy. - Wilt


Có nhiều câu trả lời, nhưng không có câu trả lời nào Object.create từ ECMAScript 5, mà phải 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ế đó 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 xem xét giải pháp này để vượt trội? Đó là bản địa, do đó không có vòng lặp, không có đệ quy. Tuy nhiên, các trình duyệt cũ hơn sẽ cần một polyfill.


114



Lưu ý: Object.create không phải là một bản sao sâu (itpastorn đề cập không đệ 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
Đây là sự thừa kế nguyên mẫu, không nhân bản. Đây là những thứ 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 của nhân bản là tạo một đối tượng mới không tham chiếu bất kỳ thuộc tính nào trong một đối tượng khác. - d13
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 có thể là 'dự định'. Nhưng hãy đến với mọi người, 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à họ là tất cả "blah" cho bạn, nhưng họ là trong thực tế rất hữu ích nếu bạn biết những gì bạn đang làm. - froginvasion
@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/Cloning_(programming). Object.create trỏ đến các thuộc tính của cha mẹ thông qua các tham chiếu. Điều đó có nghĩa là nếu giá trị thuộc tính của cha mẹ thay đổi, con của bạn 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 lồng nhau và các đối tượng 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 chúng: jsbin.com/EKivInO/2. Một đối tượng nhân bản là một đối tượng độc lập hoàn toàn mới có các thuộc tính và giá trị giống như cha mẹ, nhưng không được kết nối với phụ huynh. - d13
Đ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 là hư không gần với nó. - prajnavantha


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

An Object.assign 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 đến một đối tượng đích.

Đọc thêm...

Các polyfill để hỗ trợ các 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;
    }
  });
}

105



Xin lỗi vì câu hỏi ngớ ngẩn, nhưng tại sao Object.assign lấy hai tham số khi value chức năng trong polyfill chỉ mất một tham số? - Qwertie
@Qwertie ngày hôm qua Tất cả các đối số được lặp đi lặp lại và sáp nhập vào một đối tượng, ưu tiên các thuộc tính từ cuối cùng thông qua arg - Eugene Tiurin
Oh tôi hiểu rồi, cảm ơn (tôi không quen thuộc với arguments đối tượng trước đây.) Tôi đang gặp khó khăn trong việc tìm kiếm Object() thông qua Google ... đó là một kiểu chữ, phải không? - Qwertie
điều này sẽ chỉ thực hiện một "nhân bản" nông - Marcus Junius Brutus


Mỗi MDN:

  • Nếu bạn muốn bản sao 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 bạn cần phải kiểm tra khả năng tương thích với trình duyệt trước.


105



JSON.parse (JSON.stringify (a)) trông đẹp, nhưng trước khi sử dụng nó, tôi khuyên bạn nên điểm chuẩn thời gian cần 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
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 tháng. Phải đối phó với niềm vui của các múi giờ trong Javascript và tự sửa bất kỳ ngày nào. Có thể là các trường hợp tương tự cho các loại khác ngoài ngày tháng - Steve Seeger
cho Date, tôi sẽ sử dụng moment.js vì nó có clone chức năng. xem thêm tại đây momentjs.com/docs/#/parsing/moment-clone - Tareq


Nếu bạn đồng ý với bản sao nông, thư viện underscore.js có bản sao phương pháp.

y = _.clone(x);

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

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

70



Và lodash có một cloneDeep - dule
Cảm ơn. Sử dụng kỹ thuật này trên máy chủ Meteor. - Turbo
Merci người đàn ông! điều đó đã làm công việc cho tôi tuyệt vời của tôi, tôi đã sử dụng nó trong một thời gian. đã bị mắc kẹt trong một dự án vue như xa như tôi biết vue không có được xây dựng trong chức năng để sao chép một đối tượng như góc không có (angular.copy) - Arnaud Bouchot


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 theo dõi, trong đó bao gồm, tại sao câu trả lời được chấp nhận không được chấp nhận.

tình hình bắt đầu

tôi muốn bản sao sâu một Javascript Object với tất cả các con cái của nó và con cái của họ và như vậy. Nhưng vì tôi không phải là một nhà phát triển bình thường, Object có bình thường  properties, circular structures và ngay cả nested objects.

Vì vậy, hãy tạo một circular structure và một nested object Đầu tiên.

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

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

Hãy mang mọi thứ lại với nhau trong một Object có 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 a vào một biến có tên b và 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 xuố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ờ chúng ta hãy tìm một giải pháp.

JSON

Nỗ lực đầu tiên tôi đã thử đã 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 vào nó, bạn sẽ nhận được TypeError: Converting circular structure to JSON.

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

Chúng ta hãy nhìn vào 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 structures không hoạt động tốt với nhau ... RangeError: Maximum call stack size exceeded

giải pháp gốc

Sau khi tranh luận với đồng nghiệp của tôi, ông chủ của tôi hỏi chúng tôi chuyện gì đã xảy ra, và anh ấy đã tìm thấy một đơn giản dung dịch sau khi một số googling. 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 số 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 gốc

Có một polyfill cho Object.create trong trình duyệt cũ giống như IE 8. Đó là một cái gì đó như đề nghị của Mozilla, và tất nhiên, nó không phải là hoàn hảo và kết quả trong cùng một vấn đề 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 đã để F bên ngoài phạm vi để chúng tôi có thể xem instanceof nói với chúng tôi.

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

Cùng một vấn đề với giải pháp gốc, nhưng đầu ra có một chút tồi tệ hơn.

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

Khi tìm hiểu 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 thế nào để tránh một chu kỳ, do một tài sản là "này"?) với cá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à chúng ta 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 khớp, nhưng vẫn còn một số vấn đề nhỏ hơn, bao gồm cả việc thay đổi instance của nested và circ đến Object.

Cấu trúc cây chia sẻ một chiếc lá sẽ không được sao chép, chúng sẽ trở thành hai 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à thực bản sao sâu của đối tượng. Nó xử lý đơn giản properties, circular structures và nested object, nhưng nó sẽ làm rối loạn bản sao của chúng trong khi nhân bản.

http://jsfiddle.net/einfallstoll/N4mr2/


63



vì vậy việc kết hợp là để tránh vấn đề đó :) - mikus
@mikus cho đến khi có thực đặc điểm kỹ thuật không chỉ bao gồm các trường hợp sử dụng cơ bản, vâng. - Fabio Poloni
Một phân tích ổn định 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ả chỉ ra rằng không có giải pháp cho câu hỏi này. - Amir Mog
Thật đáng xấu hổ khi JS không bao gồm chức năng bản sao bản địa. - l00k
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 là gần đúng. - KTU


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 luận là JSON mã hóa đối tượng đích 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 như bạn muốn tạo bao nhiêu bản sao tùy ý.

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

Phương pháp này là 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 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 bản sao trạng thái ban đầu của đối tượng để 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

36



Tại sao các hàm không thuộc về JSON? Tôi đã nhìn thấy chúng được chuyển thành JSON nhiều hơn rồi một lần ... - the_drow
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 chắn về hành vi của người khác. - Kris Walker
IMHO câu trả lời tốt nhất, bởi vì OP nói các đối tượng "được xây dựng theo nghĩa đen" - mark
Không có gì không phù hợp hơn việc sử dụng từ không hòa đồng. - Kevin Laity
@dấu: { 'foo': function() { return 1; } } là một đối tượng được xây dựng theo nghĩa đen. - abarnert