Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating inheritance on revealing modular pattern objects

I'm trying to create some kind of inheritance between objects:

var foo = (function(){

    function doFooStuff(){
        console.log(arguments.callee.name);
    }

    return {
        doFooStuff: doFooStuff
    }

})();

var bar = (function(){

    $.extend(this, foo);

    function doBarStuff(){
        console.log(arguments.callee.name);
        doFooStuff();
    }

    return {
        doBarStuff: doBarStuff,
    }

})();

bar.doBarStuff();
bar.doFooStuff(); //<-- Uncaught TypeError: 
                  //Object #<Object> has no method 'doFooStuff' 

http://jsfiddle.net/wRXTv/

Why isn't doFooStuff accessible here? Would you recommend another approach than using $.extend?

like image 549
Johan Avatar asked Sep 15 '13 20:09

Johan


3 Answers

$.extend(this, foo);

this is not the object which you return from the function below (in fact it cannot be since it's created after this call), but the global object - check MDN's introduction to the this keyword.

For what you want to do, there are two ways:

  • Copy all properties from foo onto your bar object after it is created:

    var bar = (function() {
        …
        return {…};
    })();
    $.extend(bar, foo);
    

    You can do that as well directly on the returned object:

        return $.extend({…}, foo);
    

    A variant of this pattern allows you to overwrite foo properties. Copy foo into an empty object, then write your bar properties to it:

        return $.extend({}, foo, {…});
    
  • Use prototypical inheritance. Create an object that inherits its properties from foo, and then write your bar properties to it:

        return $.extend(Object.create(foo), {…});
    

    Now when foo changes afterward, you still can access those new properties on bar (unless they're shadowed by own properties). Notice that Object.create might not be supported in legacy environments, but you can easily shim it.


As noted by @raina77ow, your doBarStuff function is flawed too. The doFooStuff function is not in the scope of your function, and you cannot change that. You never will be able to access the private declared functions and variables from the foo module, only those that are public - if you did need them, consider a different pattern or app design. However, doFooStuff is a property on the exported (public) foo object, from which bar inherits (regardless in which of the above demonstrated ways). Therefore, you can access it as a method of bar as well, usually by using this.doFooStuff().

like image 152
Bergi Avatar answered Nov 20 '22 10:11

Bergi


You attempt to work with revealing module as if it were a constructor. Hence an attempt to extend this, wrong for many reasons. The most glaring, I suppose, is that neither the function is used as a constructor (no new) nor its context is changed. In plain words, this just points to a global object here.

But that's not the only problem. Consider that part of your code:

function doBarStuff(){
  console.log(arguments.callee.name);
  doFooStuff();
}

Here doFooStuff won't be in the scope even if you somehow manage to extend this. ) Remember, the scope resolution doesn't involve the context object.

So what's the solution? Well, I often use aggregation in similar cases:

var foo = (function(){
    function doFooStuff(){
        console.log(arguments.callee.name);
    }
    return {
        doFooStuff: doFooStuff
    }
})();

var bar = (function(){
    var _super = foo;
    function doBarStuff(){
        console.log(arguments.callee.name);
        _super.doFooStuff();
    }
    // static parent: further changes on `foo` won't affect `bar`,
    // as $.extend takes the parent's current state
    return $.extend({}, _super, {
        doBarStuff: doBarStuff,
    });
    // dynamic parent: further changes on `foo` will affect `bar`,
    // as extended object now has `foo` in its prototype chain
    return $.extend(Object.create(_super), {
        doBarStuff: doBarStuff,
    });
})();

JS Fiddle.

Yes, it's aggregation, not inheritance. But so what? I'm still able to get the main prize - removing code duplication AND I'm able to control the usage of parent functions within the child module.

like image 27
raina77ow Avatar answered Nov 20 '22 09:11

raina77ow


The problem with your code is that this in $.extend(this,foo) refers to the window object and not foo. Anonymous function are run in window's context.

I would recommend using a John Resig implementation of classical inheritance in javascript. http://ejohn.org/blog/simple-javascript-inheritance/.

Extract:

var Person = Class.extend({
 init: function(isDancing){
 this.dancing = isDancing;
 },
dance: function(){
 return this.dancing;
 }
});

var Ninja = Person.extend({
 init: function(){
  this._super( false );
 },
dance: function(){
 // Call the inherited version of dance()
 return this._super();
},
 swingSword: function(){
  return true;
 }
});

var p = new Person(true);
p.dance(); // => true

var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true

// Should all be true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class
like image 1
Selvam Palanimalai Avatar answered Nov 20 '22 10:11

Selvam Palanimalai