MoyaSystem

もやしです。

Object.create()時にenumerableをtrueにしておかないとオブジェクトがクローンできない件

Object.create()とは

JavaScriptにおいて、プロトタイプベースの継承を行う際によく用いられる関数です。

MDN: Object.create()

拙著過去記事も参照ください

JavaScriptにクラスはありません - オブジェクトリテラルとコンストラクタパターン編 -

JavaScriptにクラスはありません - プロトタイプで継承編 -

オブジェクト指向でよくみられる、「親クラスのプロパティをすべて再利用しつつ、子クラス独自のプロパティも定義する」ことをJavaScriptで実現できます。使用例を次に示します。

var obj = function obj(){};
obj.prototype.prop = function(){alert('prop')};
// オブジェクト生成
var object = Object.create(obj.prototype,{
    hoge: {value: 'fuga'},
    foo: {value: 'bar'}
});

var subobj = function subobj(){};
subobj.prototype = new obj(); // objのプロパティを継承
subobj.prototype.myprop = function(){alert('myprop')}; // 独自のプロパティ

例に示したように、Object.createの引数はふたつあります。第1引数は新しいオブジェクトのプロトタイプ、そして第2引数はプロパティオブジェクトと呼ばれる、オブジェクトのプロパティを要素に持つオブジェクトです。 このプロパティオブジェクト、設定値がいろいろあるのですが、悪いこと言わないのでenumerableをtrueにしておけという話です。

enumerableをtrueにしておかないと

何が困るかというと、underscore.jsやlodash.jsを使ってオブジェクトをクローンする時に困るのです。 JavaScriptにおいて、オブジェクトや配列の代入は参照渡しです。そのため、あるオブジェクトを変数に格納して、そののちオブジェクトを編集すると、変数の中身も変わります。

var obj = {hoge: 'fuga'};
var a = obj;
obj.hoge = 'piyo';
console.log(a.hoge); // 'piyo'

残念ながら通常のJavaScriptで実体の代入を実現するのは非常にめんどくさいです。そのためライブラリを用いるのが一般的です。わたしはlodash.jsで用意されている_.cloneDeep()関数をよく使っています。

var obj = {hoge: 'fuga'};
var a = _.cloneDeep(obj); //実体のコピーを作成して代入
obj.hoge = 'piyo';
console.log(a.hoge); // 'fuga'

ところが、Object.create()で作られたオブジェクトをこの関数に適用させる場合、プロパティのenumerableがtrueになっていないとコピーされないのです。 enumerableはデフォルトでfalseになっているので(なぜだ……)、明示的にtrueを指定してやる必要があります。

var obj = function obj(){};
obj.prototype.prop = function(){alert('prop')};
// オブジェクト生成
// ダメな例
var no_good_object = Object.create(obj.prototype,{
    hoge: {value: 'fuga'},
    foo: {value: 'bar'}
});
// これはOK
var good_object = Object.create(obj.prototype,{
    hoge: {value: 'fuga', enumerable: true},
    foo: {value: 'bar', enumerable: true}
});
var a = _.cloneDeep(no_good_object); // これはクローンされない!
var b = _.cloneDeep(good_object); // これはクローンされる

この現象に直面して、lodash.jsのバグじゃないかと思いライブラリのコードを追いかけて数時間無駄にしたのが今日の筆者です。ということで、enumerableをtrueにして快適JavaScriptライフをすごしましょう!