JavaScript: Làm thế nào để chuyển đối tượng theo giá trị?


96
  • Khi chuyển các đối tượng dưới dạng tham số, JavaScript sẽ chuyển chúng theo tham chiếu và khó tạo bản sao cục bộ của các đối tượng.

    var o = {};
    (function(x){
        var obj = x;
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)
    

    osẽ có .foo.bar.

  • Có thể giải quyết vấn đề này bằng cách nhân bản; ví dụ đơn giản:

    var o = {};
    
    function Clone(x) {
       for(p in x)
       this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
    }
    
    (function(x){
        var obj = new Clone(x);
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)
    

    osẽ không có .foohoặc .bar.


Câu hỏi

  1. Có cách nào tốt hơn để chuyển các đối tượng theo giá trị, ngoài việc tạo một bản sao / bản sao cục bộ không?

3
Trường hợp sử dụng của bạn để cần cái này là gì?
jondavidjohn

2
Lập trình vui vẻ. Xem liệu các công cụ JS mới có giải quyết vấn đề này hay không (về mặt kỹ thuật, nó đang chuyển tham chiếu theo giá trị), nhưng chủ yếu là để giải trí.
vol7ron

1
Xem JavaScript có phải là ngôn ngữ chuyển theo tham chiếu hay chuyển theo giá trị không? - JavaScript không chuyển qua tham chiếu. Giống như Java, khi truyền các đối tượng cho một hàm, nó sẽ chuyển theo giá trị, nhưng giá trị là một tham chiếu. Xem thêm Java có vượt qua tham chiếu không? .
Richard JP Le Guen

1
Theo như tôi biết, bạn không thể chuyển các đối tượng theo giá trị. Ngay cả khi có, nó sẽ thực hiện việc nhân bản mà bạn đang đề cập ở trên, vì vậy tôi không thể thấy được gì. Ngoài việc lưu 3 dòng mã.
Thor84no,

1
@ vol7ron Có, họ đã giải quyết vấn đề này bằng cách triển khai chính xác đặc tính thiết kế ngôn ngữ.
jondavidjohn

Câu trả lời:


61

Không hẳn vậy.

Tùy thuộc vào những gì bạn thực sự cần, một khả năng có thể được đặt olàm nguyên mẫu của một đối tượng mới.

var o = {};
(function(x){
    var obj = Object.create( x );
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o);

alert( o.foo ); // undefined

Vì vậy, bất kỳ thuộc tính nào bạn thêm vào objsẽ không được thêm vào o. Bất kỳ thuộc tính nào được thêm vào objcó cùng tên thuộc tính với thuộc tính trong osẽ làm mờ thuộc otính đó.

Tất nhiên, bất kỳ thuộc tính nào được thêm vào osẽ có sẵn objnếu chúng không bị che khuất và tất cả các đối tượng có otrong chuỗi nguyên mẫu sẽ thấy các bản cập nhật tương tự o.

Ngoài ra, nếu objcó thuộc tính tham chiếu đến một đối tượng khác, chẳng hạn như Mảng, bạn cần phải đảm bảo phủ bóng đối tượng đó trước khi thêm các thành viên vào đối tượng, nếu không, các thành viên đó sẽ được thêm vào objvà sẽ được chia sẻ giữa tất cả các đối tượng có objtrong chuỗi nguyên mẫu.

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // 'new_value'

Ở đây bạn có thể thấy rằng vì bạn không shadow Array ở baztrên ovới một baztài sản trên obj, các o.bazmảng được sửa đổi.

Vì vậy, thay vào đó, bạn cần làm bóng nó trước:

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz = [];
    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // undefined

3
+1, tôi sẽ đề nghị Object.createtrong câu trả lời của tôi cũng vậy, nhưng tôi không muốn căng ra để giải thích sự nông cạn của bản sao ;-)
Andy E

+1, tôi đã quên về điều này. Tôi đã thử var obj = new Object(x). Đối với tôi, tôi sẽ nghĩ rằng điều đó new Object(x)sẽ hoạt động Object.create(x)theo mặc định - thú vị
vol7ron

1
@ vol7ron: Vâng, new Object(x)chỉ cần nhổ lại cùng một đối tượng (như bạn có thể nhận thấy) . Sẽ thật tuyệt nếu có một cách sao chép bản địa. Không chắc liệu có gì trong tác phẩm.
user113716,

43

Hãy xem câu trả lời này https://stackoverflow.com/a/5344074/746491 .

Tóm lại, JSON.parse(JSON.stringify(obj))là một cách nhanh chóng để sao chép các đối tượng của bạn, nếu các đối tượng của bạn có thể được tuần tự hóa thành json.


2
Tôi có xu hướng tránh điều này vì các đối tượng không phải lúc nào cũng được tuần tự hóa và bởi vì trong các trình duyệt trung tuổi như IE7 / 8, JSONKhông được bao gồm và cần được xác định - cũng có thể mô tả nhiều hơn với một cái tên như thế nào Clone. Tuy nhiên, tôi nghi ngờ quan điểm của mình có thể thay đổi trong tương lai, vì JSON được tích hợp nhiều hơn vào phần mềm không dựa trên trình duyệt như cơ sở dữ liệu và IDE
vol7ron

1
Nếu không có hàm và đối tượng ngày tháng, giải pháp JSON.parse có vẻ là tốt nhất. stackoverflow.com/questions/122102/…
Herr_Hansen

Có thật không?!? Serialize thành JSON chỉ để tạo một bản sao đối tượng? Tuy nhiên, một lý do khác để ghét ngôn ngữ vô lý này.
RyanNerd

1
Serializing JSON thực sự là một cách nhanh chóng để sao chép nó? Theo tài liệu NodeJS, thao tác JSON là tác vụ chuyên sâu nhất của CPU. nodejs.org/en/docs/guides/dont-block-the-event-loop/...
harshad

38

Đây là chức năng sao chép sẽ thực hiện sao chép sâu của đối tượng:

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

    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

Bây giờ bạn có thể sử dụng như thế này:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)

3
Đây là một câu trả lời sâu sắc.
Nathan

Xinh đẹp! Câu trả lời hay nhất IMO.
ganders

23

Sử dụng Object.assign()

Thí dụ:

var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.

Một ví dụ rõ ràng hơn thực hiện điều tương tự:

var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.

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


4
Tốt hơn:var b = Object.assign({}, a);
Bergi

Đã đồng ý. Đang cập nhật câu trả lời cho phù hợp. Ngoài ra, không phải trình duyệt nào cũng hỗ trợ phương thức gán (). Tùy thuộc vào yêu cầu tương thích của bạn, có thể cần sử dụng polyfill như polyfill trên MDN.
Brandon

3
@Brandon, trong chính liên kết bạn đã cung cấp, nó đưa ra cảnh báo về nhân bản sâu: "Để nhân bản sâu, chúng tôi 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à tham chiếu đến một đối tượng, nó chỉ sao chép giá trị tham chiếu đó. "
pwilcox

2
Điều này hoạt động nhưng hãy nhớ rằng nó không thực hiện sao chép sâu, bất kỳ đối tượng nào chứa bên trong asẽ được sao chép dưới dạng tham chiếu.
Stoinov

15

Bạn hơi bối rối về cách các đối tượng hoạt động trong JavaScript. Tham chiếu của đối tượng giá trị của biến. Không có giá trị chưa được công nghiệp hóa. Khi bạn tạo một đối tượng, cấu trúc của nó được lưu trong bộ nhớ và biến mà nó được gán để giữ một tham chiếu đến cấu trúc đó.

Ngay cả khi những gì bạn đang yêu cầu được cung cấp theo một cách dễ dàng, cấu trúc ngôn ngữ mẹ đẻ thì về mặt kỹ thuật, nó vẫn sẽ được nhân bản.

JavaScript thực sự chỉ là giá trị truyền ... nó chỉ là giá trị được truyền có thể là một tham chiếu đến một cái gì đó.


1
Này AndyE! Tôi không bối rối vì điều đó, tôi hy vọng rằng JS sẽ thực hiện việc nhân bản theo mặc định. Có sự kém hiệu quả lớn trong việc tự nhân bản, vì vậy nếu động cơ làm được điều đó, tôi chắc chắn rằng nó sẽ tốt hơn. Tuy nhiên, tôi nghi ngờ nhu cầu / nhu cầu vượt quá lợi ích.
vol7ron

12
Trong JS khi mọi người nói bằng tham chiếu thay vì bằng giá trị, họ có nghĩa là giá trị giống như một con trỏ hơn là một bản sao. Đó là biệt ngữ khá tốt.
Erik Reppen

4
@Erik: theo ý kiến ​​khiêm tốn của tôi, tôi nghĩ tốt hơn nên định nghĩa nó rõ ràng hơn để không gây nhầm lẫn cho những người mới tiếp cận với ngôn ngữ này. Chỉ vì một cái gì đó được thiết lập tốt không có nghĩa là nó phù hợp hoặc thậm chí đúng về mặt kỹ thuật (vì nó không phải vậy). Mọi người nói "bằng cách tham khảo" bởi vì họ nói điều đó dễ hơn là giải thích cách nó thực sự hoạt động.
Andy E

6
Giá trị là một tham chiếu đến một vị trí trong bộ nhớ. Cuối cùng thì bạn vẫn đang xử lý cùng một mục trong bộ nhớ thay vì làm việc với một bản sao. Làm thế nào để phân biệt mơ hồ đó sẽ giúp ích cho người mới? Đây là cách nó được giải thích trong rất nhiều sách công nghệ bao gồm Hướng dẫn về quyền tác giả của O'Reilly, hiện đã xuất bản trên ấn bản thứ năm (OT: và được cập nhật rất yếu).
Erik Reppen

1
@AndyE Tôi đã đọc thêm một chút để xem những gì rắc rối về. IMO, đây giống như vấn đề liệu JS có OOP "thực sự" hay chúng ta có thể gọi một thứ gì đó là "phương thức" so với các ngôn ngữ khác một cách chính xác. Tôi không thực sự chắc chắn sẽ khả thi nếu coi các vars là ghi trực tiếp vào cùng một đối tượng trong bộ nhớ thay vì ghi đè con trỏ đã sao chép của nó bằng một ngôn ngữ dựa trên bao đóng với sự phân nhánh của toán tử gán hiệu năng. Sự khác biệt về hành vi thực sự duy nhất mà chúng tôi muốn nói trong JS so với các ngôn ngữ khác là điều gì sẽ xảy ra khi bạn sử dụng toán tử gán.
Erik Reppen

15

Dùng cái này

x = Object.create(x1);

xx1sẽ là hai đối tượng khác nhau, thay đổi trong xsẽ không thay đổix1


Tôi kết thúc bằng cách sử dụng Object.assign (). Đối với trường hợp thử nghiệm đơn giản, .create () hoạt động nhưng đối với những trường hợp phức tạp hơn (tôi chưa thể tìm thấy mẫu), nó vẫn tham chiếu đến cùng một địa chỉ.
Coisox

Nó sẽ không chỉ thay đổi nếu các giá trị của đối tượng không được lưu trữ bằng tham chiếu, tức là chúng là các kiểu dữ liệu tiền nghiệm như số hoặc boolean. Ví dụ: hãy xem xét đoạn mã dưới đây a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (ax);
Jjagwe Dennis 13/09/18

8

Javascript luôn đi theo giá trị. Trong trường hợp này, nó chuyển một bản sao của tham chiếu ovào hàm ẩn danh. Mã đang sử dụng một bản sao của tham chiếu nhưng nó làm thay đổi đối tượng duy nhất. Không có cách nào để khiến javascript chuyển qua bất kỳ thứ gì khác ngoài giá trị.

Trong trường hợp này, những gì bạn muốn là chuyển một bản sao của đối tượng bên dưới. Nhân bản đối tượng là cách duy nhất. Mặc dù vậy, phương pháp sao chép của bạn cần một chút cập nhật

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}

1
+1 Đối với "luôn đi theo giá trị" mặc dù tôi thích sử dụng Chia sẻ Gọi theo Đối tượng (trái ngược với "Gọi theo Giá trị [của Tham chiếu]") và quảng bá "giá trị" cho "chính đối tượng" (với lưu ý rằng đối tượng không được sao chép / nhân bản / nhân bản) vì các tham chiếu chỉ là một chi tiết triển khai và không được tiếp xúc với JavaScript (hoặc được đề cập trực tiếp trong đặc tả!).

Tôi đã gặp sự cố khi thêm thuộc tính vào một đối tượng mà tôi đang nhận từ kết nối từ xa (tôi nghĩ đó có thể là vấn đề mà tôi không thể thêm bất kỳ thuộc tính nào vào đối tượng đó) vì vậy tôi đã thử hàm cạn này và gặp lỗi này - Nguyên mẫu đối tượng chỉ có thể là một Đối tượng hoặc null tại var copy = Object.create (o); bất kỳ ý tưởng làm thế nào để giải quyết vấn đề của tôi
Harshit Laddha

Đây không phải là một bản sao sâu?
River Tam

@RiverTam Không, nó không phải là một bản sao sâu, vì nó không sao chép các đối tượng bên trong các đối tượng. Tuy nhiên, bạn có thể dễ dàng biến nó thành một bản sao sâu chỉ bằng cách làm cho nó tự gọi đệ quy lên từng đối tượng con.
ABPerson

