238
94

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Object.assign({}, obj) と { ...obj } の違い

Last updated at Posted at 2019-12-21

オブジェクトリテラル内のスプレッド構文は、ES2018で追加されたたいへん便利な構文です。特に、{ ...obj }という形のコードでオブジェクトをコピーするのはJavaScriptプログラミングでは極めて頻出です。

スプレッド構文が無かった時代はObject.assign({}, obj)として同様のことを達成していた方も多いと思われます。Object.assignはES2015から使用可能でした。

では、この2種類の方法は同じでしょうか。タイトルにもある通り、もちろん違います。今回は、この違いに触れている日本語資料がMDN日本語版で一瞬触れているくらいしか無かったので記事にまとめました。

結論

最初に結論を述べると、Object.prototypeが汚染されていた場合にのみ違いが発生します。特に、Object.prototypeにsetterを持つプロパティ名が存在し、そのプロパティ名でコピーしようとした場合に違いが現れます。

Object.defineProperty(Object.prototype, "foo", {
  get() { return 100; },
  set(n) { console.log(`foo is set to ${n}`); }
});

console.log({}.foo); // 100

const sourceObj = { foo: 999 };

const obj1 = Object.assign({}, sourceObj); // foo is set to 999 と表示される
const obj2 = { ...sourceObj };             // 何も表示されない

console.log(obj1.foo, obj2.foo); // 100 999

obj1obj2を2つの方法で作ったあと、それぞれのfooプロパティの値を調べると異なる値となっています。

解説

Object.assignとスプレッド構文は非常に似た動作をしますが、その違いを一言でまとめるとこうです。すなわち、Object.assign代入によってプロパティをコピーする一方で、スプレッド構文はプロパティ自体の作成によってプロパティをコピーします。

すなわち、sourceObj{ foo: 999 }であるという前提で、const obj1 = Object.assign({}, sourceObj)の挙動は以下とおおよそ同様です。

const obj1 = {};
obj1.foo = sourceObj.foo;

一方、const obj2 = { ...sourceObj };の挙動は以下とおおよそ同様です。

const obj2 = {};
Object.defineProperty(obj2, "foo", {
  configurable: true,
  enumerable: true,
  writable: true,
  value: sourceObj.foo
});

前者はobj1.fooに対する代入ですから、obj1.fooがセッタを持っていた場合はそれが呼び出されることになります。一方、後者はObject.definePropertyによるプロパティの作成なので、セッタが呼び出されません。今回は{}で新規オブジェクトを作ってそれを対象としているので、その違いを引き出すためにはObject.prototypeを汚染する必要がありました。

仕様書を実際に見てみると、Object.assign...objは非常に似た処理をしていることが分かります。

まずObject.assignの定義を仕様書から画像で引用します。

19.1.2.1 Object.assign

次に、...objの処理の本体部分を使用書から画像で引用します。なお、この場合excludedItemsは空リストになります。

7.3.24CopyDataProperties

この2つの仕様を注意深く比べると、本質的な違いは下から2行目だけであることが見て取れます。Object.assignはここでSetを使っているのに対し、スプレッド構文ではCreateDataPropertyOrThrowを使っています。深入りするのはやめておきますが、前者がプロパティへの代入に相当する操作である一方、後者はObject.definePropertyに相当する操作です。

まとめ

Object.prototypeが汚染されていたときのことを考えながらコードを書きましょう!Object.prototypeを汚染するのはやめましょう。

238
94
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
238
94

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?