Simple javascript inheritance using $.extend and module pattern

I have wondered for a couple years now what people think of doing inheritance with a module-pattern-esque constructor pattern and WITHOUT normal prototypal inheritance. Why do programmers not use a module pattern for non-singleton js classes? For me the advantages are:

  • Very clear public and private scope (easy to understand the code and the api)
  • No needing to track the 'this' pointer via $.proxy(fn, this) in callbacks
  • No more var that = this, etc. with event handlers, etc. Whenever I see a 'this', I know it is context that is being passed into a callback, it is NOT something I am tracking to know my object instance.
  • Disadvantages:

  • Small perf degradation
  • Risk possible "wag of the finger" from Doug Crockford?
  • Consider this (just run in any js console)

    var Animal = function () {
        var publicApi = {
            Name: 'Generic',
            IsAnimal: true,
            AnimalHello: animalHello,
            GetHelloCount:getHelloCount
        };
    
        var helloCount = 0;
    
        function animalHello() {
            helloCount++;
            console.log(publicApi.Name + ' says hello (animalHello)');
        }
    
        function getHelloCount(callback) {
            callback.call(helloCount);
        }
    
        return publicApi;
    };
    
    var Sheep = function (name) {
        var publicApi = {
            Name: name || 'Woolie',
            IsSheep: true,
            SheepHello: sheepHello
        };
    
        function sheepHello() {
            publicApi.AnimalHello();
            publicApi.GetHelloCount(function() {
                console.log('i (' + publicApi.Name + ') have said hello ' + this + ' times (sheepHello anon callback)');
            });
        }
    
        publicApi = $.extend(new Animal(), publicApi);
        return publicApi;
    };
    
    var sheepie = new Sheep('Sheepie');
    var lambie = new Sheep('Lambie');
    
    sheepie.AnimalHello();
    sheepie.SheepHello();
    lambie.SheepHello();
    

    My question is what are the drawbacks to this approach that I am not seeing? Is this a good approach?

    Thanks!

    [update]

    Thanks for the great responses. Wish I could give everyone the bounty. It was what I was looking for. Basically what I thought. I would never use module pattern to construct more than a few instances of something. Usually only a couple. The reason I think it has its advantages is whatever small perf degradation you see is recaptured in the simplicity of the coding experience. We have a LOT of code to write these days. We also have to reuse other peoples' code and personally I appreciate when someone has taken the time to create a nice elegant pattern rather than dogmatically adhering to prototypal inheritance when it makes sense.


    I think it boils down to the issue of performance . You mentioned that there is small performance degradation, but this really depends on scale of the application(2 sheep vs 1000 sheep). Prototypal inheritance should not be ignored and we can create an effective module pattern using a mix of functional and prototypal inheritance .

    As mentioned in the post JS - Why use Prototype?, one of the beauties of prototype is that you only need to initialize the prototypal members only once, whereas members within the constructor are created for each instance . In fact, you can access prototype directly without creating a new object.

    Array.prototype.reverse.call([1,2,3,4]);
    //=> [4,3,2,1]
    
    function add() {
        //convert arguments into array
        var arr = Array.prototype.slice.call(arguments),
            sum = 0;
        for(var i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
    
        return sum;
    }
    
    add(1,2,3,4,5);
    //=> 15
    

    In your functions, there is extra overhead to create a completely new Animal and sheep each time a constructor is invoked. Some members such as Animal.name are created with each instance, but we know that Animal.name is static so it would be better to instantiate it once. Since your code implies that Animal.name should be the same across all animals, it is easy to update Animal.name for all instance simply by updating Animal.prototype.name if we moved it to the prototype.

    Consider this

    var animals = [];
    for(var i = 0; i < 1000; i++) {
        animals.push(new Animal());
    }
    

    Functional inheritance/Module Pattern

    function Animal() {
    
        return {
          name : 'Generic',
          updateName : function(name) {
              this.name = name;
          }
       }
    
    }
    
    
    //update all animal names which should be the same
    for(var i = 0;i < animals.length; i++) {
        animals[i].updateName('NewName'); //1000 invocations !
    }
    

    vs. Prototype

    Animal.prototype = {
    name: 'Generic',
    updateName : function(name) {
       this.name = name
    };
    //update all animal names which should be the same
    Animal.prototype.updateName('NewName'); //executed only once :)
    

    As shown above with your currently module pattern we lose effeciency in updating properties that should be in common to all members.

    If you are concered about visibility , I would use the same modular method you are currently using to encapsulate private members but also use priviledged members for accessing these members should they need to be reached. Priviledged members are public members that provide an interface to access private variables. Finally add common members to the prototype.

    Of course going this route, you will need to keep track of this. It is true that in your implementation there is

  • No needing to track the 'this' pointer via $.proxy(fn, this) in callbacks
  • No more var that = this, etc. with event handlers, etc. Whenever I see a 'this', I know it is context that is being passed into a callback, it is NOT something I am tracking to know my object instance.
  • , but you are are creating a very large object each time which will consume more memory in comparison to using some prototypal inheritance.

    Event Delegation as Analogy

    An analogy to gaining performance by using prototypes is improved performance by using event delegation when manipulating the DOM.Event Delegation in Javascript

    Lets say you have a large grocery list.Yum.

    <ul ="grocery-list"> 
        <li>Broccoli</li>
        <li>Milk</li>
        <li>Cheese</li>
        <li>Oreos</li>
        <li>Carrots</li>
        <li>Beef</li>
        <li>Chicken</li>
        <li>Ice Cream</li>
        <li>Pizza</li>
        <li>Apple Pie</li>
    </ul>
    

    Let's say that you want to log the item you click on. One implementation would be to attach an event handler to every item(bad) , but if our list is very long there will be a lot of events to manage.

    var list = document.getElementById('grocery-list'),
     groceries = list.getElementsByTagName('LI');
    //bad esp. when there are too many list elements
    for(var i = 0; i < groceries.length; i++) {
        groceries[i].onclick = function() {
            console.log(this.innerHTML);
        }
    }
    

    Another implementation would be to attach one event handler to the parent(good) and have that one parent handle all the clicks. As you can see this is similar to using a prototype for common functionality and significantly improves performance

    //one event handler to manage child elements
     list.onclick = function(e) {
       var target = e.target || e.srcElement;
       if(target.tagName = 'LI') {
           console.log(target.innerHTML);
       }
    }
    

    Rewrite using Combination of Functional/Prototypal Inheritance

    I think the combination of functional/prototypal inheritance can be written in an easy understandable manner. I have rewritten your code using the techniques described above.

    var Animal = function () {
    
        var helloCount = 0;
        var self = this;
        //priviledge methods
        this.AnimalHello = function() {
            helloCount++;
            console.log(self.Name + ' says hello (animalHello)');
        };
    
        this.GetHelloCount = function (callback) {
            callback.call(null, helloCount);
        }
    
    };
    
    Animal.prototype = {
        Name: 'Generic',
        IsAnimal: true
    };
    
    var Sheep = function (name) {
    
        var sheep = new Animal();
        //use parasitic inheritance to extend sheep
        //http://www.crockford.com/javascript/inheritance.html
        sheep.Name = name || 'Woolie'
        sheep.SheepHello = function() {
            this.AnimalHello();
            var self = this;
            this.GetHelloCount(function(count) {
                console.log('i (' + self.Name + ') have said hello ' + count + ' times (sheepHello anon callback)');
            });
        }
    
        return sheep;
    
    };
    
    Sheep.prototype = new Animal();
    Sheep.prototype.isSheep = true;
    
    var sheepie = new Sheep('Sheepie');
    var lambie = new Sheep('Lambie');
    
    sheepie.AnimalHello();
    sheepie.SheepHello();
    lambie.SheepHello();
    

    Conclusion

    The takeaway is to use both prototypal and functional inheritance to their advantages both to tackle performance and visibility issues . Lastly, if you are working on a small JavaScript applications and these performance issues are not a concern , then your method would be viable approach.


    a module-pattern-esque constructor pattern

    This is known as parasitic inheritance or functional inheritance.

    For me the advantages are:

  • Very clear public and private scope (easy to understand this code and the api)
  • The same holds true for the classical constructor pattern. Btw, in your current code it's not super clear whether animalHello and getHelloCount are supposed to be private or not. Defining them right in the exported object literal might be better if you care about that.

  • No needing to track the 'this' pointer via $.proxy(fn, this) in callbacks
  • No more var that = this, etc. with event handlers, etc. Whenever I see a 'this', I know it is context that is being passed into a callback, it is NOT something I am tracking to know my object instance.
  • That's basically the same. You either use a that dereference or binding to solve this problem. And I don't see this as a huge disadvantage, since the situation where you'd use object "methods" directly as callbacks are pretty rare - and apart from the context you often want to feed additional arguments. Btw, you're using a that reference as well in your code, it's called publicApi there.

    Why do programmers not use a module pattern for non-singleton js classes?

    Now, you've named some disadvantages yourself already. Additionally, you are loosing prototypical inheritance - with all its advantages (simplicity, dynamism, instanceof , …). Of course, there are cases where they don't apply, and your factory function is perfectly fine. It is indeed used in these cases.

    publicApi = $.extend(new Animal(), publicApi);
    …
    … new Sheep('Sheepie');
    

    These parts of your code are a little confusing as well. You are overwriting the variable with a different object here, and it happens in the middle-to-end of your code. It would be better to see this as a "declaration" (you're inheriting the parent properties here!) and put it at the top of your function - right as var publicApi = $.extend(Animal(), {…});

    Also, you should not use the new keyword here. You do not use the functions as constructors, and you don't want to create instances that inherit from Animal.prototype (which slows down your execution). Also, it confuses people that might expect prototypical inheritance from that constructor invocation with new . For clarity, you even might rename the functions to makeAnimal and makeSheep .

    What would be a comparable approach to this in nodejs?

    This design pattern is completely environment-independent. It will work in Node.js just as like as at the client and in every other EcmaScript implementation. Some aspects of it even are language-independent.


    With your approach, you wont be able to override functions and call the super function so conveniently.

    function foo ()
    {
    }
    
    foo.prototype.GetValue = function ()
    {
            return 1;
    }
    
    
    function Bar ()
    {
    }
    
    Bar.prototype = new foo();
    Bar.prototype.GetValue = function ()
    {
        return 2 + foo.prototype.GetValue.apply(this, arguments);
    }
    

    Also, in the prototype approach, you can share data among all the instances of the object.

    function foo ()
    {
    }
    //shared data object is shared among all instance of foo.
    foo.prototype.sharedData = {
    }
    
    var a = new foo();
    var b = new foo();
    console.log(a.sharedData === b.sharedData); //returns true
    a.sharedData.value = 1;
    console.log(b.sharedData.value); //returns 1
    

    One more advantage of the prototype approach would be to save memory.

    function foo ()
    {
    }
    
    foo.prototype.GetValue = function ()
    {
       return 1;
    }
    
    var a = new foo();
    var b = new foo();
    console.log(a.GetValue === b.GetValue); //returns true
    

    Whereas in of your approach,

    var a = new Animal();
    var b = new Animal();
    console.log(a.AnimalHello === b.AnimalHello) //returns false
    

    This means with each new object, a new instance of the functions are created where as it is shared among all objects incase of prototype approach. This wont make much difference incase of few instances but when large number of instances are created, it would show a considerable difference.

    Also, one more powerful feature of prototype would be once all objects are created, you can still change the properties among all objects at once (only if they are not altered after object creation).

    function foo ()
    {
    }
    foo.prototype.name = "world";
    
    var a = new foo ();
    var b = new foo ();
    var c = new foo();
    c.name = "bar";
    
    foo.prototype.name = "hello";
    
    console.log(a.name); //returns 'hello'
    console.log(b.name); //returns 'hello'
    console.log(c.name); //returns 'bar' since has been altered after object creation
    

    Conclusion: If the above mentioned advantages of the prototype approach are not so useful for your application, your approach would be better.

    链接地址: http://www.djcxy.com/p/50128.html

    上一篇: Javascript:模块模式vs构造函数/原型模式?

    下一篇: 简单的JavaScript继承使用$ .extend和模块模式