Đối tượng lây lan so với Object.assign


396

Giả sử tôi có một optionsbiến và tôi muốn đặt một số giá trị mặc định.

Lợi ích / nhược điểm của hai lựa chọn này là gì?

Sử dụng đối tượng lây lan

options = {...optionsDefault, ...options};

Hoặc sử dụng Object.assign

options = Object.assign({}, optionsDefault, options);

Đây là cam kết khiến tôi tự hỏi.


4
Chà, đầu tiên là một cú pháp mới được đề xuất và không phải là một phần của ES6, vì vậy nó phụ thuộc vào tiêu chuẩn bạn muốn tuân thủ.
loganfsmyth

5
Xác định " tốt nhất " (một cách cẩn thận, đừng kết thúc bằng một câu hỏi dựa trên ý kiến ​​:-)
Amit

1
Cũng có thể phụ thuộc vào cách bạn muốn hỗ trợ nó nếu chạy trong môi trường mà không có hỗ trợ riêng. Cú pháp bạn có thể chỉ cần biên dịch. Một đối tượng hoặc phương thức bạn có thể cần polyfill.
JMM

6
ngoài các vấn đề tương thích, Object.assign có thể biến đổi đối tượng ban đầu hữu ích. lây lan không thể.
pstanton

2
Để làm rõ nhận xét của @ pstanton - object.assign có thể sửa đổi một đối tượng đích hiện có (ghi đè các thuộc tính từ nguồn, trong khi vẫn giữ nguyên các thuộc tính khác); nó không chạm vào đối tượng nguồn . Lần đầu tiên tôi đọc "đối tượng gốc" của mình là "đối tượng nguồn", vì vậy hãy viết ghi chú này cho bất kỳ ai khác đọc sai nó. :)
ToolmakerSteve

Câu trả lời:


328

Điều này không nhất thiết là toàn diện.

Cú pháp lây lan

options = {...optionsDefault, ...options};

Ưu điểm:

  • Nếu tác giả mã để thực thi trong các môi trường mà không có hỗ trợ riêng, bạn có thể chỉ cần biên dịch cú pháp này (trái ngược với việc sử dụng một polyfill). (Với Babel chẳng hạn.)

  • Ít dài dòng hơn.

Nhược điểm:

  • Khi câu trả lời này ban đầu được viết, đây là một đề xuất , không được chuẩn hóa. Khi sử dụng các đề xuất hãy xem xét những gì bạn sẽ làm nếu bạn viết mã với nó ngay bây giờ và nó không được chuẩn hóa hoặc thay đổi khi nó chuyển sang tiêu chuẩn hóa. Điều này đã được chuẩn hóa trong ES2018.

  • Nghĩa đen, không năng động.


Object.assign()

options = Object.assign({}, optionsDefault, options);

Ưu điểm:

  • Chuẩn hóa.

  • Năng động. Thí dụ:

    var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
    options = Object.assign.apply(Object, [{}].concat(sources));
    // or
    options = Object.assign({}, ...sources);

Nhược điểm:

  • Dài dòng hơn.
  • Nếu tác giả mã để thực thi trong môi trường mà không có hỗ trợ riêng, bạn cần phải thực hiện polyfill.

Đây là cam kết khiến tôi tự hỏi.

Điều đó không liên quan trực tiếp đến những gì bạn đang hỏi. Mã đó không được sử dụng Object.assign(), đó là sử dụng mã người dùng ( object-assign) thực hiện điều tương tự. Chúng dường như đang biên dịch mã đó với Babel (và gói nó với Webpack), đó là những gì tôi đã nói về: cú pháp bạn có thể biên dịch. Họ rõ ràng thích rằng phải bao gồm object-assignnhư một sự phụ thuộc sẽ đi vào công trình của họ.


15
Nó có thể là đáng chú ý là phần còn lại đối tượng lây lan đã chuyển sang giai đoạn 3 nên có khả năng sẽ được chuẩn hóa trong tương lai twitter.com/sebmarkbage/status/781564713750573056
williaster

11
@JMM Tôi không chắc chắn tôi thấy "Thêm dài dòng." như một bất lợi.
DiverseAndRemote.com

