Nodejs: cách sao chép một đối tượng


90

Nếu tôi sao chép một mảng, tôi sử dụng cloneArr = arr.slice()

Tôi muốn biết cách sao chép một đối tượng trong nodejs.

Câu trả lời:


175

Đối với các tiện ích và lớp học nơi không cần phải ép từng giọt hiệu suất, tôi thường gian lận và chỉ sử dụng JSON để thực hiện một bản sao sâu:

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

Đây không phải là câu trả lời duy nhất hay câu trả lời thanh lịch nhất; tất cả các câu trả lời khác nên được xem xét cho các điểm nghẽn sản xuất. Tuy nhiên, đây là một giải pháp nhanh và bẩn, khá hiệu quả và hữu ích trong hầu hết các tình huống mà tôi muốn sao chép một hàm băm đơn giản của các thuộc tính.


2
@djechlin Chắc chắn rồi. Hãy thử: jsfiddle.net/katowulf/E5jC3 (được thử nghiệm với nút 0.10.11) Nó sẽ không thể khôi phục lại các chức năng hoặc dữ liệu nguyên mẫu, nhưng nó nhận được các giá trị xung quanh rất tốt.
Kato

11
Điều này sẽ chuyển đổi ngày để dây
backus

4
@Backus Cũng như các đối tượng và chức năng.
Kato

2
@Kato, Tại sao tham chiếu vòng tròn lại ngụ ý không cần tạo bản sao sâu? Chúng là hai thứ không liên quan. Chắc chắn có thể viết một phương pháp sao chép tốt cho NodeJS hỗ trợ các tham chiếu vòng tròn và không rơi vào trường hợp sử dụng JSON để sao chép. github.com/pvorb/node-clone
Samuel Neff,

2
Đã được ghi chú trong các bình luận và trong mô tả. Bạn không thể sao chép một hàm, nguyên mẫu đối tượng hoặc những thứ khác mà bạn không nên cố gắng làm với một phương pháp sao chép chung. Bạn sẽ cần nhiều hơn một dòng mã cho điều đó, và có lẽ không nên được nhân bản rác mà anyway - đặt một phương pháp nhân bản trên lớp của bạn mà biết làm thế nào để xử lý các internals và làmnewObj = obj.clone(args...);
Kato

34

Object.assign chưa được đề cập trong bất kỳ câu trả lời nào ở trên.

let cloned = Object.assign({}, source);

Nếu bạn đang sử dụng ES6, bạn có thể sử dụng toán tử spread:

let cloned = { ... source };

Tham khảo: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


28
Lưu ý rằng cả hai đều là bản sao nông và toán tử lây lan trên các đối tượng yêu cầu node.js 8+.
jlh

1
Như @jlh đã đề cập, điều này sẽ không hoạt động đối với các đối tượng lồng nhau. Bạn có thể đọc thêm về các vấn đề nhân bản sâu của phương pháp này tại đây: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Shnd

Như Shnd đã nói, câu trả lời này không chính xác. Để nhân bản sâu, chúng ta cần sử dụng các lựa chọn thay thế khác vì Object.assign () sao chép các giá trị thuộc tính. Nếu giá trị nguồn là một tham chiếu đến một đối tượng, nó chỉ sao chép giá trị tham chiếu đó.
troy

1
Điều này không có bản sao, nó chỉ tham chiếu đối tượng gốc, có nghĩa là nếu bạn thay đổi một cái gì đó nó sẽ đi đến các bậc thầy
Aidan Welch

@AidanWelch Tôi không nghĩ điều đó chính xác. let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Miguel Pynto

20

Có một số mô-đun Node ngoài đó nếu không muốn "tự tung tự tác". Cái này có vẻ ổn: https://www.npmjs.com/package/clone

Có vẻ như nó xử lý tất cả các loại nội dung, bao gồm cả các tham chiếu vòng tròn. Từ trang github :

