admin管理员组

文章数量:1287849

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?

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?

Share Improve this question edited Jun 24, 2012 at 1:16 mpen asked Jun 23, 2012 at 20:38 mpenmpen 283k281 gold badges890 silver badges1.3k bronze badges 13
  • 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 Commented Jun 23, 2012 at 20:47
  • 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 Commented Jun 23, 2012 at 20:56
  • 1 Every instance you create has a reference to MyClass.prototype. instA.init === instB.init will yield true. – Felix Kling Commented Jun 23, 2012 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. – user1106925 Commented Jun 23, 2012 at 21:16
  • 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 Commented Jun 23, 2012 at 22:02
 |  Show 8 more ments

6 Answers 6

Reset to default 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. )

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.

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. to add static properties
 * By John Resig http://ejohn/
 * 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;
    };
})();

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/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

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 mented with "add in the static members"

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn/
 * 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>

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;
});

本文标签: javascriptStatic variables with John Resig39s simple class patternStack Overflow