Object.create()時にenumerableをtrueにしておかないとオブジェクトがクローンできない件
Object.create()とは
JavaScriptにおいて、プロトタイプベースの継承を行う際によく用いられる関数です。
拙著過去記事も参照ください
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ライフをすごしましょう!