"JScript - script block" and memory leaks - How to freeing up resources properly?
Asked Answered
S

3

8

I put some jquery tabs inside a partial view of my project. I noticed by Visual Studio's "Solution Explorer", that during debug a new dynamic JScript - script block is generated every time I click on a new tab.

This happens even if I put $('#mytabs .ui-tabs-hide').children().remove(); and $(".ui-tabs-hide").empty(); inside show event of the tabs. Script blocks contains javascript i put inside the partial views called by tabs, so every time I click a previously clicked tab, a new JScript block appears: it is evident that this leads to problems of stability or memory leaks...for example, I already noticed that some timers and bindings do not work properly after I load twice a tab.

I do not know if the problem is induced by the way in which call the partial views containing the scripts. Please be careful how I set the controller actions (Index in the example).

This is my environment: jquery 1.6.4 - jquery-ui 1.8.16 - IE 8.0.7601 I cannot succeed to debug with other browsers, because Visual Studio does not seems to attach their processes and does not show dynamic data...

CONTROLLER

Here is an action example called by the tabs

  public ActionResult Index()
    {
         if (Request.IsAjaxRequest())
            return PartialView("_Index");

        return View(); 
    }

Here are some parts of my views and scripts:

_Layout.cshtml

 ....
 <div id="body">  
    @Html.Partial("_TabsMenu");
 </div>
 ....

_TabsMenu.cshtml (Partial view containing the tabs menu)

 <div id="menutabs" class="content-wrapper">
    <ul >
        <li>@Html.ActionLink("Home", "Index", "Home")</li>
        <li>@Html.ActionLink("Test", "Index", "Test")</li>
         ...
    </ul>
 </div>

 <script type="text/javascript">
 $(function () {
     $('#menutabs').tabs({
         cache: false,
         show: function (event, ui) {
             $('#menutabs .ui-tabs-hide').children().remove(); // the content is removed , but the script is still in memory
             $(".ui-tabs-hide").empty(); // the content is removed, but the script is still in memory
         },
         select: function (event, ui) {
             $(window).unbind(); 
         }
     });
 });

(I even tried to put script inside div id, pheraps is silly, but I wanted to see if the script inside the DOM was removed...but nothing)

Index.cshtml

  @{Html.RenderPartial("_Index");}

