6

I'm referring to this article.

In it, he defines a function that looks something like this:

function makeClass() {
    return function _class() {
        if(this instanceof _class) {
            if(typeof this.init === 'function') {
                this.init.apply(this, arguments);
            }
        } else {
            throw new Error('Constructor called as a function');
        }
    };
}

And then you can use it with something like this:

var MyClass = makeClass();

MyClass.prototype = {
    init: function(width, height) { ... },
    clear: function(ctx) {... },
    draw: function(ctx) { ... }
}

But now I want to initialize some static variables that should be shared across all instances. How do I do that?

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 1
    I'm not sure if I understand, but can't you add those to the prototype as well? Such variables (like the functions) are accessible on all instances and only exist at once place. – pimvdb Jun 23 '12 at 20:47
  • refer this question http://stackoverflow.com/questions/1535631/static-variables-in-javascript – Umesh Aawte Jun 23 '12 at 20:49
  • @pimvdb: Does it not create multiple copies of the object each time you instantiate the class? – mpen Jun 23 '12 at 20:53
  • 1
    @Mark: No, in fact the point of prototype is that they are *not* copied. Your prototype functions are shared as well - changing the prototype function makes that function change on all instances. – pimvdb Jun 23 '12 at 20:56
  • 1
    Every instance you create has a *reference* to `MyClass.prototype`. `instA.init === instB.init` will yield `true`. – Felix Kling Jun 23 '12 at 21:02
  • 2
    FWIW, I'd give the function a name, and use that name instead of `arguments.callee`, which should probably be avoided at this point. –  Jun 23 '12 at 21:16
  • +1, I was surprised by that recommendation too. ) – raina77ow Jun 23 '12 at 21:18
  • @pimvdb: Ah..so the only way to create "non-static" member variables then is by setting `this.xxx` inside the constructor/init function? – mpen Jun 23 '12 at 22:01
  • 1
    @Mark: In a sense, yes - basically, setting a property on the instance (e.g. `this.a = 1` in the constructor) doesn't share it, whereas setting it on the prototype (e.g. `MyClass.prototype.a = 1`) does. – pimvdb Jun 23 '12 at 22:02
  • @amnotiam: The `makeClass` function does have a name, and it doesn't work if I put `makeClass` in place of `arguments.callee`... – mpen Jun 23 '12 at 22:09
  • @Mark: I'm talking about the returned function. That's what's being referenced by `arguments.callee`. As in: `return function foobar() { if(this instanceof foobar) {`. Point is, don't use `arguments.callee`. –  Jun 23 '12 at 22:23
  • 1
    @amnotiam: Ahh..right. Glossed over that. Thanks for the tip. Will update 'lest someone else copies it. – mpen Jun 24 '12 at 01:16
  • related but not a duplicate https://stackoverflow.com/q/7892884/125981 – Mark Schultheiss Nov 05 '19 at 15:19

6 Answers6

4

Well, the easiest approach is to define a static variable as a prototype property:

MyClass.prototype.xxx: 3, // ...
var t1 = new MyClass();
console.log(t1.xxx); // 3

... but it won't behave as static properties in other languages usually do:

var t2 = new MyClass();
t2.xxx = 5;
console.log(t1.xxx); // still 3 :(

The other way around is to use the fact that properties might be attached to functions as well:

MyClass.xxx = 3;