6
@Omar tốt Tôi đoán bạn chỉ cần chứng minh rằng đó là một ý kiến ​​:) Nếu ai đó không nhận ra lợi thế đó thì ừ, tất cả những người khác đều bình đẳng họ chỉ có thể sử dụng Object.assign(). Hoặc bạn có thể lặp lại thủ công trên một mảng các đối tượng và đạo cụ riêng của chúng tự gán chúng cho một mục tiêu và làm cho nó dài hơn nữa: P
JMM

7
như @JMM đã đề cập, giờ đây nó nằm trong nút spec ES2018.green/#ES2018-features-object-rest-siverse-properies
Sebastien H.

2
"Mỗi byte được tính" Đó là những gì tối thiểu hóa / uglify dành cho @yzorg
DiverseAndRemote.com 31/03/18

171

Đối với phần còn lại / trải rộng đối tượng tham chiếu được hoàn thành trong ECMAScript 2018 như là một giai đoạn 4. Đề xuất có thể được tìm thấy ở đây .

Đối với hầu hết các phần đặt lại và trải rộng đối tượng hoạt động theo cùng một cách, điểm khác biệt chính là trải rộng xác định các thuộc tính, trong khi Object.assign () đặt chúng . Điều này có nghĩa là Object.assign () kích hoạt setters.

Điều đáng ghi nhớ là khác với điều này, đối tượng nghỉ ngơi / trải rộng 1: 1 ánh xạ tới Object.assign () và hoạt động khác với trải rộng mảng (lặp lại). Ví dụ, khi trải một giá trị null mảng được trải rộng. Tuy nhiên, việc sử dụng các giá trị null của đối tượng được truyền âm thầm thành không có gì.

Ví dụ về lây lan mảng (lặp lại)

const x = [1, 2, null , 3];
const y = [...x, 4, 5];
const z = null;

console.log(y); // [1, 2, null, 3, 4, 5];
console.log([...z]); // TypeError

Ví dụ trải rộng đối tượng

const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};

console.log(z); //{a: 1, b: 2}

Điều này phù hợp với cách Object.assign () hoạt động, cả hai đều âm thầm loại trừ giá trị null không có lỗi.

const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);

console.log(z); //{a: 1, b: 2}

10
Điều này nên được. Câu trả lời được chọn.
Evan Plaice

4
Đây sẽ là câu trả lời ... bây giờ là tương lai.
Zachary Abresch

1
Đây là câu trả lời đúng. Sự khác biệt chính là Object.assignsẽ sử dụng setters. Object.assign({set a(v){this.b=v}, b:2}, {a:4}); // {b: 4}vs.{...{set a(v){this.b=v}, b:2}, ...{a:4}}; // {a: 4, b: 2}
David boho

1
"Tương lai là đây!" - George Allen Ngày mai là quá muộn.
ruffin

1
Việc chứng minh null được xử lý khác nhau là "táo và cam" - không phải là một so sánh có ý nghĩa. Trong trường hợp mảng, null là một phần tử của mảng. Trong trường hợp lây lan, null là toàn bộ đối tượng. So sánh đúng sẽ là x để có thuộc tính null : const x = {c: null};. Trong trường hợp đó, AFAIK, chúng ta sẽ thấy hành vi giống như mảng : //{a: 1, b: 2, c: null}.
ToolmakerSteve

39

Tôi nghĩ rằng một sự khác biệt lớn giữa toán tử lây lan và Object.assigndường như không được đề cập trong các câu trả lời hiện tại là toán tử trải sẽ không sao chép nguyên mẫu của đối tượng nguồn vào đối tượng đích. Nếu bạn muốn thêm thuộc tính vào một đối tượng và bạn không muốn thay đổi thể hiện của nó, thì bạn sẽ phải sử dụng Object.assign. Ví dụ dưới đây sẽ chứng minh điều này:

const error = new Error();
error instanceof Error // true

const errorExtendedUsingSpread = {
  ...error,
  ...{
    someValue: true
  }
};
errorExtendedUsingSpread instanceof Error; // false

const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true


"Sẽ không giữ nguyên mẫu" - nó chắc chắn sẽ, vì nó không sửa đổi bất cứ điều gì. Trong ví dụ của bạn errorExtendedUsingAssign === error, nhưng errorExtendedUsingSpreadlà một đối tượng mới (và nguyên mẫu không được sao chép).
maaartinus

