简体   繁体   中英

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 ?

 $.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() .

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.

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

It is because this is an anonymous function. This part of your function deceleration:

   var foo = (function(){

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

    return {

        doFooStuff: doFooStuff
      }

   })();

Do you see that you are immediately invoking this function call and ultimately do not have access to doFooStuff within your return statement.

If you remove the anonymous function deceleration, it will work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM