Right now, while I am debugging backbone or marionette using the chrome dev tools, I end up setting break points and whatnot, but once the code pauses, its hard to tell what type of objects i'm working with because chrome labels everything a "child".
(I think because that's the constructor function)
Is there any easy way to either change this declaration or determine which type of model/collection i'm using.
The amount craziness this causes in me wants to start doing something like this:
MyModel = Backbone.Model.Extend({
// the $$$ puts it at the top of the inspector, the NAME is just for other devs
$$$NAME = "MyModel",
...
});
I don't really like it, because its... ugly, its a variable... and it only helps when I inspect and expand the variable... it would be great to change the name chrome uses to display it.
In any case, does anyone know how to change the name? or have some other cleaner convention you use?
Thanks!
Matt
Background
It is interesting to look at why the browser uses “child” to display the type of Backbone objects in the console / debugger.
All JavaScript objects have a constructor property, a reference to the function used to create the object. The constructor is used by the browser to display the object’s “type” in the console / debugger. The value of the constructor function’s name property will be used if it is not empty. However, only functions defined using named function expressions get a useful name property:
function A() { }
console.log(A.name); // 'A'
Anonymous functions have an empty name property:
var B = function() { };
console.log(B.name); // ''
So, what happens with anonymous functions? Chrome infers the name of anonymous functions from the name of the variable or property to which the function was first assigned. Here are some examples:
// 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() { … }
};
A more detailed script is available here: http://jsfiddle.net/danmalcolm/Xa7ma/6/
The browser appears to get this name from the source code - there isn’t a JavaScript feature that can tell you at runtime the name of the first variable that a function was assigned to. Other browsers support a convention where a displayName property defined on anonymous constructor functions is used, but this doesn’t currently happen in Chrome: http://code.google.com/p/chromium/issues/detail?id=17356.
Returning to Backbone, assuming you're not using a custom constructor (see below), your type will end up with an anonymous constructor function, created in Backbone's extend function used by Model, View, Collection and Route as follows:
child = function(){ return parent.apply(this, arguments); };
This is why you see “child” next to your Backbone objects in the console / debugger. It is the browser’s best guess at a suitable name for your object’s constructor.
Solutions
To give your objects a better type name, you can supply a named constructor via the first “protoProps” argument when you define your Backbone types. Just add a constructor property that wraps a call to the “parent” constructor as follows:
var Product = Backbone.Model.extend({
constructor: function Product() {
Backbone.Model.prototype.constructor.apply(this, arguments);
}
});
Your Product model instances will now look really nice in the debugger.
It is a bit cumbersome to do this for every View, Model, Collection and Route that you define. You can monkey patch Backbone’s extend function to do the work for you.
You first need to establish a convention for defining the names of your types. Here we're using a __name__
property, which you specify as follows:
var Product = Backbone.Model.extend({
__name__: 'Product'
// other props
});
You then replace the extend function used by Model, View, Collection and Route to read this property and add a named constructor to your type. You don’t need to modify backbone.js itself, just include the following in a separate script that is loaded after 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;
})();