0

I have a commercial application that has an existing JavaScript object structure using prototype chains. I have had success extending this API by adding more methods to the prototypes of objects. However, I realize that it would be best to add a namespace in front of my methods in case the application vendor decides to name a new method the same as one of my methods in a future release.

If I have an existing object called State, I would add a method called getPop like so:

State.prototype.getPop = function(){return this.pop;};

var Washington = new State('Washington',7000000);

Washington.getPop(); //returns 7000000

What I want to do is add a namespace called 'cjl' before my custom method to avoid name collision so that I can call it like so:

Washington.cjl.getPop();

I tried:

State.prototype.cjl = {};
State.prototype.cjl.getPop = function(){return this.pop;};

The problem is this. It doesn't point to the instance but instead points to the 'cjl' object.

I tried various methods, including using .bind() but none of them seemed to work. I finally found an answer here: Is it possible to organise methods on an object's prototype into namespaces? This works using the Object.defineProperty() method. The problem is the commercial application only works in compatibility mode in IE which doesn't support the Object.defineProperty() method for non-DOM elements.

Is there another way to accomplish this? I don't want to have to call multiple functions, which is the result of some techniques, e.g.:

Washington.cjl().getPop();
Community
  • 1
  • 1
Johnsonium
  • 2,005
  • 1
  • 13
  • 15
  • 1
    You'll be better off prefixing your method names. There's no real clean solution to do what you want. – cookie monster May 30 '14 at 19:52
  • 2
    Much better to just do `Washington.cjl_getPop();` as it's a ton easier to implement and faster to execute and really has no more likelihood of name collision than what you were proposing (in fact, it might be even less prone to collision). Using a sub-object on the prototype is not simple. – jfriend00 May 30 '14 at 19:53
  • Well, I'm somewhat glad it's not simple because trying to get this to work has made me question the legitimacy of my intermediate JavaScript skills. I want to make using my extensions as painless and clean as possible for the people that was using it, that's why I wanted to avoid the prefix method if possible but I may just fall back on that. – Johnsonium May 30 '14 at 20:32

3 Answers3

1

You could namespace in the following way, reading your comments I see that you can't change the original constructor so you'll have to replace the original with your own and save the original in a closure.

Every state instance will have it's own cjl instance but that only has a reference to current State instance, all the cjl functions are shared as they exist only once:

[UPDATE] Forgot to get State.prototype in myState's prototype chain.

//the original constructor
function State(name, pop){
  this.name=name;this.pop=pop;
}
State.org="original constructor";

//original constructor is available through
//  closure and window.State is replaced with
//  your constructor having the cjl namespace
(function(State){
  //cjl namespace
  function cjl(stateInstance){
    this.stateInstance=stateInstance;
  };
  //cjl functions
  cjl.prototype.getPopInThousands=function(){
    //do not use this, instead use this.stateInstance
    return this.stateInstance.pop/1000;
  }
  function myState(){
    //apply State constructor
    State.apply(this,arguments);
    //create a clj instance, all methods
    //  are on cjl.prototype so they're shared
    this.cjl = new cjl(this);
  }
  //inherit from State (use polyfil for older browsers)
  myState.prototype = Object.create(State.prototype);
  //replace window.State with your constructor
  window.State=myState;
}(State))

var s = new State("Wasington", 7000000);
console.log(s.cjl.getPopInThousands());
//non standard, name
console.log("constructor name",s.constructor.name);
console.log("constructor tostring",s.constructor.toString());

More on constructor functions and prototype can be found here: https://stackoverflow.com/a/16063711/1641941

I have to agree with friend and cookie that pre fixing the function names may be the better solution but if you want to use the same methods for an object named Country then you may think of using the previous code as you can re use the cjl object.

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • Wow. Thanks for that. I'm not sure if I'll go to that length but I'm going to analyze it fully to improve my understanding. I have a question about it. Will this will work if the objects are already instantiated when my code fires? That's the situation I have to deal with. – Johnsonium Jun 02 '14 at 06:33
  • @Johnsonium If you want already existing instances to have the functions you cannot use the code above. That code replaces the constructor with something else and already created instances were created with the old constructor so won't have your added functions. In your case you have to mutate the State.prototype and use the name prefix as already suggested: `State.prototype.cji_getPop=function(){...` – HMR Jun 02 '14 at 08:06
0

Instead of defining State.prototype.cjl outside of the function, try to set the cjl "namespace" inside the constructor function.

function State(){
    var thisObject = this;

    this.cjl = {
        getPop: function(){
            return thisObject.pop;
        }
    };
}

Then you can do Washington.cjl.getPop();.

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • You're losing all the benefit of prototypal inheritance. Might as well skip it altogether then and create an object factory. – cookie monster May 30 '14 at 19:55
  • Unfortunately, I don't have the ability to modify the constructor as it is elaborate and obfuscated. I don't think I'd want to change it even if I could as my intent is to extend the supplied functionality, not modify it. – Johnsonium May 30 '14 at 20:37
0

Try:

var State = function(name, pop) {
  this.name = name;
  this.pop = pop;
};

State.prototype.cjl = function(method) {
  return this.cjlDefs[method].apply(this, Array.prototype.slice.call(arguments, 1) );
};

State.prototype.cjlDefs = {
  getPop: function() {return this.pop;}
};


var Washington = new State('Washington', 80000);
console.log( Washington.cjl('getPop') );

https://jsfiddle.net/ghbjhxyh/

Or another shape if you prefer:

var State = function(name, pop) {
  this.name = name;
  this.pop = pop;
};

State.prototype.cjl = function(method) {
  this.cjlDefs.obj = this;
  return this.cjlDefs;
};

State.prototype.cjlDefs = {
  assertObj: function() { /* Make sensible assertion */ },
  getPop: function() { this.assertObj(); return this.obj.pop; }
};

var Washington = new State('Washington', 75000);
console.log( Washington.cjl().getPop() ); // 75000

https://jsfiddle.net/7vjrz2mn/