Garbage collection after unloading the module from the parent application

    Firstly, everything written below refers to the client side of web applications created in JavaScript.
    Secondly, this applies to web applications that use a modular architecture to expand their functionality (loading / unloading modules).

    Parent application - an application in which modules are launched, the launched module knows about the parent application.
    Module - a set of scripts and resources that implement certain functionality, designed to expand the ability of a web application. The module code can call the internal functions of the web application and, as a result, change the internal state of this application.

    Task:When unloading a module from a web application, roll back the changes made by the module by calling the functions of the web application inside its code.

    Example: A cartographic application loads a module showing the user layer using the methods of this application, after unloading the module, the user layer must be correctly removed. For this, the map application has an internal method for correctly deleting the user layer and after unloading the module it must be called.

    The task is to let the module know what it was created using the methods of the web application and know how to remove it using cleaning methods from the same web application. Of course, it is ideal for each developer of modules for all created objects to call the necessary cleaning functions, but it is better to automate this while increasing the security of the entire system.

    In the process of coming up with how to do this, 4 solutions were generated. Two of which used the caller property from the Function object, but after studying this www.opera.com/docs/specs/opera9/js/ecma these solutions could be safely sent to the scrap, unfortunately ... Although there is still a debatable question Of course, the fact that there is no this property in Opera solves certain security issues of JavaScript code, I raised this issue in a postModification of the platform core code by loaded applets (modules) and a hack of my approach was proposed precisely through the caller habrahabr.ru/blogs/javascript/28660/#comment_756953 property . In Opera, of course, such a hack will not work, but the lack of this property does not make it possible to implement some beautiful solutions, in particular, what will be discussed in this topic. And of course, putting the safety of JavaScript execution in the first place, the caller property can be completely removed over time (although some unaware individuals do not understand what, how and why is happening in the world of technology, but simply minus it without trying to get to the bottom of it ). Therefore, I had to look for another solution.

    The essence of the problem is further superficially presented, focusing on the essence of the problem.

    Example module class:

    function Module (fCode) {
       if (fCode) this.CreateInstance (fCode);
       return this;
    }

    Module.prototype = new Object;

    Module.prototype.oInstance = null;

    Module.prototype.CreateInstance = function (fCode) {this.oInstance = fCode; }
    Module.prototype.GetInstance = function () {return this.oInstance; }
    Module.prototype.Run = function () {

       if (this.oInstance) this.oInstance.apply (this.oInstance, arguments);
    }

    Module.prototype.Unload = function () {/ * There will be a cleaning code * /}

    The Module constructor accepts executable code, in this case, as a ready-made function, but you can make the code come in the form of text.
    The Run method runs the code and passes the necessary parameters to it.
    The Unload method deals with the correct deletion of objects created in the module.

    Based on the Module class, create our parent web application:

    function BaseModule () {}
    BaseModule.prototype = new Module (function (oParentInstance) {

       function MyClass () {this.aObjects = [];}
       MyClass.prototype.aObjects = null;

       MyClass.prototype.CreateObject = function (sParam) {
          var oObject = new Object;
          oObject.sParam = sParam;
          this.aObjects.push (oObject);
       }

       MyClass.prototype.RemoveObject = function (oObject) {
          alert ("Release object:„ + oObject.sParam);

          for (var i = 0; i          if (this.aObjects [i] == oObject) {
                this.aObjects.splice (i, 1);
                break;
             }
          }
          delete oObject;
       }

       var oMyClass = new MyClass;

       this.oMyClass = oMyClass;

    });

    The application contains the MyClass class, which provides two methods for correctly creating and deleting objects (just abstract objects are shown in the example). After starting the application creates an instance of the class and makes it public: var oMyClass = new MyClass; this.oMyClass = oMyClass;
    Other modules can now get the oMyClass property and call the CreateObject and RemoveObject methods.

    We describe the loadable module:

    function ExternModule1 () {}
    ExternModule1.prototype = new Module (function (oParentInstance) {
       var oMyClass = oParentInstance.oMyClass;
       var oObject = oMyClass.CreateObject (“Hello world!”);
    });

    After loading and starting execution, the module accesses the namespace of the parent application, receives the oMyClass property, and calls the CreateObject method.

    And in general:

    // Run our application.
    var oBaseModule = new BaseModule;
    oBaseModule.Run ({});

    // Run the module by passing the Function object to the application code (we logically connect the parent application and the launched module).
    var oExternModule1 = new ExternModule1 ();
    oExternModule1.Run (oBaseModule.GetInstance ());

    // Unload the module.
    oExternModule1.Unload ();


    In the context of the task at hand, it is necessary that after unloading the module, objects created in the module through the methods of the parent application are automatically deleted. In the example above, an object is created via the CreateObject method, and after unloading, if the developer of the module himself did not delete it using the RemoveObject method, the module itself must do it.

    It was decided for those objects that require automatic deletion by calling a special deletion method, to introduce the registration function of the object, and registration for the context in which this object was created. In the example about the object, oObject should know the Module in which this object was created. After unloading, the module itself goes through the list of those objects that were created in it and calls the Release method (the method already indicates how to correctly delete itself).

    We will expand the native Object by indicating methods for registering and deleting objects:

    function GBind (oContext, fFunctor) {
       return function () {
            return fFunctor.apply (oContext, arguments);
       }
    }

    Object.prototype.aHeap = null;
    Object.prototype.Release = function () {}

    Object.prototype.RegisterObject = function (oContext, oObject, fRelease) {
       oObject.Release = GBind (oContext, fRelease);
       if (! this.aHeap) this.aHeap = [];
       this.aHeap.push (oObject);
    }

    Object.prototype.ReleaseObjects = function () {
       if (! This.aHeap) return;
       for (var i = 0; i       if (this.aHeap [i] .Release)
             this.aHeap [i] .Release (this.aHeap [i]);
    }


    We finalize the CreateObject method, taking into account the registration of the created object:

       MyClass.prototype.CreateObject = function (sParam) {
          var oObject = new Object;
          oObject.sParam = sParam;
          this.aObjects.push (oObject);

          this.RegisterObject (this, oObject, function (oObject) {this.RemoveObject (oObject);});

       }

    where in the RegisterObject method it is indicated where to delete (the context in which the delete method will be called), what to delete (the object to be deleted) and how to delete (the method to delete).

    There is one more important thing to do, RegisterObject is still called in the context of the parent web application, and for the module to be able to register the object in its context, it is necessary to call RegisterObject in the context of the module. Before that I had a solution based on caller. To determine the call context, there was a function that recursively ran through the caller property to the call point (the module from where the call was made), received the module context, and RegisterObject was called through it. Without the caller property, this problem can be solved differently, but not so beautifully. Since the module knows which application is parent for it, it can replace the context of calling the RegisterObject method with its context (the task is solved through closure). Substitution is done before the module code is run. Immediately add the removal of registered objects to the Unload method.

    function Module (fCode) {
       if (fCode) this.CreateInstance (fCode);
       return this;
    }

    Module.prototype = new Object;

    Module.prototype.oInstance = null;

    Module.prototype.CreateInstance = function (fCode) {this.oInstance = fCode; }
    Module.prototype.GetInstance = function () {return this.oInstance; }
    Module.prototype.Run = function () {

       var oParentInstance = arguments [0] .oMyClass;
       var oCloneInstance = {};
       for (vProp in oParentInstance) oCloneInstance [vProp] = oParentInstance [vProp];
       oCloneInstance.RegisterObject = GBind (this.oInstance, Object.prototype.RegisterObject);
       arguments [0] .oMyClass = oCloneInstance;

       if (this.oInstance) this.oInstance.apply (this.oInstance, arguments);
    }

    Module.prototype.Unload = function () {this.oInstance.ReleaseObjects (); }


    An example for studying with two modules: www.okarta.ru/hyper/modulerelease.html

    Based on articles: Dynamic loading of modules in JavaScript , Task schedules in JavaScript , Modification of the kernel code of the platform with loadable modules and this article, the following practical example is made: www. okarta.ru/hyper/base.html

    PS Again, the idea of ​​implementation is given, there are various nuances, but if you give an implementation based on them, the article (as well as the code) will grow very much, and it will be very difficult to understand the essence.

    Also popular now: