LoginSignup
17

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-05-01

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 が上げられます。

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 を定義すると、(デフォルトでは)列挙不可能になります。

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

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

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

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

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

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

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 ではない)列挙可能なプロパティーになってますね。これではダメです。

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

prev[curr] = hoge[curr];

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

完全なコピー

完全なコピー
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.assign

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

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

一瞬でコピーできた!やった!Object.assign は神!

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

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
17