... but that narrows the ways we can use this property (it can't be called by t1.xxx from the previous examples).

There's another way, though. One can define static properties as variables, local to init method, accessible by methods, defined... in this init method as well. ) Like this.

   init: function() {
      var xxx = 3;
      MyClass.prototype.getXXX = function() {
         return xxx;
      };
      MyClass.prototype.setXXX = function(newXXX) {
         xxx = newXXX;
      }
   }

Then all one can use this property simply by this:

  var t1 = new MyClass();
  var t2 = new MyClass();
  console.log(t1.getXXX()); // 3
  console.log(t2.getXXX()); // 3
  t1.setXXX(5);
  console.log(t1.getXXX()); // 5 now
  console.log(t2.getXXX()); // 5 as well, behold the power of closures!

And here's a fiddle used.

UPDATE: this approach is better be used, I suppose, when we need to work with a (sort of) container of the static class data, that is to be shared by all objects - but we don't know exactly what can actually be stored in this container. Then we use just two functions - getStatic and setStatic - to store and retrieve data by string keys or some other identifiers. It may seem a bit confusing, and it is, but I think it may be worth an effort. )

raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • 1
    The disadvantage of the last approach, however, is that every time you instantiate a new MyClass instance, the static variable's value gets reset. – John Kurlak Jan 17 '13 at 15:10
3

Just add it to MyClass itself.

MyClass.myVariable = 42;

It's not really static in the Java/C# sense, but gives you the same effect.

Jordão
  • 55,340
  • 13
  • 112
  • 144
2

I "solved" this problem by using a naming convention.

I wanted the convenience of the Class.extend({ }) syntax, but also a way to declare "static" properties within it.

I opted for a leading underscore to declare a static property, although you could do whatever you liked.

Usage:

var myClass = Class.extend({

    _staticProperty: 1337

    , instanceProperty: 'foo'
    , instanceMethod: function() { }

    , ctor: function() {
        this.base();
    }
});

note I've renamed init and this._super() from the original code

And the code:

/* Simple JavaScript Inheritance
 * Modified by Andrew Bullock http://blog.muonlab.com to add static properties
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function () {
    var initializing = false, fnTest = /xyz/.test(function () { xyz; }) ? /\bbase\b/ : /.*/;

    // The base Class implementation (does nothing)
    this.Class = function () { };

    // Create a new Class that inherits from this class
    Class.extend = function (prop) {
        var base = this.prototype;

        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;
        var prototype = new this();
        initializing = false;

        // The dummy class constructor
        function Class() {
            // All construction is actually done in the ctor method
            if (!initializing && this.ctor)
                this.ctor.apply(this, arguments);
        }

        // Copy static properties from base
        for (var name in this) {
            if (name.substr(0, 1) == '_')
                Class[name] = this[name];
        }

        // Copy the properties over onto the new prototype
        for (name in prop) {
            // Check if we're overwriting an existing function
            if (typeof prop[name] == "function" && typeof base[name] == "function" && fnTest.test(prop[name])) {
                prototype[name] = (function(name, fn) {
                    return function() {
                        var tmp = this.base;

                        // Add a new .base() method that is the same method
                        // but on the super-class
                        this.base = base[name];

                        // The method only need to be bound temporarily, so we
                        // remove it when we're done executing
                        var ret = fn.apply(this, arguments);
                        this.base = tmp;

                        return ret;
                    };
                })(name, prop[name]);
            } else if (name.substr(0, 1) == '_') {
                Class[name] = prop[name];
            } else {
                prototype[name] = prop[name];
            }
        }

        // Populate our constructed prototype object
        Class.prototype = prototype;

        // Enforce the constructor to be what we expect
        Class.prototype.constructor = Class;

        // And make this class extendable
        Class.extend = arguments.callee;

        return Class;
    };
})();
Andrew Bullock
  • 36,616
  • 34
  • 155
  • 231
1

If you don't care about browser support, you could also use a WeakMap of constructor/static properties pairs. Here's the idea: http://jsfiddle.net/DfNNU/2/. This requires MyClass.prototype.constructor, which you should not discard. So, you'd need to add back constructor: MyClass to the prototype.

var statics = (function() {
    var map = new WeakMap;
    return function(inst) {
        var ctor = inst.constructor;
        return map.get(ctor) || map.set(ctor, {});
    };
})();

Use it like:

var a = function() {};
var b = function() {};

var inst1 = new a;
var inst2 = new a;
var inst3 = new b;

statics(inst1).foo = 123;
statics(inst3).foo = 456;

console.log( statics(inst1).foo );  // 123
console.log( statics(inst2).foo );  // 123
console.log( statics(inst3).foo );  // 456
pimvdb
  • 151,816
  • 78
  • 307
  • 352
1

I've modified John Resig's class to provide copy over the parent's static members to the new class, which adds this:

    for (var name in this) {
        if (!Class[name]) {
            Class[name] = this[name];
        }
    }

Here's a fiddle.

// This is a modified version of John Resig's simple inheritence class to add copying of static methods
// The new code is the for loop commented with "add in the static members"

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
 
  // The base Class implementation (does nothing)
  this.Class = function(){};
 
  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;
   
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
   
    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;
           
            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];
           
            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);        
            this._super = tmp;
           
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
    
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
   
    // Populate our constructed prototype object
    Class.prototype = prototype;
   
    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    //add in the static members
    for (var name in this) {
      if (!Class[name]) {
          Class[name] = this[name];
        }
    }

    // And make this class extendable
    Class.extend = arguments.callee;
   
    return Class;
  };
})();

function addText(text) {
    document.getElementById('greetings').innerHTML = document.getElementById("greetings").innerHTML + '<br>' + text;
}

//parent class with a prototype method and two static methods
var Parent = Class.extend({
  hello: function () {
    addText('parent.hello');
  }
});  
Parent.static = function() {
  addText('Parent.static');
}
Parent.overrideStatic = function() {
  addText('Parent.overrideStatic');
}

//child class that overrides one of the parent's static methods
var Child = Parent.extend();
Child.overrideStatic = function() {
  addText('Child.overrideStatic');
}

var parent = new Parent();
parent.hello();
Parent.static();

var child = new Child();
child.hello();   //should output parent.hello
Child.static();  //should output Parent.static
Child.overrideStatic();
<div id="greetings"></div>
mpen
  • 272,448
  • 266
  • 850
  • 1,236
Mike Hedman
  • 1,391
  • 1
  • 11
  • 30
  • @mpen - Thanks for the edit. I did try to find out how to do a runnable snippet prior to posting my answer, but was not able to find it. I've searched harder now, and have found it: https://blog.stackoverflow.com/2014/09/introducing-runnable-javascript-css-and-html-code-snippets/ – Mike Hedman Mar 29 '16 at 16:15
  • 2
    NP. We like to inline as much code as possible here just in case the link goes down. Plus, it's nice to have everything in one place. However, I do like jsfiddle and jsbin more than SO's integrated fiddle thing, so the link is still nice. – mpen Mar 29 '16 at 17:10
0

Pass in an optional list of static members in the call to 'extend'. This method adds the static properties (if any) to a 'statics' attribute on the constructor function.

Code Changes

Changes as follows. These lines added just after the 'dummy class constructor' code:

    if(staticProp) {
        Class.statics = [];
        for (var name in staticProp) {
            !Class.statics[name] && (Class.statics[name] = staticProp[name]);
        }
    }

An additional argument 'staticProp' added when type is declared in order to allow introduction of static members at this stage:

Class.extend = function(prop,staticProp) {

A fiddle can be found here, includes some tests.

Usage Examples

Can define statics at type declaration time like so using the second optional constructor argument:

var A = Class.extend({},{myStatic:1});

Can access/define statics inside an instance method:

var B = Class.extend({test:function(){B.statics.myStatic=2;}});

Or from outside an instance:

A.statics.myStatic=3;

Example with requirejs:

Put Class.js in the baseUrl folder. Example new class definition. Not mandatory to name the file of the new class the same as the 'var C' (i.e. C.js) but probably better for readability so references to the C name within the class's methods are aligned to any external references to its static members:

define(['Class'],function($) {
    var C = Class.extend({
        init: function(params){
            C.statics.myStatic++; // access static data
        }
    },{
        myStatic: 123
    });
    return C;
});

Another class in D.js refers to static data in class C:

define(['Class', 'C'],function($,C) {
    var D = Class.extend({
        init: function(params){
            C.statics.myStatic++; // static data of another class
        }
    },{});
    return D;
});
Moika Turns
  • 677
  • 11
  • 17