Quickstart: Use the dispose model in WinJS

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

Learn how to use the dispose model in WinJS to reliably control the memory usage of controls and other objects in a Windows Store app using JavaScript. The dispose model allows you to create controls that release their memory once they've gone out of scope. The object becomes marked for disposal, which means that the Garbage Collector (a process that runs in the background to manage memory usage) destroys the object when it runs.

Note  You can use the Windows Performance Recorder and Windows Performance Analyzer to observe how your app uses memory. For more info about the Windows Performance Recorder and Windows Performance Analyzer, see Windows Performance Toolkit Technical Reference.

 

Prerequisites

Instructions

Create a new project by using the Blank App template

This article uses a basic Blank App template in Microsoft Visual Studio 2013. To create a new Blank App, follow these steps:

  1. Start Microsoft Visual Studio.

  2. From the Start Page tab, click New Project. The New Project dialog opens.

  3. In the Installed pane, expand Templates, JavaScript, and then Store Apps. Select the type of app that you want to create (Windows, Windows Phone, Universal). The available project templates for JavaScript are displayed in the center pane of the dialog.

  4. In the center pane, pick the Blank App project template.

  5. In the Name text box, type Dispose demo.

  6. Click OK to create the project.

Add a custom control to the project

  1. In Solution Explorer, right-click the js folder and click Add > New JavaScript File. In the Add New Item dialog box, name the new file RandomShapesControl.js and click Add.

  2. Open default.html and insert the following <script> tag into the <head> tag.

    <script src="/js/RandomShapesControl.js"></script>
    
  3. Open RandomShapesControl.js (in the js folder) and add the following code.

    (function () {
        "use strict";
    
        var svgNS = "http://www.w3.org/2000/svg";
    
        // Define a utility function for building HTML elements.
        function buildElement(type, attributes, text, namespace) {
            var newElement = namespace ?
                document.createElementNS(namespace, type) : document.createElement(type);
            for (var i in attributes) {
                if (attributes.hasOwnProperty(i)) {
                    newElement.setAttribute(i, attributes[i]);
                }
            }
            newElement.innerText = text || "";
            return newElement;
        }
    
        // Draw a randomly-colored rectangle onto the 'svg' container
        // element.
        function drawShape() {
            var newShape = buildElement("rect", {
                width: "30",
                height: "30",
                x: (Math.floor(Math.random() * 470) + 1).toString(),
                y: (Math.floor(Math.random() * 470) + 1).toString(),
                fill: getRandomColor()
            }, null, svgNS);
    
            document.getElementById('svg').appendChild(newShape);
        }
    
        // Generate a random RGB value as a string.
        function getRandomColor() {
            var color = function () { return Math.floor(Math.random() * 256) + 1; }
            var colorString = "rgb(" + color().toString() + "," + color().toString() 
                + "," + color().toString() + ")";
    
            return colorString;
        }
    
        WinJS.Namespace.define("Shapes", {
            RandomShape: WinJS.Class.define(
    
                // Define the constructor function / class definition for the
                // custom Random Shapes control. The Random Shapes control draws
                // a lot of randomly-colored SVG rectangles when a button is clicked. 
                function (element) {
    
                    // Implement a standard WinJS control 'interface' so that
                    // the winControl property returns this constructor function.
                    this._element = element;
                    this._element.winControl = this;
    
                    // Create an SVG container element for the rectangles.
                    this.svg = buildElement("svg", {
                        id: "svg",
                        width: "500",
                        height: "500"
                    }, null, svgNS);
    
                    // Create a button to raise the click event for the control.
                    this.drawMany = buildElement("button", {
                        id: "drawMany"
                    }, "Draw a bunch of shapes");
    
                    element.appendChild(this.svg);
                    element.appendChild(document.createElement("br"));
                    element.appendChild(this.drawMany);
    
                    // Add the click event handler to call drawManyShapes, which
                    // adds a lot of SVG elements to the SVG container.
                    this.drawMany.addEventListener("click", this.drawManyShapes);
    
                }, {
    
                    // Insert 1,000 randomly-colored shapes into the 'svg' container.
                    drawManyShapes: function () {
                        for (var i = 0; i < 1000; i++) {
                            drawShape();
                        }
                    }
                })
        });
    })();
    

Add the HTML and JavaScript for the sample app

This demo app creates a custom control that generates a large number (1,000) of SVG elements when the user clicks a button. Each element is uniquely colored and placed within the larger SVG element, which requires a lot of memory.

Open default.html and insert the following HTML into the <body> element. The code should be a direct child of the <body> element. This HTML creates two <div> elements: one element contains the custom control and the other contains a button that disposes the custom control.

<div>
    <h1>Custom random-shape generator control</h1><br/><br/>
    <p>Use the button below to dispose the Random Shapes control.</p>
    <button id="dispose">Dispose control</button>
</div><br/><br/>
<div id="randomShapes" data-win-control="Shapes.RandomShape"></div>

Open the default.js file (in the js folder) and replace the app.onactivated event handler with the following code. This JavaScript creates a new RandomShapes control and adds an event handler to the Dispose control button.

app.onactivated = function (args) {
    if (args.detail.kind === activation.ActivationKind.launch) {
        if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
            // TODO: This appl has been newly launched. Initialize
            // your app here.
        } else {
            // TODO: This app has been reactivated from suspension.
            // Restore app state here.
        }

        args.setPromise(WinJS.UI.processAll().done(
            function () {

                // Add an event handler to the 'Dispose control' button.
                dispose.addEventListener("click", function () {
                    var shapesContainer = document.getElementById("randomShapes");
                    var shapesControl = shapesContainer.winControl;

                    shapesContainer.innerHTML = "";
                    shapesContainer = null;
                });
            }
       ));
    }
};

