First, 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 extend its functionality (module loading / unloading).
Parent application - the application in which the 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 possibility of a web-application. Module code can call internal functions of a 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 that the module has made by calling functions of the web application inside its code.
Example: A mapping application loads a module showing the user layer with the methods of this application; after unloading the module, you must correctly remove the user layer. For this, the mapping application has an internal method for correctly deleting a user layer and after unloading the module it must be called.
')
The task is to ensure that the module knew what it was creating using web application methods and knew how to remove it using cleaning methods from the same web application. Of course, it is ideal that each module developer for all created objects himself calls the necessary cleaning functions, but it is better to automate this by increasing the security of the entire system.
In the process of inventing 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 scrapped, unfortunately ... Although there is still a controversial issue Of course, the fact that Opera does not have this property solves certain issues of JavaScript security, I raised this issue in a post
Modifying the platform’s kernel code with applets (modules) and suggested the hack of my approach through the caller property
habrahabr.ru/blogs/javascript/28660 / # comment_756953 . In the Opera, of course, such a hack will not work, but the absence of this property does not allow to implement some beautiful solutions, in particular, what will be discussed in this topic. And of course putting safety of JavaScript execution in the first place, the caller property may eventually be removed altogether (although some unconscious individuals do not understand what, how and why it happens in the world of technology, but simply
minus without trying to get to the bottom ). So I had to look for another solution.
Next, the essence of the problem is superficially presented, focusing on the essence of the problem.
Approximate 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 clean code * / }
The Module constructor accepts executable code, in this case in the form of a finished 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, we 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 < this .aObjects.length; 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 that provides two methods for correctly creating and deleting objects (in the example, only abstract objects are shown). Once launched, the application creates an instance of the class and makes it public: var oMyClass = new MyClass; this.oMyClass = oMyClass;
Now other modules can 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, gets 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 with the application code (logically linking the parent application and the launched module).
var oExternModule1 = new ExternModule1 ();
oExternModule1.Run (oBaseModule.GetInstance ());
// Upload the module.
oExternModule1.Unload ();
In the context of the problem being solved, it is necessary that after the module is unloaded, the objects created in the module through the methods of the parent application are automatically deleted. In the example above, an object is created through the CreateObject method, and after unloading, if the developer of the module itself did not delete it using the RemoveObject method, the module itself should do this for it.
It was decided for those objects that need automatic deletion with a call to a special deletion method, to introduce the function of registering an 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 delete itself correctly).
Let us extend the native Object object by specifying 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 < this .aHeap.length; i ++)
if ( this .aHeap [i] .Release)
this .aHeap [i] .Release ( this .aHeap [i]);
}
Let's refine 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 the RegisterObject method indicates where to delete (the context in which the deletion method will be called), what to delete (the object to be deleted) and how to delete (the method to be deleted).
There is another important thing to do: RegisterObject is still called in the context of the parent web application, and for the module to register the object in its context, it is necessary to call RegisterObject in the context of the module. Before that, I had a caller based solution. To determine the context of the call, there was a function that recursively ran through the caller property to the call point (the module where the call was made from), received the context of the module and already called RegisterObject through it. Without the caller property, this problem can be solved differently, but not so beautiful. Since the module knows which application is its parent, it can replace the context of calling the RegisterObject method with its own context (the problem is solved through the closure). The substitution is done before the launch of the module code. Immediately add to the Unload method delete registered objects. And the final class of the module:
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
On the basis of articles:
Dynamic loading of modules on JavaScript ,
Schedules of tasks on JavaScript ,
Modification of the kernel code of the platform with loadable modules and this article made the following practical application example:
www.okarta.ru/hyper/base.html
PS Again, the idea of implementation is given, there are various nuances, but if you give a realization with their account, then the article (as well as the code) will grow very much, and it will be very difficult to understand the essence.