Backbone.Modelのattributesにオブジェクト入れるときの注意
Backbone.js Advent Calendarの15日目です。軽めにいきます。
Backbone.Modelのattributes
にオブジェクトを設定するときの注意点など。attributes
はset
とかで設定される値をオブジェクトして持っているやつです。
まず次のようにset
でattributes
を設定します。
var MyModel = Backbone.Model.extend();
var m = new MyModel();
m.set('hoge', 'fuga');
m.set('foo', { bar: 'baz' });
このようにhoge
には文字列、foo
にはオブジェクトを設定しました。そしてtoJSON
でattributes
を取得して値を更新してみます。
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でした。
- Prev Entry:Backbone.jsでNode.jsとクライアントサイドのロジックをイケてる感じで共通化する
- Next Entry:Backbone.js 0.9.9 の変更点