背景
看看为什么浏览器使用“child”在控制台/调试器中显示 Backbone 对象的类型是很有趣的。
所有 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 的扩展函数中创建的,如下所示:
child = function(){ return parent.apply(this, arguments); };
这就是您在控制台/调试器中的 Backbone 对象旁边看到“子”的原因。这是浏览器对对象构造函数的合适名称的最佳猜测。
解决方案
为了给您的对象一个更好的类型名称,您可以在定义 Backbone 类型时通过第一个“protoProps”参数提供一个命名构造函数。只需添加一个包含对“父”构造函数的调用的构造函数属性,如下所示:
var Product = Backbone.Model.extend({
constructor: function Product() {
Backbone.Model.prototype.constructor.apply(this, arguments);
}
});
您的 Product 模型实例现在在调试器中看起来非常好。
对您定义的每个 View、Model、Collection 和 Route 执行此操作有点麻烦。您可以使用猴子补丁 Backbone 的扩展功能来为您完成这项工作。
您首先需要建立定义类型名称的约定。在这里,我们使用一个__name__
属性,您指定如下:
var Product = Backbone.Model.extend({
__name__: 'Product'
// other props
});
然后,您替换 Model、View、Collection 和 Route 使用的扩展函数来读取此属性并将命名构造函数添加到您的类型。您不需要修改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;
})();