Webtech Walker

Backbone.Modelのattributesにオブジェクト入れるときの注意

Backbone.js Advent Calendarの15日目です。軽めにいきます。

Backbone.Modelのattributesにオブジェクトを設定するときの注意点など。attributessetとかで設定される値をオブジェクトして持っているやつです。

まず次のようにsetattributesを設定します。

var MyModel = Backbone.Model.extend();

var m = new MyModel();
m.set('hoge', 'fuga');
m.set('foo', { bar: 'baz' });

このようにhogeには文字列、fooにはオブジェクトを設定しました。そしてtoJSONattributesを取得して値を更新してみます。

var attrs = m.toJSON();
attrs.hoge = 'new fuga';
attrs.foo.bar = 'new baz';

そしてattributesの中身を見てみると・・

console.log(m.attributes);
// => { hoge: 'fuga', foo: { bar: 'new baz' } }

hogeの値は変わってないのにfoo.barの値が変わってますね。どうしてこうなった。

と、まあこういう問題があるわけです。

では原因を見て行きましょう。まず、toJSONの実装は次のようになっています。

toJSON: function(options) {
  return _.clone(this.attributes);
},

このようにattributes_.cloneしてるだけです。_.cloneしてるということは参照ではなくてオブジェクトのコピーが返りそうな雰囲気です。コピーが返るということは返ってきた値を変更しても元のオブジェクトには影響しないはず・・。

なんですが、実は_.closeはネストしたオブジェクトに対応しておらず、ネストしている場合はそのまま参照がコピーされるのです。なんてこったい\(^o^)/

なのでhogeの値は変更しても元の値は影響を受けておらず、foo.barの値を変更したら元のオブジェクトにも影響がでてしまったというわけ。

ちなみに_.closeの実装は次のようになってて

_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

_.extend({}, obj)を返してるだけなので元凶は_.extendだったりします。_.extendは次のようにネストしたオブジェクトに対応してないのがわかります。

var a = {
  foo: { bar: 'baz' }
};

var b = {
  foo: { hoge: 'fuga' }
};

_.extend(a, b); // { foo: { hoge: 'fuga' } }

この問題を解決できるのは我らがjQuery大先生です。jQueryの$.extendは第一引数をtrueにすることでネストしたオブジェクトにも対応できます。

var a = {
  foo: { bar: 'baz' }
};

var b = {
  foo: { hoge: 'fuga' }
};

$.extend(true, a, b); // { foo: { bar: 'baz', hoge: 'fuga' } }

すばらしいですね。これを利用して次のようにtoJSONを上書きします。

var MyModel = Backbone.Model.extend({
  toJSON: function(options) {
    return $.extend(true, {}, this.attributes);
  }
});

これで大丈夫なはず。

var m = new MyModel();
m.set('hoge', 'fuga');
m.set('foo', { bar: 'baz' });

var attrs = m.toJSON();
attrs.hoge = 'new fuga';
attrs.foo.bar = 'new baz';

console.log(m.attributes);
// => { hoge: 'fuga', foo: { bar: 'baz' } }

おっけーですねー。すばらしいですねー。jQueryまじイノベーティブです。

以上、jQueryBackbone.js Advent Calendarでした。

このエントリーをはてなブックマークに追加