バックグラウンド
ブラウザーが「子」を使用して、コンソール/デバッガーにバックボーン オブジェクトのタイプを表示する理由を見ると興味深いです。
すべての JavaScript オブジェクトには、オブジェクトの作成に使用される関数への参照であるコンストラクター プロパティがあります。コンストラクターは、コンソール/デバッガーにオブジェクトの「タイプ」を表示するためにブラウザーによって使用されます。空でない場合は、コンストラクター関数の name プロパティの値が使用されます。ただし、名前付き関数式を使用して定義された関数のみが、有用な名前プロパティを取得します。
function A() { }
console.log(A.name); // 'A'
無名関数には空の name プロパティがあります。
var B = function() { };
console.log(B.name); // ''
では、無名関数はどうなるでしょうか。Chrome は、関数が最初に割り当てられた変数またはプロパティの名前から無名関数の名前を推測します。ここではいくつかの例を示します。
// 1. named function expression - objects will show as “a” in the console
function a() { … }
// 2. anonymous function assigned to variable - objects will show as “b” in the console
var b = function(){ … };
// 3. anonymous function assigned to property of object - objects will show as “container.c” in the debugger
var container = {
c: function() { … }
};
より詳細なスクリプトは、http: //jsfiddle.net/danmalcolm/Xa7ma/6/で入手できます。
ブラウザーはソース コードからこの名前を取得しているように見えます。関数が割り当てられた最初の変数の名前を実行時に通知できる JavaScript 機能はありません。他のブラウザーは、匿名コンストラクター関数で定義された displayName プロパティが使用される規則をサポートしていますが、これは現在 Chrome では発生しません: http://code.google.com/p/chromium/issues/detail?id=17356。
Backbone に戻ると、カスタム コンストラクター (以下を参照) を使用していないと仮定すると、モデル、ビュー、コレクション、およびルートで次のように使用されるBackbone の extends 関数で作成された匿名コンストラクター関数で型が終了します。
child = function(){ return parent.apply(this, arguments); };
これが、コンソール/デバッガーでバックボーン オブジェクトの横に「子」が表示される理由です。これは、オブジェクトのコンストラクターに適した名前をブラウザーが推測する最善の方法です。
ソリューション
オブジェクトに適切な型名を付けるには、バックボーン型を定義するときに、最初の「protoProps」引数を介して名前付きコンストラクターを指定できます。次のように、「親」コンストラクターへの呼び出しをラップするコンストラクター プロパティを追加するだけです。
var Product = Backbone.Model.extend({
constructor: function Product() {
Backbone.Model.prototype.constructor.apply(this, arguments);
}
});
Product モデルのインスタンスがデバッガーで見栄えがよくなります。
定義するすべてのビュー、モデル、コレクション、およびルートに対してこれを行うのは少し面倒です。Backbone の拡張機能にモンキー パッチを適用して、作業を行うことができます。
まず、型の名前を定義するための規則を確立する必要があります。ここでは__name__
、次のように指定するプロパティを使用しています。
var Product = Backbone.Model.extend({
__name__: 'Product'
// other props
});
次に、モデル、ビュー、コレクション、およびルートで使用される拡張関数を置き換えて、このプロパティを読み取り、名前付きコンストラクターを型に追加します。backbone.js 自体を変更する必要はありません。backbone.js の後にロードされる別のスクリプトに次を含めるだけです。
(function () {
function createNamedConstructor(name, constructor) {
var fn = new Function('constructor', 'return function ' + name + '()\n'
+ '{\n'
+ ' // wrapper function created dynamically for "' + name + '" constructor to allow instances to be identified in the debugger\n'
+ ' constructor.apply(this, arguments);\n'
+ '};');
return fn(constructor);
}
var originalExtend = Backbone.View.extend; // Model, Collection, Router and View shared the same extend function
var nameProp = '__name__';
var newExtend = function (protoProps, classProps) {
if (protoProps && protoProps.hasOwnProperty(nameProp)) {
// TODO - check that name is a valid identifier
var name = protoProps[nameProp];
// wrap constructor from protoProps if supplied or 'this' (the function we are extending)
var constructor = protoProps.hasOwnProperty('constructor') ? protoProps.constructor : this;
protoProps = _.extend(protoProps, {
constructor: createNamedConstructor(name, constructor)
});
}
return originalExtend.call(this, protoProps, classProps);
};
Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = newExtend;
})();