Object をコピーしたいマンの悩みとその解決法

JavaScript でオブジェクトをコピーしたいと思ってですね。こんなコードを書いたんです。

```javascript:ものすごく単純なコピー
var hoge = {
  name: "ほげ",
};

var fuga = Object.keys(hoge).reduce(function(prev, curr) {
  prev[curr] = hoge[curr];
  return prev;
}, {});

fuga.name === "ほげ"; // true
```

[Object.keys][] はオブジェクトの**列挙可能な**プロパティーを返す関数です。この時点で地雷臭がしますね。では**列挙不可能なプロパティー**って何でしょう。代表的なものには getter/setter が上げられます。

[Object.keys]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

```javascript:getter付きのオブジェクト
var hoge = {
  name: "ほげ",
  get shout: function() {
    return this.name + "ほげ〜";
  },
};

hoge.name === "ほげ";        // true
hoge.shout === "ほげほげ〜"; // true
```

こんなやつですね。

/* 追記ここから 2016-05-02 */

ここ嘘でした。getter / setter だからといって列挙不可能になるわけではありません。この例のように Object literal で getter / setter を定義した場合は、相変わらず列挙可能です。後述の、Object.defineProperty で getter / setter を定義すると、（デフォルトでは）列挙不可能になります。

```javascript
Object.defineProperty(hoge, "shout", {
  get: function() {
    return this.name + "ほげ〜";
  },
  // enumerable: true,    // これを入れると列挙可能になる。
});
```

/* 追記ここまで 2016-05-02 */

これをコピーするにはどうしたらいいの？ってことで出てくるのが [Object.getOwnPropertyNames][]。

[Object.getOwnPropertyNames]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames

```javascript:getOwnPropertyNamesを使ってみる
var fuga = Object.getOwnPropertyNames(hoge).reduce(function(prev, curr) {
  prev[curr] = hoge[curr];
  return prev;
}, {});

fuga.name === "ほげ";        // true
fuga.shout === "ほげほげ〜"; // true
```

コピーできてる！やった！……とはなりません。

```javascript
Object.getOwnPropertyDescriptor(hoge, "shout");
// {get: function, set: undefined, enumerable: false, configurable: false}

Object.getOwnPropertyDescriptor(fuga, "shout");
// {value: "ほげほげ〜", writable: true, enumerable: true, configurable: true}
```

ここで突然出てきた [Object.getOwnPropertyDescriptor][] はプロパティーの様々な属性（**descriptor**）を取得するためのメソッドです。`shout` が普通の（getter ではない）列挙可能なプロパティーになってますね。これではダメです。

[Object.getOwnPropertyDescriptor]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor

ここまで来ればおわかりと思いますが、上記のコードの問題は

```javascript
prev[curr] = hoge[curr];
```

これだったのです。単なる値のコピーではなく、descriptor をコピーする必要があります。

# 完全なコピー

```javascript:完全なコピー
var fuga = Object.getOwnPropertyNames(hoge).reduce(function(prev, curr) {
  var descriptor = Object.getOwnPropertyDescriptor(hoge, curr);
  Object.defineProperty(prev, curr, descriptor);
  return prev;
}, {});

fuga.name === "ほげ";        // true
fuga.shout === "ほげほげ〜"; // true
Object.getOwnPropertyDescriptor(fuga, "shout").enumerable; // false => 列挙不可能
```

又新しいメソッドです。[Object.defineProperty][] は指定した descriptor で任意のプロパティーを追加してくれます。これでやっと悩みが解決されました。

[Object.defineProperty]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

しかし、たかだか**オブジェクトをコピーする**という目的のためだけに、毎回こんな複雑なコードを書かなければならないんでしょうか？

# 救世主: Object.assign

```javascript
// Object.assign が使えるならこれでいい。
// var fuga = Object.assign(hoge);  // 修正前。コメント参照
var fuga = Object.assign({}, hoge); // 修正後

fuga.name === "ほげ";
fuga.shout === "ほげほげ〜";
```

一瞬でコピーできた！やった！[Object.assign][] は神！

でもこれ、[**IE 11 では使えません**][assign]。他のブラウザーは主要なブラウザーはサポートしてるんですけどね。人類はいつまで IE に苦しめられることになるんですかね……。つらい……。

[Object.assign]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
[assign]: http://kangax.github.io/compat-table/es6/#test-Object_static_methods_Object.assign