2
@maaartinus Bạn nói đúng, có lẽ tôi đã nói xấu điều đó. Tôi có nghĩa là nguyên mẫu không nằm trên đối tượng sao chép. Tôi có thể chỉnh sửa nó để rõ ràng hơn.
Sean Dawson

Đây có phải là một cách để "nhân bản nông" một đối tượng với lớp của nó không? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve

@ToolmakerSteve Có, nó sẽ sao chép tất cả "thuộc tính riêng" của đối tượng trên đó sẽ thực sự là một bản sao nông. Xem: stackoverflow.com/questions/33692912/ từ
Sean Dawson

12

Như những người khác đã đề cập, tại thời điểm viết bài này, Object.assign()đòi hỏi phải có một polyfill và đối tượng trải rộng ...yêu cầu một số dịch chuyển (và có lẽ là một polyfill nữa) để hoạt động.

Xem xét mã này:

// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);

Cả hai đều sản xuất cùng một đầu ra.

Đây là đầu ra từ Babel, đến ES5:

var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);

Đây là sự hiểu biết của tôi cho đến nay. Object.assign()là thực sự được tiêu chuẩn hóa, nơi mà đối tượng lây lan ...chưa được. Vấn đề duy nhất là hỗ trợ trình duyệt cho cái trước và trong tương lai, cái sau cũng vậy.

Chơi với mã ở đây

Hi vọng điêu nay co ich.


1
Cảm ơn bạn! Mẫu mã của bạn làm cho quyết định thực sự dễ dàng cho bối cảnh của tôi. Bộ chuyển mã (babel hoặc typecript) làm cho toán tử trải rộng tương thích hơn với các trình duyệt bằng cách bao gồm một pollyfill trong mã nội tuyến. Chỉ cần quan tâm phiên bản nguyên cảo transpiled là hầu như giống như Babel: typescriptlang.org/play/...
Đánh dấu Whitfeld

2
hmm ... hai trường hợp của bạn sẽ không tạo ra kết quả giống nhau chứ? trong trường hợp đầu tiên, bạn sao chép các thuộc tính từ đối tượng này sang đối tượng khác và trong trường hợp khác bạn đang tạo một đối tượng mới. Object.assign trả về mục tiêu, vì vậy trong trường hợp đầu tiên của bạn objAss và newObjAss là như nhau.
Kevin B

Thêm một tham số đầu tiên mới {}sẽ khắc phục sự không nhất quán.
Kevin B

11

Toán tử trải rộng đối tượng (...) không hoạt động trong các trình duyệt, vì nó chưa phải là một phần của bất kỳ đặc tả ES nào, chỉ là một đề xuất. Tùy chọn duy nhất là biên dịch nó với Babel (hoặc một cái gì đó tương tự).

Như bạn có thể thấy, đó chỉ là cú pháp cú pháp so với Object.assign ({}).

Theo như tôi có thể thấy, đây là những khác biệt quan trọng.

  • Object.assign hoạt động trong hầu hết các trình duyệt (không biên dịch)
  • ... cho các đối tượng không được tiêu chuẩn hóa
  • ... bảo vệ bạn khỏi vô tình làm biến đổi đối tượng
  • ... sẽ polyfill Object.assign trong các trình duyệt mà không có nó
  • ... cần ít mã hơn để diễn đạt cùng một ý tưởng

34
không phải là cú pháp cho Object.assign, vì toán tử lây lan sẽ luôn cung cấp cho bạn một đối tượng mới.
MaxArt

4
Trên thực tế, tôi ngạc nhiên khi những người khác không nhấn mạnh vào sự khác biệt về tính đột biến nhiều hơn. Hãy nghĩ về tất cả các nhà phát triển đã mất việc gỡ lỗi các đột biến ngẫu nhiên với Object.assign
deepelement 13/2/19

Điều này hiện được hỗ trợ trong hầu hết các trình duyệt hiện đại (như với ES6 khác): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
trộm

11

Tôi muốn tóm tắt trạng thái của tính năng ES "đối tượng lây lan", trong các trình duyệt và trong hệ sinh thái thông qua các công cụ.

Thông số kỹ thuật

