DeeDub
DeeDub

Reputation: 1662

ASP.NET MVC 3 Single-Page Application using Javascript/jQuery AJAX: Memory and Detached DOM Issues

I have recently built an single-page ASP.NET MVC 3 with a JS/jQuery UI (on top of the view's HTML), the general idea of the Javascript work is below. I am encountering issues with GC not properly freeing memory and leaving a large number of elements (24,000 for the biggest form, 15-20k, and 1k depending on which form is loaded/unloaded) in Detached DOM (viewable in Chrome's Developer tools Heap Profiler).

 var MainApp = function () {
    var meBase = this;
    this.activeObject = undefined;

    this.someFunc = function (val1, val2, etc) {
        //Some operation here
    }
    this.GetView(x, y, z)
    {
        if (meBase.activeObject != null) {
            meBase.BustActive(x, y, z);
        } else {
            if (condition) {
                //Load static html via $.get
            } else {
                switch (activeObjectSelector) {
                    case CASEHERE:
                        self.activeObject = new SomeObject();
                        self.activeObject.BeginInit();
                        break;
                    case .....
                }
            }
        }
    }
    this.BustActive = function (x, y, z) {
        if (meBase.activeObject.Destroy()) {
            meBase.activeObject = null;
            meBase.GetView(x, y, z);
        }
    }
}
var SomeObject = function () {
    var meBase = this;
    this.Bindings = [];
    this.Container = "#somecontainer";
    //Some Properties

    this.Unbind = function () {
        $("#Somecontainer .bound").each(function () {
            if ($(this)["click"] && $.isFunction($(this)["click"])) {
                $(this).unbind('click');
            }
            if ($(this)["blur"] && $.isFunction($(this)["blur"])) {
                $(this).unbind('blur');
            } if ($(this)["change"] && $.isFunction($(this)["change"])) {
                $(this).unbind('change');
            }
            if ($(this)["mouseenter"] && $.isFunction($(this)["mouseenter"])) {
                $(this).unbind('mouseenter');
            } if ($(this)["mouseleave"] && $.isFunction($(this)["mouseleave"])) {
                $(this).unbind('mouseleave');
            }
        });

        //iterate through meBase.Bindings to remove any 'special' bindings such as 'live/die'
    }
    this.MapEvents = function () {
        //For Example

        $("#Somecontainer #element").click(meBase.SomeAction).addClass('bound');

        // create object with removal function for 'special' bindings such as 'live/die'
        // and push it into meBase.Bindings;
    }
    this.InitUI = function () {
        //Setup tabs, datepickers, etc
    }
    this.Destroy = function () {
        meBase.Unbind();

        //remove object fields and methods
        delete meBase.someProp;

        $(meBase.Container).empty();
        delete meBase.BeginInit;
        delete meBase.InitUI;
        delete meBase.MapEvents;
        delete meBase.SomeAction;
        delete meBase;
        return true;
    }
    this.SomeAction = function () {
        //Do something productive..hopefully
    }
    this.ProcessView = function (data) {
        $("#MainContainer").fadeOut(150, "swing", function () {
            $(this).empty().append(data);
        });
    }
    this.LoadView = function () {
        $.ajax({
            url: '/somewhere/something',
            type: 'GET',
            success: meBase.ProccessView, error: SomeGlobalObject.LogAjaxError
        });
    }
    this.BeginInit = function () {
        //Load pages via ajax
        meBase.LoadView();
        meBase.InitUI();
        meBase.MapEvents();
        return true;
    }

}

I have tried doing iterations with javascript to remove events and elements in .Destroy() function, and it substantially reduced the number of elements left in Detached DOM versus $(container).empty() or $(container).remove(). But my memory is never properly collecting back down, it just continually rises during each load/unload. There are drops at random intervals, but not the amount I would expect. Is it normal for so many elements to remain hung-up, or is there some fundamental issue with the way my code is functioning?

Thanks for taking the time to read this!

First post, please be gentle...

Upvotes: 4

Views: 3245

Answers (1)

Chris Jaynes
Chris Jaynes

Reputation: 2907

I've also recently been building some single-page apps in .Net MVC3. I suspect your problems are arising because Microsoft, in their attempts to keep developers out of JS and in C#, mucks around with the Javascript and Jquery on your page pretty badly.

The best advice I can give you is that you need to ditch all of Microsoft's cruft, and build the html/js part of your app as though it were totally platform independent. This means that you'll mostly be using the M in the MVC, and you'll only need enough Cs to manage your Ms. If the View is all HTML and Javascript, life gets a lot simpler indeed. Here's how to get started:

  • Delete all pre-packaged server-side code, including the Razor or ASPX pages.
  • Switch to static HTML files, static JS files
  • (Optional) Use Require.js to manage your JS dependencies (read the docs carefully, it seems weird at first, but it's incredibly powerful)
  • (Optional) Use Spine.js to give your JS code some structure
  • (Optional) Use Handlebars.js for your client-side templating engine

Require and Spine have quickly become my favorite tools for building single-page apps. They give you some very powerful and flexible tools to help you manage the increased volume of Javascript code you'll be writing in any single-page app.

Once you've got your client-side code completely disconnected from Microsoft's attempts to ruin Javascript, then you can focus on your data, which should use JSON-based Rest Services in MVC3. You can get help with this here and here.

Good luck!

Upvotes: 1

Related Questions