Penyebaran objek vs Object.assign

396

Katakanlah saya punya options variabel dan saya ingin menetapkan beberapa nilai default.

Apa manfaat / kelemahan dari dua alternatif ini?

Menggunakan objek menyebar

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

Atau menggunakan Object.assign

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

Ini adalah komitmen yang membuat saya bertanya-tanya.

Olivier Tassinari
sumber
4
Nah, yang pertama adalah sintaks baru yang diusulkan dan bukan bagian dari ES6, jadi itu tergantung pada standar apa yang ingin Anda patuhi.
loganfsmyth
5
Definisikan " terbaik " (hati-hati, jangan berakhir dengan pertanyaan berdasarkan opini :-)
Amit
1
Bisa juga tergantung bagaimana Anda ingin mendukungnya jika berjalan di lingkungan tanpa dukungan asli. Sintaks Anda mungkin hanya dapat mengkompilasi. Objek atau metode yang mungkin perlu Anda isi.
JMM
6
Terlepas dari masalah kompatibilitas, Object.assign dapat bermutasi objek asli yang bermanfaat. penyebaran tidak bisa.
pstanton
2
Untuk mengklarifikasi komentar @ pstanton - object.assign dapat memodifikasi objek target yang ada (menimpa properti dari sumber, sambil membiarkan properti lain tetap utuh); itu tidak menyentuh objek sumber . Saya pertama kali membaca "objek aslinya" sebagai "objek sumber", jadi menulis catatan ini untuk orang lain yang juga salah baca. :)
ToolmakerSteve

Jawaban:

328

Ini belum tentu lengkap.

Sebarkan sintaks

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

Keuntungan:

  • Jika membuat kode untuk dieksekusi di lingkungan tanpa dukungan asli, Anda mungkin bisa mengkompilasi sintaks ini (bukan menggunakan polyfill). (Dengan Babel, misalnya.)

  • Kurang bertele-tele.

Kekurangan:

  • Ketika jawaban ini awalnya ditulis, ini adalah proposal , bukan standar. Saat menggunakan proposal pertimbangkan apa yang akan Anda lakukan jika Anda menulis kode dengannya sekarang dan itu tidak mendapatkan standar atau perubahan saat bergerak menuju standardisasi. Sejak ini telah distandarisasi dalam ES2018.

  • Secara literal, tidak dinamis.


Object.assign()

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

Keuntungan:

  • Standar

  • Dinamis. Contoh:

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

Kekurangan:

  • Lebih banyak verbose.
  • Jika membuat kode untuk eksekusi di lingkungan tanpa dukungan asli, Anda perlu melakukan polyfill.

Ini adalah komitmen yang membuat saya bertanya-tanya.

Itu tidak terkait langsung dengan apa yang Anda minta. Kode itu tidak menggunakan Object.assign(), itu menggunakan kode pengguna ( object-assign) yang melakukan hal yang sama. Mereka tampaknya mengkompilasi kode itu dengan Babel (dan menggabungkannya dengan Webpack), yang saya bicarakan: sintaks yang bisa Anda kompilasi. Mereka tampaknya lebih suka daripada harus dimasukkan object-assignsebagai ketergantungan yang akan masuk ke gedung mereka.

JMM
sumber
15
Mungkin perlu dicatat bahwa penyebaran objek telah pindah ke tahap 3 sehingga kemungkinan akan distandarisasi di masa depan twitter.com/sebmarkbage/status/781564713750573056
williaster
11
@ JMM saya tidak yakin saya melihat "Lebih banyak kata." sebagai kerugian.
DiverseAndRemote.com
6
@ Omar baik saya kira Anda baru saja membuktikan bahwa itu adalah pendapat :) Jika seseorang tidak mengenali keuntungan itu ya, semua yang lain sama-sama bisa mereka gunakan Object.assign(). Atau Anda bisa secara manual mengulangi lebih dari satu array objek dan props mereka sendiri secara manual menugaskan mereka ke target dan membuatnya lebih verbose: P
JMM
7
seperti yang disebutkan @JMM, sekarang dalam ES2018 spec node.green/#ES2018-features-object-rest-spread-properties
Sebastien H.
2
"Every byte counts" Itulah yang diminimalkan oleh / minimugable untuk @yzorg
DiverseAndRemote.com
171

