Cách hiệu quả nhất để sao chép sâu một đối tượng trong JavaScript là gì?


5180

Cách hiệu quả nhất để sao chép một đối tượng JavaScript là gì? Tôi đã thấy obj = eval(uneval(o));đang được sử dụng, nhưng điều đó không chuẩn và chỉ được Firefox hỗ trợ .

Tôi đã làm những việc như thế obj = JSON.parse(JSON.stringify(o));nhưng đặt câu hỏi về hiệu quả.

Tôi cũng đã thấy các chức năng sao chép đệ quy với các lỗi khác nhau.
Tôi ngạc nhiên không có giải pháp kinh điển tồn tại.


566
Eval không xấu xa. Sử dụng eval kém là được. Nếu bạn sợ tác dụng phụ của nó, bạn đang sử dụng sai. Các tác dụng phụ bạn sợ là lý do để sử dụng nó. Có ai đã thực sự trả lời câu hỏi của bạn không?
James

15
Đối tượng nhân bản là một công việc khó khăn, đặc biệt là với các đối tượng tùy chỉnh của các bộ sưu tập tùy ý. Mà có lẽ tại sao không có cách nào khác để làm điều đó.
b01

12
eval()nói chung là một ý tưởng tồi vì nhiều trình tối ưu hóa của công cụ Javascript phải tắt khi xử lý các biến được đặt quaeval . Chỉ cần có eval()mã của bạn có thể dẫn đến hiệu suất tồi tệ hơn.
user56reinstatemonica8


12
Lưu ý rằng JSONphương thức sẽ mất bất kỳ loại Javascript nào không có tương đương trong JSON. Ví dụ: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))sẽ tạo{a: null, b: null, c: null, g: false}
oriadam

Câu trả lời:


4731

Nhân bản sâu bản địa

Nó được gọi là "nhân bản có cấu trúc", hoạt động thử nghiệm trong Nút 11 trở đi và hy vọng sẽ xuất hiện trong các trình duyệt. Xem câu trả lời này để biết thêm chi tiết.

Nhân bản nhanh với mất dữ liệu - JSON.parse / stringify

Nếu bạn không sử dụng Dates, chức năng, undefined, Infinity, regexps, Maps, Sets, Blobs, FileLists, ImageDatas, Mảng thưa thớt, Chuỗi đã nhập hoặc các loại phức tạp khác trong đối tượng của bạn, một liner rất đơn giản để bản sao sâu một đối tượng 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'
  re: /.*/,  // lost
}
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()

Xem câu trả lời của Corban cho điểm chuẩn.

Nhân bản đáng tin cậy bằng thư viện

Vì các đối tượng nhân bản không phải là nhỏ (các kiểu phức tạp, tham chiếu vòng tròn, hàm, v.v.), nên hầu hết các thư viện chính đều cung cấp chức năng cho các đối tượng nhân bản. Đừng phát minh lại bánh xe - nếu bạn đã sử dụng thư viện, hãy kiểm tra xem nó có chức năng nhân bản đối tượng không. Ví dụ,

ES6

Để đầy đủ, lưu ý rằng ES6 cung cấp hai cơ chế sao chép nông: Object.assign()cú pháp lây lan . trong đó sao chép các giá trị của tất cả các thuộc tính riêng từ vô số đối tượng này sang đối tượng khác. Ví dụ:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js ở dòng 276 (có một chút mã làm một cái gì đó khác nhưng mã cho "cách làm điều này trong JS" là có :)
Rune FS

7
Đây là mã JS đằng sau bản sao sâu của jQuery, cho bất kỳ ai quan tâm: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
Ái chà! Rất rõ ràng: không biết tại sao câu trả lời này được chọn là câu trả lời đúng, đây là câu trả lời cho câu trả lời được đưa ra dưới đây: stackoverflow.com/a/122190/6524 (được khuyến nghị .clone(), không phải là mã đúng sử dụng trong bối cảnh này). Thật không may, câu hỏi này đã trải qua rất nhiều lần sửa đổi, cuộc thảo luận ban đầu thậm chí không còn rõ ràng nữa! Vui lòng chỉ làm theo lời khuyên của Corban và viết một vòng lặp hoặc sao chép các thuộc tính trực tiếp sang một đối tượng mới, nếu bạn quan tâm đến tốc độ. Hoặc tự kiểm tra nó!
John Resig

9
Đây là một câu hỏi JavaScript (không đề cập đến jQuery).
gphilip

60
Làm thế nào một người có thể làm điều này mà không sử dụng jQuery?
Awesomeness01

2266

Kiểm tra điểm chuẩn này: http://jsben.ch/#/bWfk9

Trong các thử nghiệm trước đây của tôi, nơi tốc độ là mối quan tâm chính mà tôi tìm thấy

JSON.parse(JSON.stringify(obj))

là cách chậm nhất để sao chép sâu một đối tượng (nó chậm hơn jQuery.extend với deepcờ được đặt đúng 10-20%).

jQuery.extend khá nhanh khi deepcờ được đặt thành false(bản sao nông). Đây là một tùy chọn tốt, vì nó bao gồm một số logic bổ sung để xác thực loại và không sao chép các thuộc tính không xác định, v.v., nhưng điều này cũng sẽ làm bạn chậm lại một chút.

Nếu bạn biết cấu trúc của các đối tượng bạn đang cố sao chép hoặc có thể tránh các mảng lồng nhau sâu, bạn có thể viết một for (var i in obj)vòng lặp đơn giản để sao chép đối tượng của mình trong khi kiểm tra hasOwnProperty và nó sẽ nhanh hơn nhiều so với jQuery.