Trình duyệt: trong Chrome, trong SF, Firefox sớm (ver 60, IIUC)

  • Hỗ trợ trình duyệt cho "thuộc tính lây lan" được phân phối trong Chrome 60 , bao gồm cả kịch bản này.
  • Hỗ trợ cho kịch bản này KHÔNG hoạt động trong Firefox hiện tại (59), nhưng DOES hoạt động trong Phiên bản Firefox Developer của tôi. Vì vậy, tôi tin rằng nó sẽ xuất xưởng trong Firefox 60.
  • Safari: chưa được thử nghiệm, nhưng Kangax cho biết nó hoạt động trong Desktop Safari 11.1, nhưng không phải SF 11
  • iOS Safari: không được chỉnh sửa, nhưng Kangax cho biết nó hoạt động trong iOS 11.3, nhưng không hoạt động trong iOS 11
  • chưa có trong Edge

Công cụ: Nút 8.7, TS 2.1

Liên kết

Mẫu mã (tăng gấp đôi khi kiểm tra khả năng tương thích)

var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }

Một lần nữa: Tại thời điểm viết mẫu này hoạt động mà không cần phiên dịch trong Chrome (60+), Firefox Developer Edition (bản xem trước của Firefox 60) và Node (8.7+).

Tại sao trả lời?

Tôi đang viết 2,5 năm sau câu hỏi ban đầu. Nhưng tôi đã có cùng một câu hỏi và đây là nơi Google gửi cho tôi. Tôi là nô lệ cho nhiệm vụ của SO để cải thiện cái đuôi dài.

Vì đây là một bản mở rộng của cú pháp "trải rộng mảng", tôi thấy nó rất khó tìm kiếm trên google và rất khó tìm thấy trong các bảng tương thích. Gần nhất tôi có thể tìm thấy là "tài sản lây lan" Kangax , nhưng thử nghiệm đó không có hai lệch trong cùng một biểu thức (không phải là hợp nhất). Ngoài ra, tên trong các trang đề xuất / dự thảo / trạng thái trình duyệt đều sử dụng "lây lan tài sản", nhưng theo tôi thì đó là "hiệu trưởng đầu tiên" mà cộng đồng đặt ra sau khi đề xuất sử dụng cú pháp lây lan cho "hợp nhất đối tượng". (Điều này có thể giải thích lý do tại sao nó rất khó để google.) Vì vậy, tôi ghi lại phát hiện của mình ở đây để người khác có thể xem, cập nhật và biên dịch các liên kết về tính năng cụ thể này. Tôi hy vọng nó bắt kịp. Hãy giúp truyền bá tin tức về nó hạ cánh trong thông số kỹ thuật và trong các trình duyệt.

Cuối cùng, tôi đã thêm thông tin này dưới dạng nhận xét, nhưng tôi không thể chỉnh sửa chúng mà không phá vỡ ý định ban đầu của tác giả. Cụ thể, tôi không thể chỉnh sửa nhận xét của @ ChillyPenguin mà không mất ý định sửa lỗi @RichardSchulte. Nhưng nhiều năm sau Richard hóa ra là đúng (theo ý kiến ​​của tôi). Vì vậy, tôi viết câu trả lời này thay vào đó, hy vọng cuối cùng nó sẽ đạt được lực kéo đối với các câu trả lời cũ (có thể mất nhiều năm, nhưng rốt cuộc đó là hiệu ứng đuôi dài là gì).


3
phần "tại sao trả lời" của bạn có lẽ không cần thiết
LocustHorde

@LocustHorde Có lẽ tôi có thể chuyển đoạn thứ 2 (tại sao chủ đề này rất khó để google) sang phần riêng của nó. Sau đó, phần còn lại có thể phù hợp với một nhận xét.
yzorg

8

LƯU Ý: Lan truyền KHÔNG chỉ là đường cú pháp xung quanh Object.assign. Họ hoạt động khác nhau đằng sau hậu trường.

Object.assign áp dụng setters cho một đối tượng mới, lây lan không. Ngoài ra, đối tượng phải được lặp lại.

Sao chép Sử dụng điều này nếu bạn cần giá trị của đối tượng như hiện tại và bạn không muốn giá trị đó phản ánh bất kỳ thay đổi nào được thực hiện bởi các chủ sở hữu khác của đối tượng.