Untuk objek referensi istirahat / spread diselesaikan dalam ECMAScript 2018 sebagai tahap 4. Proposal dapat ditemukan di sini .

Untuk sebagian besar objek reset dan spread bekerja dengan cara yang sama, perbedaan utama adalah bahwa spread mendefinisikan properti, sementara Object.assign () menyetelnya . Ini berarti Object.assign () memicu setter.

Perlu diingat bahwa selain ini, objek rest / spread 1: 1 memetakan ke Object.assign () dan bertindak berbeda terhadap array (iterable) spread. Misalnya, ketika menyebarkan nilai null array tersebar. Namun menggunakan objek, menyebarkan nilai nol secara diam-diam menyebar ke apa-apa.

Array (Iterable) Sebarkan Contoh

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

Contoh Penyebaran Objek

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

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

Ini konsisten dengan cara kerja Object.assign (), keduanya diam-diam mengecualikan nilai nol tanpa kesalahan.

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

console.log(z); //{a: 1, b: 2}
tomhughes
sumber
10
Ini seharusnya jawaban yang dipilih.
Evan Plaice
4
Ini harus menjadi jawabannya ... ini adalah masa depan sekarang.
Zachary Abresch
1
Ini adalah jawaban yang benar. Perbedaan utama adalah Object.assignmenggunakan setter. 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
"Masa depan SEKARANG!" - George Allen Besok sudah terlambat.
ruffin
1
Demonstrasi nol yang ditangani secara berbeda adalah "apel dan jeruk" - bukan perbandingan yang berarti. Dalam kasus array, null adalah elemen array. Dalam kasus penyebaran, null adalah seluruh objek. Perbandingan yang benar akan untuk x untuk memiliki properti nol : const x = {c: null};. Dalam hal ini, AFAIK, kita akan melihat perilaku seperti array: //{a: 1, b: 2, c: null}.
ToolmakerSteve
39

Saya pikir satu perbedaan besar antara operator spread dan Object.assignyang tampaknya tidak disebutkan dalam jawaban saat ini adalah bahwa operator spread tidak akan menyalin prototipe objek sumber ke objek target. Jika Anda ingin menambahkan properti ke objek dan Anda tidak ingin mengubah instance itu, maka Anda harus menggunakannya Object.assign. Contoh di bawah ini harus menunjukkan ini:

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

Sean Dawson
sumber
"tidak akan menjaga prototipe tetap utuh" - itu pasti akan, karena tidak mengubah apa pun. Dalam contoh Anda errorExtendedUsingAssign === error,, tetapi errorExtendedUsingSpreadobjek baru (dan prototipe tidak disalin).
maaartinus
2
@maaartinus Anda benar, saya mungkin mengatakannya dengan sangat buruk. Maksud saya prototipe tidak pada objek yang disalin. Saya mungkin mengeditnya agar lebih jelas.
Sean Dawson
Apakah cara berikut untuk "mengkloning" objek dengan kelasnya? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve
@ToolmakerSteve Ya itu akan menyalin semua "properti sendiri" objek yang secara efektif akan menjadi klon dangkal. Lihat: stackoverflow.com/questions/33692912/…
Sean Dawson
12

Seperti yang telah disebutkan orang lain, pada saat penulisan ini, Object.assign()membutuhkan polyfill dan penyebaran objek ...memerlukan beberapa pengubahan (dan mungkin juga polyfill) agar dapat bekerja.

Pertimbangkan kode ini:

// 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);

Keduanya menghasilkan output yang sama.

Ini adalah output dari Babel, ke 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);

Ini pemahaman saya sejauh ini. Object.assign()sebenarnya standar, sedangkan objek ...belum menyebar . Satu-satunya masalah adalah dukungan browser untuk yang pertama dan di masa depan, yang terakhir juga.

Mainkan dengan kode di sini

Semoga ini membantu.

Michael Giovanni Pumo
sumber
1
Terima kasih! Contoh kode Anda membuat keputusan sangat mudah untuk konteks saya. Transpiler (babel atau naskah) membuat operator spread lebih kompatibel untuk browser dengan memasukkan pollyfill dalam kode inline. Hanya untuk kepentingan versi transcript TypeScript hampir sama dengan Babel: typescriptlang.org/play/…
Mark Whitfeld
2
hmm ... bukankah kedua kasing Anda tidak menghasilkan hasil yang sama? dalam kasus pertama Anda menyalin properti dari satu objek ke yang lain, dan di yang lain Anda membuat objek baru. Object.assign mengembalikan target, jadi dalam kasus pertama Anda objAss dan newObjAss sama.
Kevin B
Menambahkan parameter pertama baru {}harus memperbaiki ketidakkonsistenan.
Kevin B
11

Operator penyebaran objek (...) tidak berfungsi di browser, karena itu belum menjadi bagian dari spesifikasi ES apa pun, hanya proposal. Satu-satunya pilihan adalah mengkompilasinya dengan Babel (atau yang serupa).

Seperti yang Anda lihat, itu hanya gula sintaksis di atas Object.assign ({}).

Sejauh yang saya bisa lihat, ini adalah perbedaan penting.

  • Object.assign berfungsi di sebagian besar browser (tanpa kompilasi)
  • ... untuk objek tidak terstandarisasi
  • ... melindungi Anda dari secara tidak sengaja bermutasi objek
  • ... akan melakukan polyfill Object.assign di browser tanpa itu
  • ... membutuhkan lebih sedikit kode untuk mengekspresikan ide yang sama
Karthick Kumar
sumber
34
Ini bukan gula sintaksis Object.assign, karena operator spread akan selalu memberi Anda objek baru.
MaxArt
4
Sebenarnya, saya terkejut bahwa orang lain tidak lebih menekankan perbedaan sifat berubah-ubah. Pikirkan semua jam pengembang kehilangan debug karena mutasi yang tidak disengaja dengan Object.assign
deepelement
Ini sekarang didukung di sebagian besar browser modern (seperti dengan ES6 lainnya): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991
11

Saya ingin merangkum status fitur ES "spread object merge", di browser, dan di ekosistem melalui alat.

Spec

Browser: di Chrome, di SF, Firefox segera (ver 60, IIUC)

  • Dukungan browser untuk "sebarkan properti" yang dikirim di Chrome 60 , termasuk skenario ini.
  • Dukungan untuk skenario ini TIDAK berfungsi di Firefox saat ini (59), tetapi DOES bekerja di Edisi Pengembang Firefox saya. Jadi saya yakin itu akan dikirimkan di Firefox 60.
  • Safari: tidak diuji, tetapi Kangax mengatakan itu berfungsi di Desktop Safari 11.1, tetapi tidak SF 11
  • iOS Safari: tidak diuji, tetapi Kangax mengatakan itu bekerja di iOS 11.3, tetapi tidak di iOS 11
  • belum di Edge

Alat: Node 8.7, TS 2.1

Tautan

Contoh Kode (berfungsi ganda sebagai uji kompatibilitas)

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 }

Lagi: Pada saat penulisan, sampel ini berfungsi tanpa transpilasi di Chrome (60+), Edisi Pengembang Firefox (preview Firefox 60), dan Node (8.7+).

Mengapa Menjawab

Saya menulis ini 2,5 tahun setelah pertanyaan awal. Tetapi saya memiliki pertanyaan yang sama, dan di sinilah Google mengirim saya. Saya seorang budak misi SO untuk memperbaiki ekor panjang.

Karena ini merupakan perluasan dari sintaks "array spread", saya merasa sangat sulit untuk google, dan sulit ditemukan di tabel kompatibilitas. Yang paling dekat yang bisa saya temukan adalah Kangax "sebaran properti" , tetapi tes itu tidak memiliki dua spread dalam ekspresi yang sama (bukan gabungan). Juga, nama dalam halaman status proposal / konsep / browser semua menggunakan "sebaran properti", tetapi bagi saya sepertinya itu adalah "prinsipal pertama" yang diterima komunitas setelah proposal untuk menggunakan sintaksis spread untuk "penggabungan objek". (Yang mungkin menjelaskan mengapa sangat sulit untuk google.) Jadi saya mendokumentasikan temuan saya di sini sehingga orang lain dapat melihat, memperbarui, dan menyusun tautan tentang fitur spesifik ini. Saya harap ini berhasil. Tolong bantu sebarkan berita itu mendarat di spec dan di browser.

Terakhir, saya akan menambahkan info ini sebagai komentar, tetapi saya tidak dapat mengeditnya tanpa melanggar maksud asli penulis. Secara khusus, saya tidak dapat mengedit komentar @ ChillyPenguin tanpa kehilangan niatnya untuk memperbaiki @RichardSchulte. Tetapi bertahun-tahun kemudian Richard ternyata benar (menurut saya). Jadi saya menulis jawaban ini sebagai gantinya, berharap akan mendapatkan daya tarik pada jawaban lama pada akhirnya (mungkin butuh bertahun-tahun, tapi itulah efek dari long tail , setelah semua).

yzorg
sumber
3
bagian "mengapa jawaban" Anda mungkin tidak diperlukan
LocustHorde
@LocustHorde Mungkin saya bisa memindahkan paragraf ke-2 (mengapa topik ini sangat sulit untuk google) ke bagian itu sendiri. Kemudian sisanya mungkin cocok dengan komentar.
yzorg
8

CATATAN: Spread BUKAN hanya gula sintaksis di sekitar Object.assign. Mereka beroperasi jauh berbeda di belakang layar.

Object.assign menerapkan setter ke objek baru, Spread tidak. Selain itu, objek tersebut harus dapat diubah.

Salin Gunakan ini jika Anda memerlukan nilai objek seperti saat ini, dan Anda tidak ingin nilai mencerminkan perubahan yang dibuat oleh pemilik objek lain.

Gunakan untuk membuat salinan dangkal objek praktik yang baik untuk selalu menetapkan properti yang tidak dapat diubah untuk disalin - karena versi yang dapat diubah dapat dipindahkan ke properti yang tidak dapat diubah, salinan akan memastikan bahwa Anda akan selalu berurusan dengan objek yang tidak dapat diubah

Tetapkan Tetapkan agak berlawanan dengan menyalin. Tetapkan akan menghasilkan setter yang memberikan nilai ke variabel instan secara langsung, daripada menyalin atau mempertahankannya. Saat memanggil pengambil properti penetapan, ia mengembalikan referensi ke data aktual.

Charles Owen
sumber
3
Mengapa dikatakan "Salin"? Apa judul yang berani ini. Saya merasa seperti saya melewatkan beberapa konteks ketika saya membaca ini ...
ADJenks
2

Jawaban lain sudah tua, tidak bisa mendapat jawaban yang baik.
Contoh di bawah ini adalah untuk objek literal, membantu bagaimana keduanya dapat saling melengkapi, dan bagaimana keduanya tidak dapat saling melengkapi (oleh karena itu perbedaan):

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

Beberapa contoh kecil lainnya di sini, juga untuk array & objek:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Manohar Reddy Poreddy
sumber
1

Ini sekarang bagian dari ES6, dengan demikian distandarisasi, dan juga didokumentasikan di MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

Ini sangat nyaman untuk digunakan dan membuat banyak akal bersama perusakan objek.

Satu kelebihan yang tersisa yang tercantum di atas adalah kemampuan dinamis Object.assign (), namun ini semudah menyebarkan array di dalam objek literal. Dalam output babel yang dikompilasi, ia menggunakan apa yang ditunjukkan dengan Object.assign ()

Jadi jawaban yang benar adalah dengan menggunakan objek menyebar karena sekarang distandarisasi, banyak digunakan (lihat bereaksi, redux, dll), mudah digunakan, dan memiliki semua fitur Object.assign ()

Rikki Schulte
sumber
2
Tidak, ini bukan bagian dari ES6. Tautan yang Anda berikan merujuk pada penggunaan operator spread pada array saja. Penggunaan operator spread pada objek saat ini merupakan proposal Tahap 2 (yaitu Konsep), seperti yang dijelaskan dalam jawaban JMM.
ChillyPenguin
1
Saya tidak berpikir saya pernah melihat jawaban yang sepenuhnya berdasarkan informasi yang salah. Bahkan setahun kemudian, itu bukan bagian jika spec ES dan tidak didukung di sebagian besar lingkungan.
3ocene
Chilly dan 3o ternyata salah, Richard benar. Dukungan browser dan dukungan alat semuanya masuk, tetapi butuh 1,5 tahun setelah jawaban Richard. Lihat jawaban baru saya untuk ringkasan dukungan pada Maret 2018.
yzorg
0

Saya ingin menambahkan contoh sederhana ini ketika Anda harus menggunakan 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!

Tidak jelas saat Anda menggunakan JavaScript. Tetapi dengan TypeScript lebih mudah jika Anda ingin membuat instance dari beberapa kelas

const spread: SomeClass = {...new SomeClass()} // Error
zemil
sumber