clone master sao chép các đối tượng, mảng, đối tượng Date và đối tượng RegEx. Mọi thứ đều được sao chép đệ quy, vì vậy, bạn có thể sao chép ngày tháng trong các mảng trong các đối tượng, chẳng hạn. [...] Tham chiếu thông tư? Vâng!


10

Thật khó để thực hiện một hoạt động sao chép chung chung nhưng hữu ích bởi vì những gì nên được sao chép đệ quy và những gì chỉ nên được sao chép phụ thuộc vào cách đối tượng cụ thể được cho là hoạt động.

Một cái gì đó có thể hữu ích là

function clone(x)
{
    if (x === null || x === undefined)
        return x;
    if (typeof x.clone === "function")
        return x.clone();
    if (x.constructor == Array)
    {
        var r = [];
        for (var i=0,n=x.length; i<n; i++)
            r.push(clone(x[i]));
        return r;
    }
    return x;
}

Trong mã này, logic là

  • trong trường hợp nullhoặc undefinedchỉ trả về cùng một trường hợp (trường hợp đặc biệt là cần thiết vì sẽ xảy ra lỗi khi thử xemclone phương thức nào không)
  • đối tượng có một clone phương thức? sau đó sử dụng
  • đối tượng có phải là một mảng không? sau đó thực hiện thao tác nhân bản đệ quy
  • nếu không thì chỉ cần trả về cùng một giá trị

Chức năng sao chép này sẽ cho phép thực hiện các phương pháp sao chép tùy chỉnh một cách dễ dàng ... ví dụ:

function Point(x, y)
{
    this.x = x;
    this.y = y;

    ...
}

Point.prototype.clone = function()
{
    return new Point(this.x, this.y);
};



function Polygon(points, style)
{
    this.points = points;
    this.style = style;

    ...
}

Polygon.prototype.clone = function()
{
    return new Polygon(clone(this.points),
                       this.style);
};

Khi ở trong đối tượng, bạn biết rằng hoạt động sao chép chính xác cho một mảng cụ thể chỉ là một bản sao cạn thì bạn có thể gọi values.slice()thay vìclone(values) .

Ví dụ trong đoạn mã trên, tôi yêu cầu rõ ràng rằng một bản sao của một đối tượng đa giác sẽ sao chép các điểm, nhưng sẽ chia sẻ cùng một đối tượng kiểu. Nếu tôi cũng muốn sao chép đối tượng kiểu thay thế thì tôi có thể vượt qua clone(this.style).


1
+1 vì đã đề cập rằng các đối tượng cần tự triển khai .clonephương thức. Đây là cách tốt nhất để đối phó với các đối tượng nhân bản.
Raynos

3
if (x.clone)nênif (typeof x.clone === 'function')
Yanick Rochon

@YanickRochon: Cảm ơn, đã sửa. Xin lỗi, bằng cách nào đó tôi không để ý nhận xét này trước khi ...
6502

9

Không có phương pháp gốc nào để nhân bản các đối tượng. Dấu gạch dưới thực hiện _.clonelà một bản sao nông.

