“JScript - script block” and memory leaks - How to

2019-04-06 19:08发布

问题:

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]);                

回答1:

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.



回答2:

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.



回答3:

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;}