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 が上げられます。
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。
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 に苦しめられることになるんですかね……。つらい……。