Cuối cùng, nếu bạn đang cố gắng sao chép cấu trúc đối tượng đã biết trong một vòng lặp nóng, bạn có thể nhận được NHIỀU HIỆU SUẤT HƠN bằng cách đơn giản là lót thủ tục nhân bản và xây dựng đối tượng theo cách thủ công.

Các công cụ theo dõi JavaScript hút tối ưu hóa for..incác vòng lặp và kiểm tra hasOwnProperty cũng sẽ làm bạn chậm lại. Thủ công nhân bản khi tốc độ là phải tuyệt đối.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Cẩn thận khi sử dụng JSON.parse(JSON.stringify(obj))phương thức trên Datecác đối tượng - JSON.stringify(new Date())trả về một chuỗi đại diện cho ngày ở định dạng ISO, JSON.parse() không chuyển đổi trở lại Dateđối tượng. Xem câu trả lời này để biết thêm chi tiết .

Ngoài ra, xin lưu ý rằng, ít nhất trong Chrome 65, nhân bản gốc không phải là hướng đi. Theo JSPerf, việc thực hiện nhân bản gốc bằng cách tạo một hàm mới chậm hơn gần 800 lần so với sử dụng JSON.opesify, tốc độ cực nhanh trên mọi nẻo đường.

Cập nhật cho ES6

Nếu bạn đang sử dụng Javascript ES6, hãy thử phương pháp gốc này để sao chép hoặc sao chép nông.

Object.assign({}, obj);

4
@trysis Object.create không nhân bản đối tượng, đang sử dụng đối tượng nguyên mẫu ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser

105
Phương pháp này cũng sẽ loại bỏ keyskhỏi giá trị của bạn object, có functionsgiá trị của chúng, vì các hàm JSONkhông hỗ trợ.
Karlen Kishmiryan

39
Ngoài ra, hãy nhớ rằng việc sử dụng JSON.parse(JSON.stringify(obj))vào Đối tượng ngày cũng sẽ chuyển đổi ngày trở lại thành UTC theo cách biểu diễn chuỗi theo định dạng ISO8601 .
dnlgmzddr

31
Cách tiếp cận JSON cũng bóp nghẹt các tham chiếu tròn.
người làm giàu giàu có

28
@velop, Object.assign ({}, objToClone) có vẻ như là một bản sao nông cạn - sử dụng nó trong khi chơi xung quanh trong bảng điều khiển công cụ dev, đối tượng nhân bản vẫn chỉ vào một tham chiếu của đối tượng nhân bản. Vì vậy, tôi không nghĩ rằng nó thực sự áp dụng ở đây.
Garrett Simpson

473

Giả sử rằng bạn chỉ có các biến và không có bất kỳ chức năng nào trong đối tượng của mình, bạn chỉ có thể sử dụng:

var newObject = JSON.parse(JSON.stringify(oldObject));

86
Con của cách tiếp cận này như tôi vừa tìm thấy là nếu đối tượng của bạn có bất kỳ chức năng nào (của tôi có getters & setters nội bộ) thì chúng sẽ bị mất khi được
xâu chuỗi

31
@Jason, Lý do tại sao phương pháp 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 pháp này, theo định nghĩa, các bản sao sâu. Nhưng vì JSONđược triển khai bằng mã gốc (trong hầu hết các trình duyệt), nên việc này sẽ nhanh hơn đáng kể so với việc 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 ).
MiJyn

35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer

32
kỹ thuật này cũng sẽ phá hủy tất cả Datecác đối tượng được lưu trữ bên trong đối tượng, chuyển đổi chúng thành dạng chuỗi.
fstab

13
Nó sẽ không sao chép bất cứ thứ gì không phải là một phần của thông số JSON ( json.org )
cdmckay

397

Nhân bản cấu trúc

Tiêu chuẩn HTML bao gồm thuật toán nhân bản / tuần tự hóa có cấu trúc bên trong có thể tạo ra các bản sao sâu của các đối tượng. Nó vẫn bị giới hạn ở một số loại tích hợp nhất định, nhưng ngoài một số loại được JSON hỗ trợ, nó còn hỗ trợ Dates, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, Mảng thưa thớt, Mảng được gõ và có thể nhiều hơn trong tương lai . Nó cũng bảo tồn các tham chiếu trong dữ liệu nhân bản, cho phép nó hỗ trợ các cấu trúc đệ quy và đệ quy có thể gây ra lỗi cho JSON.

Hỗ trợ trong Node.js: Thử nghiệm

Các v8mô-đun trong hiện Node.js (tính Node 11) cho thấy nhiều serialization API cấu trúc trực tiếp , nhưng chức năng này vẫn được đánh dấu là "thử nghiệm", và phụ thuộc vào sự thay đổi hoặc loại bỏ trong các phiên bản trong tương lai. Nếu bạn đang sử dụng một phiên bản tương thích, việc nhân bản một đối tượng cũng đơn giản như:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Hỗ trợ trực tiếp trong trình duyệt: Có thể cuối cùng? 😐

Các trình duyệt hiện không cung cấp giao diện trực tiếp cho thuật toán nhân bản có cấu trúc, nhưng một structuredClone()hàm toàn cục đã được thảo luận trong whatwg / html # 793 trên GitHub . Như hiện tại đề xuất, sử dụng nó cho hầu hết các mục đích sẽ đơn giản như:

const clone = structuredClone(original);

Trừ khi điều này được vận chuyển, việc triển khai bản sao có cấu trúc của trình duyệt chỉ được phơi bày gián tiếp.

Cách giải quyết không đồng bộ: Có thể sử dụng. 😕

