Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugging Javascript (Backbone and Marionette)

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

like image 991
MattyP Avatar asked Feb 14 '13 00:02

MattyP


People also ask

How do you debug a marionette?

The most common way to debug Marionette, as well as chrome code in general, is to use dump() to print a string to stdout. In Firefox, this log output normally ends up in the gecko. log file in your current working directory. With Fennec it can be inspected using adb logcat .

How do I debug backbone JS in chrome?

You can try to use Chrome extension which is able to show you all views, models, collections and routers and its data. Show activity on this post. Backbone doesn't provide any inspection tools out of the box. Using the chrome dev tools debugger (adding break points) might be your best shot.

What is marionette JavaScript?

Marionette is a composite application library for Backbone. js that aims to simplify the construction of large scale JavaScript applications. It is a collection of common design and implementation patterns found in applications.

Can JavaScript be debugged?

But fortunately, all modern browsers have a built-in JavaScript debugger. Built-in debuggers can be turned on and off, forcing errors to be reported to the user. With a debugger, you can also set breakpoints (places where code execution can be stopped), and examine variables while the code is executing.


1 Answers

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;
})();
like image 158
Dan Malcolm Avatar answered Sep 20 '22 11:09

Dan Malcolm