Sử dụng nó để tạo một bản sao nông của đối tượng, thực hành tốt để luôn đặt các thuộc tính bất biến thành sao chép - vì các phiên bản có thể thay đổi có thể được chuyển thành các thuộc tính bất biến, sao chép sẽ đảm bảo rằng bạn sẽ luôn luôn xử lý một đối tượng bất biến

Assign Assign có phần ngược lại để sao chép. Assign sẽ tạo ra một setter gán trực tiếp giá trị cho biến thể hiện, thay vì sao chép hoặc giữ lại nó. Khi gọi getter của thuộc tính gán, nó trả về một tham chiếu đến dữ liệu thực tế.


3
Tại sao nó nói "Sao chép"? Những tiêu đề táo bạo này là gì. Tôi cảm thấy như đã bỏ lỡ một số bối cảnh khi tôi đọc điều này ...
ADJenks

2

Các câu trả lời khác đã cũ, không thể có được một câu trả lời tốt.
Ví dụ dưới đây dành cho các đối tượng bằng chữ, giúp làm thế nào cả hai có thể bổ sung cho nhau và làm thế nào nó không thể bổ sung cho nhau (do đó khác biệt):

var obj1 = { a: 1,  b: { b1: 1, b2: 'b2value', b3: 'b3value' } };

// overwrite parts of b key
var obj2 = {
      b: {
        ...obj1.b,
        b1: 2
      }
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}}  // NOTE: b2,b3 still exists

// overwrite whole of b key
var obj3 = {
      b: {
        b1: 2
      }
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
  // res3: {"a":1,"b":{"b1":2}}  // NOTE: b2,b3 values are lost

Một số ví dụ nhỏ khác ở đây, cũng cho mảng & đối tượng:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Siverse_syntax


1

Đây hiện là một phần của ES6, do đó được chuẩn hóa và cũng được ghi lại trên MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Siverse_operator

Nó rất thuận tiện để sử dụng và có nhiều ý nghĩa bên cạnh việc phá hủy đối tượng.

Ưu điểm còn lại được liệt kê ở trên là khả năng động của Object.assign (), tuy nhiên điều này dễ như việc trải rộng mảng bên trong của một đối tượng theo nghĩa đen. Trong đầu ra babel đã biên dịch, nó sử dụng chính xác những gì được thể hiện với Object.assign ()

Vì vậy, câu trả lời đúng sẽ là sử dụng trải rộng đối tượng vì nó đã được chuẩn hóa, sử dụng rộng rãi (xem phản ứng, chuyển hướng, v.v.), rất dễ sử dụng và có tất cả các tính năng của Object.assign ()


2
Không, nó không phải là một phần của ES6. Liên kết mà bạn đã cung cấp chỉ đề cập đến việc sử dụng toán tử trải trên các mảng . Việc sử dụng toán tử trải trên các đối tượng hiện là đề xuất Giai đoạn 2 (tức là Dự thảo), như được giải thích trong câu trả lời của JMM.
ChillyPenguin

1
Tôi không nghĩ rằng tôi đã từng thấy một câu trả lời hoàn toàn dựa trên thông tin sai lệch. Thậm chí một năm sau, đó không phải là một phần nếu thông số ES và không được hỗ trợ trong hầu hết các môi trường.
3ocene

Chilly và 3o hóa ra là sai, Richard đúng. Hỗ trợ trình duyệt và hỗ trợ công cụ đều hạ cánh, nhưng phải mất 1,5 năm sau câu trả lời của Richard. Xem câu trả lời mới của tôi để biết tóm tắt về hỗ trợ kể từ tháng 3 năm 2018.
yzorg

0

Tôi muốn thêm ví dụ đơn giản này khi bạn phải sử dụng Object.assign.

class SomeClass {
  constructor() {
    this.someValue = 'some value';
  }

  someMethod() {
    console.log('some action');
  }
}


const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok

const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!

Nó có thể không rõ ràng khi bạn sử dụng JavaScript. Nhưng với TypeScript sẽ dễ dàng hơn nếu bạn muốn tạo cá thể của một số lớp

const spread: SomeClass = {...new SomeClass()} // Error
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.