Cách chi phí thấp hơn để tạo bản sao có cấu trúc với các API hiện có là đăng dữ liệu qua một cổng của MessageChannels . Các cổng khác sẽ phát ra một messagesự kiện với một bản sao có cấu trúc của đính kèm .data. Thật không may, lắng nghe những sự kiện này nhất thiết là không đồng bộ, và các lựa chọn thay thế đồng bộ ít thực tế hơn.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Ví dụ sử dụng:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Cách giải quyết đồng bộ: Tuyệt vời! 🤢

Không có tùy chọn tốt để tạo bản sao có cấu trúc đồng bộ. Dưới đây là một vài hack không thực tế thay thế.

history.pushState()history.replaceState()cả hai tạo một bản sao có cấu trúc của đối số đầu tiên của chúng và gán giá trị đó cho history.state. Bạn có thể sử dụng điều này để tạo một bản sao có cấu trúc của bất kỳ đối tượng nào như thế này:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Ví dụ sử dụng:

Mặc dù đồng bộ, điều này có thể cực kỳ chậm. Nó phải chịu tất cả các chi phí liên quan đến thao túng lịch sử trình duyệt. Gọi phương thức này nhiều lần có thể khiến Chrome tạm thời không phản hồi.

Các Notificationconstructor tạo ra một bản sao cấu trúc dữ liệu liên quan của nó. Nó cũng cố gắng hiển thị thông báo trình duyệt cho người dùng, nhưng điều này sẽ âm thầm thất bại trừ khi bạn đã yêu cầu quyền thông báo. Trong trường hợp bạn có quyền cho các mục đích khác, chúng tôi sẽ ngay lập tức đóng thông báo chúng tôi đã tạo.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Ví dụ sử dụng:


3
@rynah Tôi chỉ nhìn qua spec một lần nữa và bạn nói đúng: sự history.pushState()history.replaceState()cả phương pháp đồng bộ thiết lập history.stateđể một bản sao cấu trúc của các đối số đầu tiên của họ. Một chút kỳ lạ, nhưng nó hoạt động. Tôi đang cập nhật câu trả lời của tôi bây giờ.
Jeremy Banks

40
Điều này thật sai lầm! API đó không có nghĩa là được sử dụng theo cách này.
Fardin K.

209
Là người đã triển khai PushState trong Firefox, tôi cảm thấy một sự pha trộn kỳ lạ của niềm tự hào và sự nổi loạn ở bản hack này. Làm tốt lắm, các bạn.
Justin L.

PushState hoặc thông báo hack không hoạt động đối với một số loại đối tượng như Chức năng
Shishir Arora

323

Nếu không có bất kỳ nội dung nào, bạn có thể thử:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

20
Giải pháp JQuery sẽ hoạt động cho các phần tử DOM nhưng không phải bất kỳ Đối tượng nào. Mootools có cùng giới hạn. Ước gì họ có một "bản sao" chung cho bất kỳ đối tượng nào ... Giải pháp đệ quy sẽ hoạt động cho mọi thứ. Có lẽ đó là con đường để đi.
jschrab

5
Hàm này bị phá vỡ nếu đối tượng được nhân bản có hàm tạo yêu cầu tham số. Có vẻ như chúng ta có thể thay đổi nó thành "var temp = new Object ()" và nó có hoạt động trong mọi trường hợp không?
Andrew Arnott

3
Andrew, nếu bạn thay đổi nó thành var temp = new Object (), thì bản sao của bạn sẽ không có cùng nguyên mẫu với đối tượng ban đầu. Hãy thử sử dụng: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
hạn

1
Tương tự như câu trả lời của limscoder, hãy xem câu trả lời của tôi bên dưới về cách thực hiện việc này mà không cần gọi hàm tạo: stackoverflow.com/a/13333781/560114
Matt Browne

3
Đối với các đối tượng chứa tham chiếu đến các phần phụ (tức là mạng của các đối tượng), điều này không hoạt động: Nếu hai tham chiếu trỏ đến cùng một đối tượng phụ, bản sao chứa hai bản sao khác nhau của nó. Và nếu có các tham chiếu đệ quy, hàm sẽ không bao giờ chấm dứt (tốt, ít nhất là không theo cách bạn muốn :-) Đối với các trường hợp chung này, bạn phải thêm một từ điển các đối tượng đã được sao chép và kiểm tra xem bạn đã sao chép chưa ... Lập trình rất phức tạp khi bạn sử dụng một ngôn ngữ đơn giản
virtualnobi

153

Cách hiệu quả để sao chép (không phải bản sao sâu) một đối tượng 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;
    }
  });
}

82
Điều này không sao chép đệ quy vì vậy không thực sự cung cấp một giải pháp cho vấn đề nhân bản một đối tượng.
mwhite

5
Phương pháp này hoạt động, mặc dù tôi đã thử nghiệm một vài và _.extend ({}, (obj)) là BY FAR nhanh nhất: nhanh hơn 20 lần so với JSON.parse và nhanh hơn 60% so với Object.assign. Nó sao chép tất cả các đối tượng phụ khá tốt.
Nico

11
@mwhite có một sự khác biệt giữa bản sao và bản sao sâu. Câu trả lời này thực tế là nhân bản, nhưng nó không sâu sắc.
Meirion Hughes

57
Các op yêu cầu nhân bản sâu. Điều này không làm bản sao sâu.
dùng566245

9
Phương pháp này tạo một bản sao SHALLOW , và không phải là bản sao DEEP ! Bởi vì điều này là câu trả lời hoàn toàn sai !
Bharata

97

Mã số:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Kiểm tra:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

3
những gì về var obj = {}obj.a = obj
neaumusic

5
Tôi không hiểu chức năng này. Giả sử from.constructorDateví dụ. ifThử nghiệm thứ ba sẽ đạt được như thế nào khi ifthử nghiệm thứ 2 thành công & khiến hàm trở lại (kể từ đó Date != Object && Date != Array)?
Adam McKee