_.clone = function(obj) {
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

Nó hoặc cắt nó hoặc mở rộng nó.

Đây là _.extend

// extend the obj (first parameter)
_.extend = function(obj) {
  // for each other parameter
  each(slice.call(arguments, 1), function(source) {
    // loop through all properties of the other objects
    for (var prop in source) {
      // if the property is not undefined then add it to the object.
      if (source[prop] !== void 0) obj[prop] = source[prop];
    }
  });
  // return the object (first parameter)
  return obj;
};

Extend chỉ cần lặp lại tất cả các mục và tạo một đối tượng mới với các mục trong đó.

Bạn có thể triển khai triển khai ngây thơ của riêng mình nếu bạn muốn

function clone(o) {
  var ret = {};
  Object.keys(o).forEach(function (val) {
    ret[val] = o[val];
  });
  return ret;
}

Có những lý do chính đáng để tránh nhân bản sâu vì không thể nhân bản các đóng.

Cá nhân tôi đã hỏi một câu hỏi deep cloning objects beforevà kết luận tôi đi đến là bạn không làm điều đó.

Khuyến nghị của tôi là sử dụng underscorevà đó là _.clonephương pháp dành cho nhân bản nông


9

Đối với một bản sao cạn, tôi thích sử dụng mẫu giảm (thường là trong một mô-đun hoặc tương tự), như sau:

var newObject = Object.keys(original).reduce(function (obj, item) {
    obj[item] = original[item];
    return obj;
},{});

Đây là jsperf cho một số tùy chọn: http://jsperf.com/shallow-copying


đơn giản, thanh lịch, rực rỡ. Tôi mong đợi những đóng góp SO khác của bạn ^ _ ^
Cảm ơn bạn

7

Câu hỏi cũ, nhưng có một câu trả lời thanh lịch hơn những gì được đề xuất cho đến nay; sử dụng utils._extend được tích hợp sẵn:

var extend = require("util")._extend;

var varToCopy = { test: 12345, nested: { val: 6789 } };

var copiedObject = extend({}, varToCopy);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 } }

Lưu ý việc sử dụng tham số đầu tiên với một đối tượng trống {} - điều này cho phép mở rộng rằng (các) đối tượng đã sao chép cần được sao chép sang một đối tượng mới. Nếu bạn sử dụng một đối tượng hiện có làm tham số đầu tiên, thì các tham số thứ hai (và tất cả các tham số tiếp theo) sẽ được sao chép hợp nhất sâu trên biến tham số đầu tiên.

Sử dụng các biến ví dụ ở trên, bạn cũng có thể làm điều này:

var anotherMergeVar = { foo: "bar" };

extend(copiedObject, { anotherParam: 'value' }, anotherMergeVar);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 }, anotherParam: 'value', foo: 'bar' }

Tiện ích rất hữu ích, đặc biệt là khi tôi sử dụng để mở rộng trong AngularJS và jQuery.

Hy vọng điều này sẽ giúp người khác; ghi đè tham chiếu đối tượng là một nỗi khốn khổ, và điều này giải quyết nó mọi lúc!


1
Sử dụng _extend hiện đã bị khấu hao. Sử dụng Object.assign () thay vì: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
macroman

7

Bạn cũng có thể sử dụng lodash . Nó có các phương thức clonecloneDeep .

var _= require('lodash');

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

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

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

1

Tùy thuộc vào những gì bạn muốn làm với đối tượng nhân bản của mình, bạn có thể sử dụng cơ chế kế thừa nguyên mẫu của javascript và đạt được một đối tượng được nhân bản thông qua:

var clonedObject = Object.create(originalObject);

Chỉ cần nhớ rằng đây không phải là một bản sao đầy đủ - tốt hơn hoặc tệ hơn.

Một điều tốt về điều đó là bạn thực sự chưa nhân bản đối tượng nên dung lượng bộ nhớ sẽ thấp.

Một số điều khó khăn cần nhớ mặc dù về phương pháp này là việc lặp lại các thuộc tính được xác định trong chuỗi nguyên mẫu đôi khi hoạt động hơi khác một chút và thực tế là bất kỳ thay đổi nào đối với đối tượng gốc cũng sẽ ảnh hưởng đến đối tượng nhân bản trừ khi thuộc tính đó cũng đã được đặt trên chính nó .


3
Điều này thật vô lý nguy hiểm và không giống một bản sao nào cả. var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
Cảm ơn bạn

@naomik: b.foo = "working"; console.log(a.foo); // still "broken";Tất nhiên phải lưu ý rằng những thay đổi trong đối tượng gốc sẽ được phản ánh trong "bản sao" và những thay đổi trong "bản sao" sẽ không được phản ánh trong đối tượng gốc - nhưng tôi sẽ không gọi nó là nguy hiểm - tất cả phụ thuộc vào những gì bạn muốn làm với bản sao của mình
VoxPelli