_Index.cshtml (partial view containing the repeated jscript object of the question)

   <table id="list4"></table>
   <jQuery("#list4").jqGrid({
datatype: "local",
height: 250,
colNames:['Inv No','Date', 'Client', 'Amount','Tax','Total','Notes'],
colModel:[
    {name:'id',index:'id', width:60, sorttype:"int"},
    {name:'invdate',index:'invdate', width:90, sorttype:"date"},
    {name:'name',index:'name', width:100},
    {name:'amount',index:'amount', width:80, align:"right",sorttype:"float"},
    {name:'tax',index:'tax', width:80, align:"right",sorttype:"float"},     
    {name:'total',index:'total', width:80,align:"right",sorttype:"float"},      
    {name:'note',index:'note', width:150, sortable:false}       
],
multiselect: true,
caption: "Manipulating Array Data"});
            var mydata = [
    {id:"1",invdate:"2007-10-01",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
    {id:"2",invdate:"2007-10-02",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},
    {id:"3",invdate:"2007-09-01",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"},
    {id:"4",invdate:"2007-10-04",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
    {id:"5",invdate:"2007-10-05",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},
    {id:"6",invdate:"2007-09-06",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"},
    {id:"7",invdate:"2007-10-04",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
    {id:"8",invdate:"2007-10-03",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},
    {id:"9",invdate:"2007-09-01",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"}
    ];
       for(var i=0;i<=mydata.length;i++)
    jQuery("#list4").jqGrid('addRowData',i+1,mydata[i]);

Updated

JScript - script block 1..N // this is what I see inside each JScript - script block, during debugging... I am testint jqgrid. This is a demo from Trirand's site.

     <jQuery("#list4").jqGrid({
datatype: "local",
height: 250,
colNames:['Inv No','Date', 'Client', 'Amount','Tax','Total','Notes'],
colModel:[
    {name:'id',index:'id', width:60, sorttype:"int"},
    {name:'invdate',index:'invdate', width:90, sorttype:"date"},
    {name:'name',index:'name', width:100},
    {name:'amount',index:'amount', width:80, align:"right",sorttype:"float"},
    {name:'tax',index:'tax', width:80, align:"right",sorttype:"float"},     
    {name:'total',index:'total', width:80,align:"right",sorttype:"float"},      
    {name:'note',index:'note', width:150, sortable:false}       
],
multiselect: true,
caption: "Manipulating Array Data"});
            var mydata = [
    {id:"1",invdate:"2007-10-01",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
    {id:"2",invdate:"2007-10-02",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},
    {id:"3",invdate:"2007-09-01",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"},
    {id:"4",invdate:"2007-10-04",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
    {id:"5",invdate:"2007-10-05",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},
    {id:"6",invdate:"2007-09-06",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"},
    {id:"7",invdate:"2007-10-04",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
    {id:"8",invdate:"2007-10-03",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},
    {id:"9",invdate:"2007-09-01",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"}
    ];
       for(var i=0;i<=mydata.length;i++)
    jQuery("#list4").jqGrid('addRowData',i+1,mydata[i]);                
Sunshinesunspot answered 20/2, 2012 at 16:53 Comment(1)
you can profile javascript in most modern browser consoles which would be a far better test than in an IDE. If your jQgrid test is straight from a demo, with no modifications...I suspect VS is giving you bad information. If it is modifed code, you have likely set up some sort of excessive recursionKore
D
8

Script that's parsed by a browser is not in the DOM, and you can't "remove" it - variables are still defined, events are still bound, methods are still there. If you put javascript into a partial view that you load repeatedly, you're going to get that javascript repeatedly.

What you need to do is author your javascript to be more resilient to this. If you're binding events to elements outside of the dynamic area - don't. You'll be binding them multiple times. Move that code somewhere it will only be loaded once. Try to keep the javascript in the dynamic area isolated so it only deals with elements that are also in the dynamic area.

You can also protect against multiple definitions with simple if checks, using jquery selectors that more scoped, etc.

Without the details of what's in that repeated block, there's not much I can offer.

Disengage answered 27/2, 2012 at 23:38 Comment(5)
THX @bhamlin! I put the involved javascript code inside the question. Could you give me a help to avoid this bad behaviour and isolate the problem, please? Every tab change loads a new script in memory. I would avoid this and I think that this could help other people too. Thank you so much!Sunshinesunspot
I'm guessing list1 is supposed to be ticketgrid and pager1 is supposed to be jqpager. That said there's nothing inherently wrong with loading the script block each time. If there is strange behavior, it might have something to do with how jqGrid is implemented, which is probably beyond the scope of this question. If you just want to have less script loaded each time, you could put that block of code in Index.cshtml as a function and just have _Index.cshtml call it, passing in the list and pager elements to operate on.Disengage
You are right. It was a refuse. I corrected the names and put another example with static data, so everyone can test and reproduce it. I know there's nothing wrong loading the script each time, but the problem as I said it's that the script is never unloaded. Moreover, all the code is here, and everyone could test and reproduce the behaviour. The jqGrid is very simple as its implementation, so unfortunately this is not the point of the problem, I think.Sunshinesunspot
"the problem as I said it's that the script is never unloaded". Scripts can't be unloaded, there's no such thing.Disengage
So scripts can't be unloaded. It seems too much strange to me. How is it possible that no one implemented a method to free up memory from unused scripts? Does not exist a way to set an expiry, GC or something else? Could you put me an example to re-use the same script and avoid it's reload eache tab change, please? Thank you very much for your helpSunshinesunspot
S
3

It seems to me like your problem is that every time you load a tab, all the scripts in it get loaded too, and as far as I'm concerned, there's nothing you can do about it, remember that you can use server-side code to generate those scripts so ASP.NET has to act like they are all different (since they can actually be).

However, javascript IS garbage collected so I don't think those scripts you see are actually taking up user's memory when your not debugging, the debugger will always show everything that was loaded regardless of it having been garbage collected or not (although I didn't actually test it).

If your worried about memory, just make sure you're not declaring global functions and variables that'll never be garbage collected (specially in scopes that get reloaded), to do this, just surround them with a

(function(){

and a

})();

So that they're in an anonymous function that can be garbage collected right after it's executed.

Schizogenesis answered 4/3, 2012 at 3:40 Comment(4)
THX Eduardo. Your assumptions about GC calm me, no way to be certain, could you give me suggestions on how to test, please?Sunshinesunspot
I found this link on javascript memory leaks link, it uses windows resource manager to watch the memory.Schizogenesis
Unfortunatly the best I can think is putting some javascript with a big string in your tab, automate the switching of tabs/click until satisfied, check the resource manager.Schizogenesis
Just remembered another tip that may be useful in this case, the debugger; statement acts like a breakpoint in any browser, so you can put one in your code then debug in another browser with firebug or chrome developer tools.Schizogenesis
P
2

You just need to collect all scripts within the tabs, in the document ready event. The code below find all script children of an element, execute them and then destroy them, so they cannot be executed again:

function GlobalEvalScriptAndDestroy(element) {
var allScriptText = CollectScriptAndDestroy(element);
jQuery.globalEval(allScriptText);}



function CollectScriptAndDestroy(element) {
var allScriptText = "";
if (element.tagName == "SCRIPT") {
    allScriptText = element.text;
    $(element).remove();
}
else {
    var scripts = $(element).find("script");
    for (var i = 0; i < scripts.length; i++) {
        allScriptText += scripts[i].text;
    }
    scripts.remove();
}
return allScriptText;}
Poppo answered 3/3, 2012 at 23:42 Comment(3)
THX Francesco. I already collect scripts in the document ready event. I read the following answer too by Eduardo, who tells that probably the scripts are GC...thanks for the scripts too. I already do $('#menutabs .ui-tabs-hide').children().remove(); and $(".ui-tabs-hide").empty(); on each tabs show and it works for html at least. Do not you think it should be enough? Grazie ancora!Sunshinesunspot
Sorry Francesco, I would like to try your solution. Could you tell me where I must put the code and how to call it? I suppose I must call on show event of the tabs. THXSunshinesunspot
No no. Do it on the original div where you apply the tabs plugin before applying the plugin...that is in the document ready event. This way when the tab is created the script has been already removed...this way you are sure that their presence will not interfere with the pluginPoppo

© 2022 - 2024 — McMap. All rights reserved.