📜 ⬆️ ⬇️

My extend and class inheritance style

In this post I want to tell you how I prefer to implement inheritance in a bulk JavaScript application.

Suppose the project requires a lot of related and not very classes.
If we try to describe each type of behavior in a separate class, then there can be a lot of classes. And the final classes may have a dozen ancestors. In this case, the usual JavaScript inheritance through prototype may not be enough. For example, I needed the ability to call a similar method from an ancestor class from a method. And I wanted to create and inherit some static properties and methods of the class. Such functionality can be added by calling for each class the following set out f-iv extend:

Extend function


cSO = {}; //     . cSO.extend = function(child, parent, other) { if(parent) { var F = function() {}; F.prototype = parent.prototype; child.prototype = new F(); child.prototype.constructor = child; child.prototype.proto = function() { return parent.prototype; } //   . } else { child.prototype.proto = function() { return; } } /* *     stat,    -  . *    _class.stat   ()   this.stat. *       ,     *  : _class.stat.prototype.myStaticMethod = function() {...<anchor>habracut</anchor> *    ,    prototype. *    stat  ,     . *     stat -  _class.stat.deleteStatVal("name"); */ child.statConstructor = function() {}; if(parent && ("statConstructor" in parent) && parent.statConstructor && typeof (parent.statConstructor) === "function") { var S = function() {}; S.prototype = parent.statConstructor.prototype; child.statConstructor.prototype = new S(); child.statConstructor.prototype.constructor = child.statConstructor; child.statConstructor.prototype.proto = function() { return parent.statConstructor.prototype; } child.statConstructor.prototype.protoStat = function() { return parent.stat; } } else { child.statConstructor.prototype.proto = function() { return; } child.statConstructor.prototype.protoStat = function() { return; } } var oldChildStat = child.stat; //   stat -  ... child.stat = new child.statConstructor(); if(oldChildStat) { //       . for(var k in oldChildStat) { child.stat[k] = oldChildStat[k]; } } child.stat.prototype = child.statConstructor.prototype; if(oldChildStat && oldChildStat.prototype) { //         . for(var k in oldChildStat.prototype) { child.stat.prototype[k] = oldChildStat.prototype[k]; } } child.prototype.stat = child.stat; if(other) { //      . if(other.statConstruct) { child.stat.prototype.construct = other.statConstruct; } } child.stat._class = child; //       . child.stat.deleteStatVal = function(name) { //   ,      . if( name in child.stat) { try { delete child.stat[name]; } catch(e) { } if(parent) { child.stat[name] = child.stat.protoStat()[name]; } } } child.prototype.protoFunc = child.statConstructor.prototype.protoFunc = function(callerFuncName, args, applyFuncName) { /* *          (  *  - -     ).   *  applyFuncName -  callerFuncName    -   *     ,     -. */ if(!args) { args = []; } if(applyFuncName) { //    ,     . } else { applyFuncName = callerFuncName; } var tProto = this; var ok = false; do { if(ok && arguments.callee.caller !== tProto[applyFuncName]) { if(( applyFuncName in tProto) && ( typeof (tProto[applyFuncName]) === "function")) { return tProto[applyFuncName].apply(this, args); } } else if(arguments.callee.caller === tProto[callerFuncName]) { ok = true; } } while(("proto" in tProto) && (tProto = tProto.proto())) return; } if(child.stat.construct) { //         stat.construct child.stat.construct(); } } 

')

A small test of how it works, and what results does the f-I extend () function produce


Create 3 classes, each - the heir of the previous one.

 cSO.class001 = function() { } cSO.extend(cSO.class001, 0, {"statConstruct":function(sc1) { console.log("statConstruct001"); }}); //  statConstruct001 cSO.class001.prototype.construct = function(c1) { console.log('c1'); this.protoFunc("construct", arguments); } cSO.class001.stat.prototype.st = function(s1) { console.log('st1'); this.protoFunc("st"); } cSO.class001.stat.dat = ["hello1"]; cSO.class002 = function() { } cSO.extend(cSO.class002, cSO.class001, {"statConstruct":function(sc2) { console.log("statConstruct002"); this.protoFunc("construct", arguments); }}); //  statConstruct002 statConstruct001 cSO.class002.prototype.construct = function(c2) { console.log('c2'); this.protoFunc("construct", arguments); } cSO.class002.stat.st = function(s2) { console.log('st2'); this.protoFunc("st"); } cSO.class003 = function() { } cSO.extend(cSO.class003, cSO.class002); //  statConstruct002 statConstruct001 cSO.class003.prototype.construct = function(c3) { console.log('c3'); this.protoFunc("construct", arguments); } cSO.class003.stat.prototype.st = function(s3) { console.log('st3'); this.protoFunc("st"); } cSO.class003.stat.dat = ["hello3"]; 