If you examine the memory usage for the app using the Performance and Diagnostics tool in Visual Studio 2013, you'll see that the app retains some memory of the control even though all of the HTML is supposedly destroyed when the innerHTML property of the container object is set to an empty string. Memory is still allocated to both elements.

Implementing the dispose pattern

When you create a custom control, ensure that it doesn't cause memory leaks in your app. In WinJS, custom controls can use the dispose pattern to release memory when the control is no longer needed by the app.

There are two basic requirements to implement the dispose pattern in a custom control in WinJS:

  • The container for the control must be marked with the Cascading Style Sheets (CSS) class "win-disposable". The HTML element that holds the custom control must have a class attribute that contains the value "win-disposable".
  • The class definition of the control must include a dispose member. When declaring a new class using the WinJS.Class.define function, the instance members of the class must include a dispose function.

To improve the memory usage of the RandomShapes control in this sample app, use the following steps to change the code that implements the dispose pattern.

  1. Open RandomShapesControl.js (in the js folder). In the constructor function for the RandomShapes control, add this line of code.

    WinJS.Utilities.addClass(element, "win-disposable");
    
  2. Also in RandomShapesControl.js, replace the call to WinJS.Namespace.define with this code.

    WinJS.Namespace.define("Shapes", {
        RandomShape: WinJS.Class.define(Control, {
            drawManyShapes: drawManyShapes,
            dispose: function () {
    
                // Remove the click-event handler from the button.
                this.drawMany.removeEventListener("click", this.drawManyShapes);
    
                // Dispose of all the child controls of this element.
                WinJS.Utilities.disposeSubTree(this._element);
    
                // Release memory from all of the class fields.
                this.svg = null;
                this.drawMany = null;
                this._element = null;
            }
        })
    });
    
  3. In default.js (in the js folder), replace the call to args.setPromise with the following code. This code makes an explicit call to RandomShape.dispose to dispose the control.

    args.setPromise(WinJS.UI.processAll().done(
        function () {
            dispose.addEventListener("click", function () {
                var shapesContainer = document.getElementById("randomShapes");
                var shapesControl = shapesContainer.winControl;
    
                // Call dispose on the control.
                shapesControl.dispose();
    
                shapesContainer.innerHTML = "";
                shapesContainer = null;
            })
        }
    ));
    

Here's the complete, updated code for RandomShapesControl.js.

   (function () {
    "use strict";

    var svgNS = "http://www.w3.org/2000/svg";

    // Define an internal utility function for building HTML elements.
    function buildElement(type, attributes, text, namespace) {
        var newElement = namespace ?
            document.createElementNS(namespace, type) : document.createElement(type);
        for (var i in attributes) {
            if (attributes.hasOwnProperty(i)) {
                newElement.setAttribute(i, attributes[i]);
            }
        }
        newElement.innerText = text || "";
        return newElement;
    }

    // Draw a randomly colored rectangle onto the 'svg' container
    // element.
    function drawShape() {
        var newShape = buildElement("rect", {
            width: "30",
            height: "30",
            x: (Math.floor(Math.random() * 470) + 1).toString(),
            y: (Math.floor(Math.random() * 470) + 1).toString(),
            fill: getRandomColor()
        }, null, svgNS);

        document.getElementById('svg').appendChild(newShape);
    }

    // Generate a random RGB value as a string.
    function getRandomColor() {
        var color = function () { return Math.floor(Math.random() * 256) + 1; }
        var colorString = "rgb(" + color().toString() + "," + color().toString() 
            + "," + color().toString() + ")";

        return colorString;
    }

    WinJS.Namespace.define("Shapes", {
        RandomShape: WinJS.Class.define(

            // Define the constructor function / class definition for the
            // custom Random Shapes control. The Random Shapes control draws
            // a lot of randomly colored SVG rectangles when a button is clicked. 
            function (element) {

                // Implement a standard WinJS control 'interface' so that
                // the winControl property returns this constructor function.
                this._element = element;
                this._element.winControl = this;

                WinJS.Utilities.addClass(element, "win-disposable");

                // Create an SVG container element for the rectangles.
                this.svg = buildElement("svg", {
                    id: "svg",
                    width: "500",
                    height: "500"
                }, null, svgNS);

                // Create a button to raise the click event for the control.
                this.drawMany = buildElement("button", {
                    id: "drawMany"
                }, "Draw a bunch of shapes");

                element.appendChild(this.svg);
                element.appendChild(document.createElement("br"));
                element.appendChild(this.drawMany);

                // Add the click-event handler to call drawManyShapes, which
                // adds a lot of SVG elements to the SVG container.
                this.drawMany.addEventListener("click", this.drawManyShapes);

            }, {

                // Insert 1,000 randomly colored shapes into the 'svg' container.
                drawManyShapes: function () {
                    for (var i = 0; i < 1000; i++) {
                        drawShape();
                    }
                },

                // Release all references to internal objects.
                dispose: function () {

                    // Remove the click-event handler from the button.
                    this.drawMany.removeEventListener("click", this.drawManyShapes);

                    // Dispose of all the child controls of this element.
                    WinJS.Utilities.disposeSubTree(this._element);

                    // Release memory from all of the class fields.
                    this.svg = null;
                    this.drawMany = null;
                    this._element = null;
                }
            })
    });
})();

If you run the sample after implementing the dispose pattern, the UI behaves exactly as it did before: when you click Draw a bunch of shapes, a thousand differently colored rectangles are added to the SVG element. And when you click Dispose control, the control disappears.

Summary

In this quickstart, you learned how to implement the dispose pattern in a custom control in a Windows Runtime app using JavaScript. This quickstart did not cover issues like how to dispose of child elements that are themselves WinJS controls or how to use WinJS.Utilities.markDisposable.