7

Đối với người dùng jQuery, cũng có một cách để thực hiện việc này một cách đơn giản bằng cách sử dụng framework. Chỉ một cách khác jQuery giúp cuộc sống của chúng ta dễ dàng hơn một chút.

var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy    = jQuery.extend(true, {}, o); 

người giới thiệu :


À - bạn đã phát hiện ra câu hỏi cũ này :) Tôi nghĩ câu hỏi ban đầu là khám phá ngôn ngữ JS nhiều hơn. Có vẻ như ngay cả thuật ngữ của tôi khi đó cũng yếu vì ví dụ của tôi là một bản sao sâu hơn là một bản sao . Nhưng có vẻ như đối tượng / băm chỉ có thể được thông qua bằng cách tham khảo, đó là phổ biến trong ngôn ngữ kịch bản
vol7ron

1
:) Đi qua nó để tìm kiếm chức năng chính xác. Tôi có một đối tượng dữ liệu mà tôi cần sao chép để chỉnh sửa trước khi gửi trong khi vẫn duy trì đối tượng gốc. Tôi thích giải pháp JS gốc và sẽ sử dụng nó trong thư viện cơ sở của riêng tôi, nhưng giải pháp jQuery là giải pháp tạm thời của tôi. Câu trả lời được chấp nhận là tuyệt vời. : D
Brett Weber