1
@AdamMcKee Bởi vì việc truyền đối số javascript và gán biến là khó khăn . Cách tiếp cận này hoạt động rất tốt, bao gồm cả ngày (thực sự được xử lý bởi thử nghiệm thứ hai) - fiddle để thử nghiệm ở đây: jsfiddle.net/zqv9q9c6 .
brichin

1
@NickSweeting: Hãy thử - có thể nó hoạt động. Nếu không - sửa nó và cập nhật câu trả lời. Đó là cách nó hoạt động ở đây trong cộng đồng :)
Kamarey

1
Hàm này không sao chép regex trong thử nghiệm, điều kiện "from.constructor! = Object && from.constructor! = Array" luôn trả về true cho các hàm tạo khác như Number, Date, v.v.
aMarCruz

95

Đây là những gì tôi đang sử dụng:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

8
Điều này có vẻ không đúng. cloneObject({ name: null })=>{"name":{}}
Niyaz

13
Điều này là do một thứ ngớ ngẩn khác trong javascript typeof null > "object"nhưng Object.keys(null) > TypeError: Requested keys of a value that is not an object.thay đổi điều kiện thànhif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us

Điều này sẽ gán các thuộc tính vô số được thừa kế của obj trực tiếp cho bản sao và giả định rằng obj là một Object đơn giản.
RobG

Điều này cũng làm rối các mảng, được chuyển đổi thành các đối tượng với các phím số.
lưỡi kiếm

Không thành vấn đề nếu bạn không sử dụng null.
Jorge Bucaran

78

Sao chép sâu theo hiệu suất: Xếp hạng từ tốt nhất đến kém nhất

  • Tái chỉ định "=" (mảng chuỗi, mảng số - chỉ)
  • Slice (mảng chuỗi, mảng số - chỉ)
  • Ghép nối (mảng chuỗi, mảng số - chỉ)
  • Hàm tùy chỉnh: for-loop hoặc bản sao đệ quy
  • $ .extend của jQuery
  • JSON.parse (mảng chuỗi, mảng số, mảng đối tượng - chỉ)
  • Underscore.js 's _.clone (mảng chuỗi, mảng số - chỉ)
  • _ -CloneDeep của Lo-Dash

Sao chép sâu một mảng các chuỗi hoặc số (một cấp - không có con trỏ tham chiếu):

Khi một mảng chứa các số và chuỗi - các hàm như .slice (), .concat (), .splice (), toán tử gán "=" và hàm clone của Underscore.js; sẽ tạo một bản sao sâu của các phần tử của mảng.

Trường hợp tái chỉ định có hiệu suất nhanh nhất:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

Và .slice () có hiệu suất tốt hơn .concat (), http://jsperf.com/d repeatate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Sao chép sâu một mảng các đối tượng (hai hoặc nhiều cấp - con trỏ tham chiếu):

var arr1 = [{object:'a'}, {object:'b'}];

Viết hàm tùy chỉnh (có hiệu suất nhanh hơn $ .extend () hoặc JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Sử dụng các chức năng tiện ích của bên thứ ba:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Trong đó $ .extend của jQuery có hiệu suất tốt hơn:


Tôi đã thử nghiệm một vài và _.extend ({}, (obj)) là BY FAR nhanh nhất: nhanh hơn 20 lần so với JSON.parse và nhanh hơn 60% so với Object.assign. Nó sao chép tất cả các đối tượng phụ khá tốt.
Nico

4
Tất cả các ví dụ của bạn là nông, một cấp độ. Đây không phải là một câu trả lời tốt. Câu hỏi liên quan đến nhân bản sâu tức là ít nhất hai cấp độ.
Karl Morrison

1
Một bản sao sâu là khi một đối tượng được sao chép toàn bộ mà không sử dụng các con trỏ tham chiếu đến các đối tượng khác. Các kỹ thuật trong phần "Sao chép sâu một mảng các đối tượng", chẳng hạn như jQuery.extend () và chức năng tùy chỉnh (được đệ quy) sao chép các đối tượng với "ít nhất hai cấp độ". Vì vậy, không phải tất cả các ví dụ đều là bản sao "một cấp".
tfmontague

1
Tôi thích chức năng sao chép tùy chỉnh của bạn, nhưng bạn nên loại trừ các giá trị null, nếu không tất cả các giá trị null đang được chuyển đổi thành các đối tượng, tức là:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi

2
@HossamMourad - Lỗi đã được Josi sửa vào ngày 1 tháng 2 (trong phần bình luận ở trên) và tôi đã không cập nhật chính xác câu trả lời. Xin lỗi rằng lỗi này dẫn đến một cấu trúc lại cơ sở mã của bạn.
tfmontague 20/03/18

64
var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});

Câu trả lời tốt nhưng điều này không thành công cho các tài liệu tham khảo tròn.
Lu-ca

59

Các đối tượng sao chép sâu trong JavaScript (Tôi nghĩ là tốt nhất và đơn giản nhất)

1. Sử dụng JSON.parse (JSON.opesify (object));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.Sử dụng phương thức đã tạo

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Sử dụng _.cloneDeep Lo-Dash của liên kết lodash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Sử dụng phương thức Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

NHƯNG SAU KHI

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Sử dụng Underscore.js _.clone link Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

NHƯNG SAU KHI

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

Sân chơi điểm chuẩn hiệu suất của JSBEN.CH 1 ~ 3 http://jsben.ch/KVQLd Hiệu suất Sao chép sâu các đối tượng trong JavaScript


5
Object.assign()không thực hiện một bản sao sâu
Roymunson

1
bạn nên thêm điểm chuẩn cho những điều này; điều đó sẽ rất hữu ích
jcollum

Khi tôi sử dụng "phương thức đã tạo" trên một đối tượng có chứa một mảng, tôi không thể sử dụng pop () hoặc splice () trên đó, tôi không hiểu tại sao? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();nó throw: TypeError: tmp.title.pop is not a function(dĩ nhiên pop () hoạt động tốt nếu tôi chỉ do let tmp = data; nhưng sau đó tôi không thể sửa đổi tmp mà không ảnh hưởng đến dữ liệu)
hugogogo 24/03/19

Này, ví dụ cuối cùng của bạn là sai. Theo tôi, bạn phải sử dụng _clone chứ không phải _cloneDeep cho ví dụ sai.
kenanyildiz

Phương thức được tạo này (2.) sẽ không hoạt động cho mảng, phải không?
Toivo Säwén

57

Có một thư viện (được gọi là bản sao clone , ) thực hiện điều này khá tốt. Nó cung cấp bản sao đệ quy / sao chép hoàn chỉnh nhất của các đối tượng tùy ý mà tôi biết. Nó cũng hỗ trợ các tài liệu tham khảo tròn, chưa được bao gồm trong các câu trả lời khác.

Bạn cũng có thể tìm thấy nó vào npm . Nó có thể được sử dụng cho trình duyệt cũng như Node.js.

Dưới đây là một ví dụ về cách sử dụng nó:

Cài đặt nó với

npm install clone

hoặc gói nó với Ender .

ender build clone [...]

Bạn cũng có thể tải mã nguồn theo cách thủ công.

Sau đó, bạn có thể sử dụng nó trong mã nguồn của bạn.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của thư viện.)


3
npm clone là vô giá đối với tôi để nhân bản các đối tượng lồng nhau tùy ý. Đây là câu trả lời đúng.
Andy Ray

hiệu suất của lib của bạn so với hãy nói là JSON.parse(JSON.stringify(obj))gì?
pkyeck

Đây là một thư viện nói rằng có các tùy chọn nhanh hơn. Mặc dù chưa được thử nghiệm.
pvorb

Giải pháp tốt và điều này hỗ trợ các tham chiếu vòng tròn (không giống như phân tích JSON)
Luke

55

Cloning Một đối tượng luôn là mối quan tâm trong JS, nhưng đó là tất cả trước ES6, tôi liệt kê các cách khác nhau để sao chép một đối tượng trong JavaScript bên dưới, hãy tưởng tượng bạn có Object bên dưới và muốn có một bản sao sâu sắc về điều đó:

var obj = {a:1, b:2, c:3, d:4};

Có một số cách để sao chép đối tượng này mà không thay đổi nguồn gốc:

1) ES5 +, Sử dụng chức năng đơn giản để thực hiện sao chép cho bạn:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 +, sử dụng JSON.parse và JSON.opesify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs & Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Hy vọng những sự giúp đỡ này ...


2
clone in undercore không phải là một bản sao sâu trong phiên bản hiện tại
Rogelio

Cảm ơn. có dưới dạng tài liệu mới cho Underscore ... clone_.clone (object) Tạo một bản sao được sao chép nông của đối tượng đơn giản được cung cấp. Bất kỳ đối tượng hoặc mảng lồng nhau sẽ được sao chép bằng tham chiếu, không trùng lặp. _.clone ({tên: 'moe'}); => {tên: 'moe'};
Alireza

59
Object.assignkhông không sâu sao chép. Ví dụ : var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Nếu đây là bản sao sâu, y.a.bvẫn sẽ c, nhưng bây giờ d.
kba

8
Object.assign () chỉ nhân bản cấp độ đầu tiên của thuộc tính!
haemse

5
Hàm cloneSO () là gì?
pastorello

53

Tôi biết đây là một bài viết cũ, nhưng tôi nghĩ rằng đây có thể là một số trợ giúp cho người tiếp theo vấp ngã.

Miễn là bạn không gán đối tượng cho bất cứ thứ gì nó không duy trì tham chiếu trong bộ nhớ. Vì vậy, để tạo một đối tượng mà bạn muốn chia sẻ giữa các đối tượng khác, bạn sẽ phải tạo một nhà máy như vậy:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

16
Câu trả lời này không thực sự phù hợp vì câu hỏi là: ví dụ b làm thế nào để tạo bản sao c WHILE không biết về nhà máy a hoặc không muốn sử dụng nhà máy a. Lý do người ta có thể không muốn sử dụng nhà máy là vì sau khi khởi tạo b có thể đã được khởi tạo với dữ liệu bổ sung (ví dụ: đầu vào của người dùng).
Noel Abrahams

12
Đúng là đây không thực sự là một câu trả lời cho câu hỏi, nhưng tôi nghĩ điều quan trọng là nó ở đây vì nó là câu trả lời cho câu hỏi mà tôi nghi ngờ nhiều người đến đây thực sự có ý nghĩa để hỏi.
Dấu chấm phẩy

8
Xin lỗi các bạn, tôi không thực sự hiểu lý do tại sao rất nhiều upvote. Nhân bản một đối tượng là một khái niệm khá rõ ràng, bạn tạo ra một đối tượng từ đối tượng KHÁC và nó không liên quan gì đến việc tạo một đối tượng mới với mô hình nhà máy.
khai mạc

2
Mặc dù điều này hoạt động cho các đối tượng được xác định trước, "nhân bản" theo cách này sẽ không nhận ra các thuộc tính mới được thêm vào đối tượng ban đầu. Nếu bạn tạo a, thêm thuộc tính mới vào a, sau đó tạo b. b sẽ không có tài sản mới. Về cơ bản, mô hình nhà máy là bất biến đối với các thuộc tính mới. Đây không phải là nhân bản vô tính. Xem: jsfiddle.net/jzumbrun/42xejnbx
Jon