@naomik: Tôi còn khá mới với JS, vì vậy tôi muốn có một số điều chỉnh. Bản sao nông do olli-k đề xuất và bạn thích có vẻ như (IMHO, nhưng tôi có thể sai) có những hạn chế tương tự so với giải pháp do voxpelli đề xuất. Bản gốc và bản đóng nông cũng sẽ chia sẻ cùng một "vật phẩm", cập nhật một vật phẩm trong bản gốc cũng sẽ tác động đến bản sao nông. Hoặc là?
bmorin

@bmornin, nếu bạn sử dụng kỹ thuật của Olli K, việc sửa đổi đối tượng gốc sẽ không gây ra thay đổi trong đối tượng mới.
Cảm ơn bạn

1
@VoxPelli Toàn bộ mục đích của một "bản sao" là để cô lập các thay đổi khỏi đối tượng ban đầu, do đó phương pháp này nguy hiểm hơn.
Doug

1

Tôi đã triển khai một bản sao sâu đầy đủ. Tôi tin rằng nó là lựa chọn tốt nhất cho một phương pháp sao chép chung, nhưng nó không xử lý các tham chiếu theo chu kỳ.

Ví dụ sử dụng:

parent = {'prop_chain':3}
obj = Object.create(parent)
obj.a=0; obj.b=1; obj.c=2;

obj2 = copy(obj)

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3
console.log(obj2, obj2.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3

parent.prop_chain=4
obj2.a = 15

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 4
console.log(obj2, obj2.prop_chain)
// '{'a':15, 'b':1, 'c':2} 4

Bản thân mã:

Mã này sao chép các đối tượng với nguyên mẫu của chúng, nó cũng sao chép các chức năng (có thể hữu ích cho ai đó).

function copy(obj) {
  // (F.prototype will hold the object prototype chain)
  function F() {}
  var newObj;

  if(typeof obj.clone === 'function')
    return obj.clone()

  // To copy something that is not an object, just return it:
  if(typeof obj !== 'object' && typeof obj !== 'function' || obj == null)
    return obj;

  if(typeof obj === 'object') {    
    // Copy the prototype:
    newObj = {}
    var proto = Object.getPrototypeOf(obj)
    Object.setPrototypeOf(newObj, proto)
  } else {
    // If the object is a function the function evaluate it:
    var aux
    newObj = eval('aux='+obj.toString())
    // And copy the prototype:
    newObj.prototype = obj.prototype
  }

  // Copy the object normal properties with a deep copy:
  for(var i in obj) {
    if(obj.hasOwnProperty(i)) {
      if(typeof obj[i] !== 'object')
        newObj[i] = obj[i]
      else
        newObj[i] = copy(obj[i])
    }
  }

  return newObj;
}

Với bản sao này, tôi không thể tìm thấy bất kỳ sự khác biệt nào giữa bản gốc và bản được sao chép ngoại trừ nếu bản gốc được sử dụng đóng trên cấu trúc của nó, vì vậy tôi nghĩ rằng nó là một triển khai tốt.

Tôi hy vọng nó sẽ giúp


1

cho mảng, người ta có thể sử dụng

var arr = [1,2,3]; 
var arr_2 = arr ; 

print ( arr_2 ); 

arr = arr.slice (0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 

1

Đối tượng và Mảng trong JavaScript sử dụng lệnh gọi theo tham chiếu, nếu bạn cập nhật giá trị đã sao chép, nó có thể phản ánh trên đối tượng gốc. Để ngăn chặn điều này, bạn có thể sao chép sâu đối tượng, để ngăn tham chiếu được truyền, sử dụng lệnh chạy phương thức cloneDeep của thư viện lodash

npm cài đặt lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
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.