And now we will find out in what order their actions are performed.

 var obj001 = new cSO.class001(); var obj002 = new cSO.class002(); var obj003 = new cSO.class003(); obj001.construct(); // c1 obj002.construct(); // c2 c1 obj003.construct(); // c3 c2 c1 cSO.class001.stat.st(); // st1 cSO.class002.stat.st(); // st2 st1 cSO.class003.stat.st(); // st3 st1 //    cSO.class002.stat.st  prototype console.log(obj003.stat.dat); // ["hello3"] obj002.stat.dat = ["world"]; console.log(obj002.stat.dat); // ["world"] cSO.class002.stat.deleteStatVal("dat"); console.log(obj002.stat.dat); // ["hello1"] console.log(obj001.stat.dat); // ["hello1"] 


A few more strokes that seemed important to me


As a result of daily practice, I have approximately the following structure of objects:
 _class={ construct:function(){}, //     -. destruct:function(){}, //     -. //  .. stat:{ create:function(){}, //       . collection:[], //        . clearAll:function(){} //       . //  .. } } 

Let me explain why.
To create a class, you need to call the constructor, the constructor that is called when new Foo () is not called when creating the descendants of this class.

For example:

 var id = 0; var Foo = function() { this.id = id++; console.log("  ,   boom"); } foo.prototype.boom = function() {} var Bar = function() { } Bar.prototype = new Foo(); var fooOb = new Foo(); //   ,   boom var barOb = new Bar(); var barOb2 = new Bar(); console.log(fooOb.id); // 1 console.log(barOb.id); // 0 console.log(barOb2.id); // 0 


And I want all the objects, the heirs of the Foo class, to have a unique id and warn the user that they can explode.
To implement this, I create a special cnstruct method (the constructor is already taken), and execute it when creating each object. In order not to forget to execute it, I refuse to create objects through new Foo () and create objects through the static method Foo.stat.create ().
The following is a shortened version of the class that is actually used, as an example of how the classes are obtained.

Real example


This class should be considered as one of many in the chain of prototypes from the base class to the final one (rather, in the opposite direction).
 (function() { var _class = cSO.LocalStorageSyncDataType = function () { /* *     ,  *       . *     ,  . */ } cSO.extend(_class, cSO.ServerSyncDataType, {"statConstruct": function() { this.protoFunc("construct", arguments); //   ,      protoFunc -    . if("addToClassesForSnapshot" in this) { //     cSO.LocalStorageSyncDataType,    ,     . this.addToClassesForSnapshot(this._class); //      . } }}); var _class_stat = _class.stat; //   -   ()     15%-25%   .       . var _class_stat_prototype = _class_stat.prototype; var _class_prototype = _class.prototype; var cfs = _class_stat.classesForSnapshot = []; _class_stat.create = function(args) { //     ,   ..   . this.addedToLocalStorage = false; if(args.addedToLocalStorage) { this.addedToLocalStorage = true; } this.protoFunc("construct", arguments); } _class_prototype.construct = function(args) { /* *    ,    . *         . */ this.addedToLocalStorage = false; if(args.addedToLocalStorage) { this.addedToLocalStorage = true; } this.protoFunc("construct", arguments); } _class_prototype.setLoaded = function(val) { this.protoFunc("setLoaded", arguments); // ,    ,      . } _class_stat.addToClassesForSnapshot = function(clas) { clas = clas || this._class; for(var i = 0; i < cfs.length; i++) { if(cfs[i] === clas) return; } cfs.push(clas); } _class_stat.createAllSnapshots = function() { for(var i = 0; i < cfs.length; i++) { cfs[i].stat.createSnapshot(); } } _class_stat_prototype.createSnapshot = function() { var co = this.collection; var str = ""; for(var i in co) { if(co[i]) { if(!str) { str = "["; } else { str += ","; } str += co[i].getJSON(); } } if(str) str += "]"; this.snapshot = str; } _class_stat.saveAllSnapshotsOnLocalStorage = function() { for(var i = 0; i < cfs.length; i++) { cfs[i].stat.saveSnapshotOnLocalStorage(); } } _class_stat_prototype.saveSnapshotOnLocalStorage = function() { if(this.snapshot) { cSO.localStorage.setItem(this.tableName, this.snapshot); } } _class_stat.setAllBySnapshotsFromLocalStorage = function() { for(var i = 0; i < cfs.length; i++) { cfs[i].stat.setBySnapshotFromLocalStorage(); } } _class_stat_prototype.setBySnapshotFromLocalStorage = function() { var arr = $.parseJSON(cSO.localStorage.getItem(this.tableName)); for(var i = 0; i < arr.length; i++) { if(arr[i]) { this.createOrGet({"cells":arr[i], "addedToLocalStorage":true}); } } } })(); 


I would add that this approach should be used specifically for classes of objects, and for “lonely” objects (for example, cSO.localStorage) you should use a traditional factory of objects.

PS I understand that most programming concepts and speed of execution with this approach suffer greatly.
I also understand that this style is not new, and for sure there are other, more suitable ones (thanks if you point them out).
PS Do not swear strongly on the code, my problem is that I almost never showed my code to others.
In a personal blog

Source: https://habr.com/ru/post/138913/


All Articles