6

Trên thực tế, Javascript luôn được chuyển theo giá trị . Nhưng vì các tham chiếu đối tượng là các giá trị , các đối tượng sẽ hoạt động giống như chúng được truyền bằng tham chiếu .

Vì vậy, để giải quyết vấn đề này, hãy xâu chuỗi đối tượng và phân tích cú pháp nó trở lại, cả hai đều sử dụng JSON. Xem ví dụ về mã bên dưới:

var person = { Name: 'John', Age: '21', Gender: 'Male' };

var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object

var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects

2
Có, đây là một tùy chọn để chuyển đổi / chuyển đổi lại đơn giản. Nói chung, làm việc với chuỗi rất kém hiệu quả; mặc dù vậy, tôi đã không đánh giá hiệu suất của phân tích cú pháp JSON trong một thời gian. Hãy nhớ rằng trong khi điều này hoạt động đối với các đối tượng đơn giản, các đối tượng có chứa hàm dưới dạng giá trị, sẽ mất nội dung.
vol7ron

4

use obj2 = {... obj1} Bây giờ cả hai đối tượng có cùng giá trị và tham chiếu khác nhau


2
Mới sử dụng javascript ở đây. Toán tử Spread tạo ra giá trị mới thay vì sao chép bằng tham chiếu? Vậy tại sao điều này lại bị phản đối?
jo3birdtalk

2
Ở cái nhìn đầu tiên, nó phù hợp với tôi. Nhưng tôi phát hiện ra nó chỉ ở lớp đầu tiên của biến. Ví dụ, nó hoạt động khi let a = {value: "old"} let b = [...a] b.value = "new", và a sẽ là {value: "old"}, b sẽ là {value: "new"}. Nhưng nó không diễn ra khi nào let a = [{value: "old"}] let b = [...a] b[0].value = "new", và a sẽ là [{value: "new"}], b sẽ là [{value: "new"}].
Brady Huang

có nó không sao chép nông. Nếu bạn có đối tượng lồng nhau, bạn phải thực hiện sao chép sâu. Để đơn giản hơn, bạn có thể sử dụng các chức năng clone và deepClone của lodash
Geetanshu Gulati

3

Tôi cần sao chép một đối tượng theo giá trị (không phải tham chiếu) và tôi thấy trang này hữu ích:

Cách hiệu quả nhất để sao chép sâu một đối tượng trong JavaScript là gì? . Đặc biệt, sao chép một đối tượng với mã sau của John Resig:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Câu hỏi là về JavaScript, không phải thư viện jQuery.
j08691

1
Đúng, đối với những người không thể sử dụng Jquery vì bất kỳ lý do gì, điều này sẽ không giúp ích cho họ. Tuy nhiên, vì Jquery là một thư viện javascript nên tôi nghĩ nó vẫn sẽ giúp ích cho hầu hết người dùng javascript.
Anthony Haffey

Nó sẽ không giúp ích cho bất kỳ lập trình viên nào đang cố gắng làm điều đó trên máy chủ. Vì vậy - không. Nó không phải là một câu trả lời hợp lệ.
Tomasz Gałkowski,

3

Với cú pháp ES6:

let obj = Object.assign({}, o);


3
Đề cập rất tốt @galkowskit. Vấn đề lớn nhất (tốt hơn: điều cần lưu ý ) Object.assignlà nó không thực hiện một bản sao sâu.
vol7ron

1

Khi bạn hiểu nó, nó chỉ là một proxy quá phức tạp, nhưng có lẽ Catch-All Proxy có thể làm được điều đó?

var o = {
    a: 'a',
    b: 'b',
    func: function() { return 'func'; }
};

var proxy = Proxy.create(handlerMaker(o), o);

(function(x){
    var obj = x;
    console.log(x.a);
    console.log(x.b);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(proxy);

console.log(o.foo);

function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {

   },
   delete:       function(name) { return delete obj[name]; },   
   fix:          function() {}
  };
}

Richard, tôi hơi muộn để trả lời :) nhưng tôi nghĩ rằng tôi chưa bao giờ làm vậy vì một vài lý do: Tôi đang tìm kiếm cú pháp cho JS khả năng truyền các đối tượng theo nhiều cách khác nhau; câu trả lời của bạn trông có vẻ dài và tôi đã bị treo khi bạn sử dụng "proxy". 3 năm sau và tôi vẫn bị treo khi sử dụng. Có lẽ tôi đã không chú ý đến lớp học, nhưng cũng giống như mạng lưới, tôi nghĩ proxy trong CS phải đóng vai trò trung gian. Có lẽ nó cũng ở đây và tôi không nghĩ chính xác về những gì nó là trung gian.
vol7ron

0

Nếu bạn đang sử dụng lodash hoặc npm, hãy sử dụng chức năng hợp nhất của lodash để sao chép sâu tất cả các thuộc tính của đối tượng vào một đối tượng trống mới như sau:

var objectCopy = lodash.merge({}, originalObject);

https://lodash.com/docs#merge

https://www.npmjs.com/package/lodash.merge


Một bản sao sâu cũng nên bao gồm các sự kiện, tôi không quá quen thuộc với lodash, nó có phản chiếu các sự kiện sang một đối tượng mới không?
vol7ron
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.