1
Tôi nghĩ rằng đây là lời khuyên tốt, nói chung, vì thay vì sử dụng const defaultFoo = { a: { b: 123 } };bạn có thể đi const defaultFoo = () => ({ a: { b: 123 } };và vấn đề của bạn đã được giải quyết. Tuy nhiên, nó thực sự không phải là một câu trả lời cho câu hỏi. Nó có thể có ý nghĩa hơn khi bình luận về câu hỏi, không phải là một câu trả lời đầy đủ.
Josh từ Qaribou 6/2/2017

48

Nếu bạn đang sử dụng nó, thư viện Underscore.js có một phương thức sao chép .

var newObject = _.clone(oldObject);

24
lodash có một phương thức cloneDeep, nó cũng hỗ trợ một thông số khác để sao chép sâu hơn: lodash.com/docs#clonelodash.com/docs#cloneDeep
opensas

12
@opensas đồng ý. Lodash thường vượt trội so với gạch dưới
nha

7
Tôi ủng hộ việc xóa câu trả lời này và tất cả các câu trả lời khác chỉ là tham chiếu một dòng cho .clone(...)phương thức của thư viện tiện ích . Mỗi thư viện lớn sẽ có chúng và các câu trả lời ngắn gọn không chi tiết lặp đi lặp lại không hữu ích cho hầu hết khách truy cập, những người sẽ không sử dụng thư viện cụ thể đó.
Jeremy Banks

41

Đây là phiên bản câu trả lời của ConroyP ở trên, hoạt động ngay cả khi hàm tạo có các tham số bắt buộc:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Chức năng này cũng có sẵn trong thư viện đơn giản của tôi .

Biên tập:

Đây là phiên bản mạnh mẽ hơn (nhờ Justin McCandless, giờ đây nó cũng hỗ trợ các tài liệu tham khảo theo chu kỳ):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

30

Sau đây tạo ra hai trường hợp của cùng một đối tượng. Tôi tìm thấy nó và hiện đang sử dụng nó. Nó đơn giản và dễ sử dụng.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

Có điều gì sai với câu trả lời này? Nó hữu ích hơn khi là một giải pháp độc lập, nhưng đơn giản; nhưng giải pháp jQuery phổ biến hơn. Tại sao vậy?
ceremcem

Vâng, xin vui lòng cho tôi biết. Nó dường như đang hoạt động như dự định, nếu có một số phá vỡ ẩn ở đâu đó, tôi cần sử dụng một giải pháp khác.
nathan rogers

4
Đối với một đối tượng đơn giản, Chrome này chậm hơn khoảng 6 lần so với câu trả lời đã cho và chậm hơn rất nhiều khi độ phức tạp của đối tượng tăng lên. Nó quy mô khủng khiếp và có thể làm tắc nghẽn ứng dụng của bạn rất nhanh.
tic

1
Bạn không cần dữ liệu, chỉ cần hiểu những gì đang diễn ra. Kỹ thuật nhân bản này tuần tự hóa toàn bộ đối tượng thành một chuỗi, sau đó phân tích tuần tự chuỗi đó để xây dựng một đối tượng. Về cơ bản, điều đó sẽ chậm hơn rất nhiều so với việc sắp xếp lại một số bộ nhớ (đó là điều mà các bản sao tinh vi hơn làm). Nhưng với điều đó đã được nói, đối với các dự án vừa và nhỏ (tùy theo định nghĩa của bạn về "cỡ trung bình"), ai quan tâm nếu nó thậm chí kém hiệu quả hơn 1000 lần? Nếu các đối tượng của bạn nhỏ và bạn không sao chép chúng 1000 tấn thì hầu như không có gì vẫn gần như không có gì.
máy

3
Ngoài ra, phương thức này làm mất các phương thức (hoặc bất kỳ nội dung nào không được phép trong JSON), cộng với - JSON.opesify sẽ chuyển đổi các đối tượng Date thành chuỗi, ... và không theo cách khác;) Tắt giải pháp này.
Mr MT

22

Crockford gợi ý (và tôi thích) sử dụng chức năng này:

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

var newObject = object(oldObject);

Thật ngắn gọn, hoạt động như mong đợi và bạn không cần một thư viện.


BIÊN TẬP:

Đây là một polyfill cho Object.create, vì vậy bạn cũng có thể sử dụng này.

var newObject = Object.create(oldObject);

LƯU Ý: Nếu bạn sử dụng một số thứ này, bạn có thể gặp vấn đề với một số lần lặp sử dụng hasOwnProperty. Bởi vì, createtạo đối tượng trống mới người kế thừa oldObject. Nhưng nó vẫn hữu ích và thiết thực cho việc nhân bản các đối tượng.

Ví dụ nếu oldObject.a = 5;

newObject.a; // is 5

nhưng:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

9
sửa tôi nếu tôi sai, nhưng đó không phải là chức năng quên của Crockford đối với thừa kế nguyên mẫu? Làm thế nào để áp dụng để nhân bản?
Alex Nolasco

3
Vâng, tôi sợ cuộc thảo luận này: sự khác biệt thực tế giữa sao chép, sao chép và kế thừa nguyên mẫu, khi nào bạn nên sử dụng từng chức năng và trang nào trên trang này thực sự đang làm gì? Tôi tìm thấy trang SO này bằng cách googling "đối tượng sao chép javascript". Điều tôi thực sự tìm kiếm là chức năng trên, vì vậy tôi đã quay lại để chia sẻ. Tôi đoán là người hỏi cũng đang tìm kiếm điều này.
Chris Broski

51
Sự khác biệt giữa clone / copy và thừa kế là, bằng cách sử dụng ví dụ của bạn, khi tôi thay đổi một thuộc tính của oldObject, thuộc tính cũng được thay đổi trong newObject. Nếu bạn tạo một bản sao, bạn có thể làm những gì bạn muốn với oldObject mà không cần thay đổi newObject.
Vô lý

13
Điều này sẽ phá vỡ kiểm tra hasOwnProperty vì vậy đây là một cách khá hay để sao chép một đối tượng và sẽ cho bạn kết quả bất ngờ.
Corban Brook

var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inherribution-potypes
Cody

22

Lodash có một phương thức _.cloneDeep (value) đẹp :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

5
Tôi ủng hộ việc xóa câu trả lời này và tất cả các câu trả lời khác chỉ là tham chiếu một dòng cho .clone(...)phương thức của thư viện tiện ích . Mỗi thư viện lớn sẽ có chúng và các câu trả lời ngắn gọn không chi tiết lặp đi lặp lại không hữu ích cho hầu hết khách truy cập, những người sẽ không sử dụng thư viện cụ thể đó.
Jeremy Banks

Một cách dễ dàng hơn là sử dụng _.merge({}, objA). Nếu chỉ có lodash không làm biến đổi các đối tượng ở vị trí đầu tiên thì clonechức năng sẽ không cần thiết.
Hoàn trả

7
Google tìm kiếm để nhân bản các đối tượng JS tham khảo tại đây. Tôi đang sử dụng Lodash vì vậy câu trả lời này có liên quan đến tôi. Xin vui lòng không đi tất cả "wikipedia xóa" trên câu trả lời xin vui lòng.
Hoàn trả

2
Trong Nút 9, JSON.parse (JSON.opesify (mảngOf About5KFlatObjects)) nhanh hơn rất nhiều so với _.deepClone (mảngOfThe5KFlatObjects).
Dan Dascalescu

21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

17
Vấn đề với phương thức, đó là nếu bạn có các đối tượng phụ trong obj, các tham chiếu của chúng sẽ được sao chép và không phải là giá trị của mọi đối tượng phụ.
Kamarey

1
chỉ cần làm cho nó đệ quy để các đối tượng phụ sẽ được nhân bản sâu.
fiatjaf

chỉ tò mò ... không phải là biến nhân bản sẽ có con trỏ đến các thuộc tính của đối tượng ban đầu? bởi vì dường như không có phân bổ bộ nhớ mới
Rupesh Patel

3
Đúng. Đây chỉ là một bản sao nông, vì vậy bản sao sẽ trỏ đến chính các đối tượng được trỏ bởi đối tượng ban đầu.
Đánh dấu Cidade

Đây không phải là một câu trả lời. Bạn thực sự chỉ nhồi một đối tượng với các tham chiếu đến đối tượng khác. Thay đổi đối tượng nguồn sẽ thực hiện thay đổi đối với "bản sao".
Shawn Whinnery

19

Shallow copy one-liner ( phiên bản ECMAScript 5 ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Và bản sao một lớp lót ( ECMAScript phiên bản 6 , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

6
Điều này có thể tốt cho các đối tượng đơn giản, nhưng nó chỉ sao chép các giá trị thuộc tính. Nó không chạm vào chuỗi nguyên mẫu và bằng cách sử dụng Object.keysnó bỏ qua các thuộc tính không thể đếm được và được thừa kế. Ngoài ra, nó mất mô tả tài sản bằng cách thực hiện chuyển nhượng trực tiếp.
Matt Bierner

Nếu bạn cũng sao chép nguyên mẫu, bạn sẽ chỉ thiếu các mô tả thuộc tính và không liệt kê, đúng không? Khá tốt. :)
sam

Hiệu suất sang một bên, đây là một cách thực sự thuận tiện để sao chép nông một đối tượng. Tôi thường sử dụng điều này để sắp xếp các thuộc tính nghỉ giả trong một nhiệm vụ hủy hoại trong các thành phần React của tôi.
mjohnsonengr

17

Chỉ vì tôi không thấy AngularJS được đề cập và nghĩ rằng mọi người có thể muốn biết ...

angular.copy cũng cung cấp một phương pháp sao chép sâu các đối tượng và mảng.


hoặc nó có thể được sử dụng theo cách tương tự như jQiery mở rộng:angular.extend({},obj);
Galvani

2
@Galvani: Cần lưu ý rằng jQuery.extendangular.extendđều là bản sao nông. angular.copylà một bản sao sâu sắc.
Dan Atkinson

16

Dường như không có toán tử nhân bản sâu lý tưởng nào cho các đối tượng giống như mảng. Như đoạn mã dưới đây minh họa, trình sao chép jQuery của John Resig biến các mảng có các thuộc tính không phải là số thành các đối tượng không phải là mảng và trình sao chép JSON của RegDwight sẽ loại bỏ các thuộc tính không phải là số. Các thử nghiệm sau đây minh họa những điểm này trên nhiều trình duyệt:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

14
như những người khác đã chỉ ra trong các nhận xét cho câu trả lời của Resig, nếu bạn muốn sao chép một đối tượng giống như mảng, bạn thay đổi {} thành [] trong lệnh gọi mở rộng, ví dụ jQuery.extend (true, [], obj)
Anentropic

15

Tôi có hai câu trả lời tốt tùy thuộc vào mục tiêu của bạn là sao chép "đối tượng JavaScript cũ đơn giản" hay không.

Chúng ta cũng giả sử rằng ý định của bạn là tạo một bản sao hoàn chỉnh mà không có tham chiếu nguyên mẫu nào quay lại đối tượng nguồn. Nếu bạn không quan tâm đến một bản sao hoàn chỉnh, thì bạn có thể sử dụng nhiều thói quen Object.clone () được cung cấp trong một số câu trả lời khác (mẫu của Crockford).

Đối với các đối tượng JavaScript cũ đơn giản, một cách tốt và cố gắng để sao chép một đối tượng trong thời gian chạy hiện đại khá đơn giản:

var clone = JSON.parse(JSON.stringify(obj));

Lưu ý rằng đối tượng nguồn phải là một đối tượng JSON thuần túy. Điều này có nghĩa là, tất cả các thuộc tính lồng nhau của nó phải là vô hướng (như boolean, chuỗi, mảng, đối tượng, v.v.). Bất kỳ chức năng hoặc đối tượng đặc biệt như RegExp hoặc Date sẽ không được sao chép.

Có hiệu quả không? Heck vâng. Chúng tôi đã thử tất cả các loại phương pháp nhân bản và điều này hoạt động tốt nhất. Tôi chắc rằng một số ninja có thể gợi ra một phương pháp nhanh hơn. Nhưng tôi nghi ngờ chúng ta đang nói về lợi nhuận cận biên.

Cách tiếp cận này chỉ đơn giản và dễ thực hiện. Gói nó vào một chức năng tiện lợi và nếu bạn thực sự cần phải vắt kiệt một số lợi ích, hãy đi sau một thời gian.

Bây giờ, đối với các đối tượng JavaScript không đơn giản, không có câu trả lời thực sự đơn giản. Thực tế, không thể vì tính chất động của các hàm JavaScript và trạng thái đối tượng bên trong. Nhân bản sâu một cấu trúc JSON với các hàm bên trong yêu cầu bạn tạo lại các hàm đó và bối cảnh bên trong của chúng. Và JavaScript đơn giản là không có cách chuẩn hóa để làm điều đó.

Cách chính xác để làm điều này, một lần nữa, là thông qua một phương thức tiện lợi mà bạn khai báo và sử dụng lại trong mã của mình. Phương thức tiện lợi có thể được ban cho một số hiểu biết về các đối tượng của riêng bạn để bạn có thể đảm bảo tái tạo đúng biểu đồ trong đối tượng mới.

Chúng tôi tự viết, nhưng cách tiếp cận chung tốt nhất tôi từng thấy được đề cập ở đây:

http://davidwalsh.name/javascript-clone

Đây là ý tưởng đúng. Tác giả (David Walsh) đã bình luận về việc nhân bản các chức năng tổng quát. Đây là điều bạn có thể chọn để làm, tùy thuộc vào trường hợp sử dụng của bạn.

Ý tưởng chính là bạn cần xử lý đặc biệt việc khởi tạo các chức năng của bạn (hoặc các lớp nguyên mẫu, có thể nói) trên cơ sở mỗi loại. Tại đây, anh ấy đã cung cấp một vài ví dụ cho RegExp và Date.

Mã này không chỉ ngắn gọn mà còn rất dễ đọc. Nó khá dễ dàng để mở rộng.

Đây có phải là hiệu quả? Heck vâng. Cho rằng mục tiêu là tạo ra một bản sao sâu thực sự, sau đó bạn sẽ phải đi bộ các thành viên của biểu đồ đối tượng nguồn. Với phương pháp này, bạn có thể điều chỉnh chính xác thành viên con nào sẽ điều trị và cách xử lý thủ công các loại tùy chỉnh.

Vì vậy, có bạn đi. Hai cách tiếp cận. Cả hai đều hiệu quả trong quan điểm của tôi.


13

Đây thường không phải là giải pháp hiệu quả nhất, nhưng nó làm những gì tôi cần. Các trường hợp thử nghiệm đơn giản dưới đây ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Kiểm tra mảng tuần hoàn ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Kiểm tra chức năng ...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

11

AngularJS

Vâng, nếu bạn đang sử dụng góc cạnh, bạn cũng có thể làm điều này

var newObject = angular.copy(oldObject);

11

Tôi không đồng ý với câu trả lời với số phiếu lớn nhất ở đây . Một đệ quy sâu Clonenhanh hơn nhiều so với JSON.parse (JSON.stringify (obj)) cách tiếp cận nêu.

Và đây là chức năng để tham khảo nhanh:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}

2
Tôi thích cách tiếp cận này nhưng nó không xử lý đúng ngày; xem xét thêm một cái gì đó như if(o instanceof Date) return new Date(o.valueOf());sau khi kiểm tra null `
Luis

Tai nạn trên tài liệu tham khảo tròn.
Harry

Trong Firefox ổn định mới nhất, thời gian này dài hơn các chiến lược khác tại liên kết Jsben.ch đó, theo một mức độ lớn hơn hoặc nhiều hơn. Nó đánh bại những người khác theo hướng sai.
WBT

11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

10

Chỉ khi bạn có thể sử dụng ECMAScript 6 hoặc bộ chuyển đổi .

Đặc trưng:

  • Không kích hoạt getter / setter trong khi sao chép.
  • Bảo quản getter / setter.
  • Bảo quản thông tin nguyên mẫu.
  • Hoạt động với cả hai kiểu viết OO đối tượngchức năng .

Mã số:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}

9

Đây là một phương thức clone () toàn diện có thể sao chép bất kỳ đối tượng JavaScript nào. Nó xử lý hầu hết các trường hợp:

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

Nó chuyển đổi nguyên thủy thành các đối tượng bao bọc, không phải là một giải pháp tốt trong hầu hết các trường hợp.
Thủy thủ Danubian

@DanubianSailor - Tôi không nghĩ rằng nó ... nó dường như trả lại nguyên thủy ngay từ đầu, và dường như không làm gì với chúng để biến chúng thành các đối tượng bao bọc khi chúng được trả lại.
Jimbo Jonny
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.