开发者

Javascript functional inheritance with prototypes

In Douglas Crockford's JavaScript: The Good Parts he recommends that we use functional inheritance. Here's an example:

var mammal = function(spec, my) {
    var that = {};
    my = my || {};

    // Protected
    my.clearThroat = function() { 
        return "Ahem";
    };

    that.getName = function() {
        return spec.name;
    };

    that.says = fu开发者_如何学Gonction() {
        return my.clearThroat() + ' ' + spec.saying || '';
    };

    return that;
};

var cat = function(spec, my) {
    var that = {};
    my = my || {};

    spec.saying = spec.saying || 'meow';
    that = mammal(spec, my);

    that.purr = function() { 
        return my.clearThroat() + " purr"; 
    };

    that.getName = function() { 
        return that.says() + ' ' + spec.name + ' ' + that.says();
    };

    return that;
};

var kitty = cat({name: "Fluffy"});

The main issue I have with this is that every time I make a mammal or cat the JavaScript interpreter has to re-compile all the functions in it. That is, you don't get to share the code between instances.

My question is: how do I make this code more efficient? For example, if I was making thousands of cat objects, what is the best way to modify this pattern to take advantage of the prototype object?


Well, you just can't do it that way if you plan on making lots of mammal or cat. Instead do it the old fashioned way (prototype) and inherit by property. You can still do the constructors the way you have above but instead of that and my you use the implicit this and some variable representing the base class (in this example, this.mammal).

cat.prototype.purr = function() { return this.mammal.clearThroat() + "purr"; }

I'd use another name than my for base access and store it in this in the cat constructor. In this example I used mammal but this might not be the best if you want to have static access to the global mammal object. Another option is to name the variable base.


Let me introduce you to Classical Inheritance that never uses prototype. This is a bad coding exercise but will teach you the real Classical Inheritance which always compared to prototypal inheritance:

Make a custructor:

function Person(name, age){
  this.name = name;
  this.age = age;
  this.sayHello = function(){return "Hello! this is " + this.name;}
}

Make another cunstructor that inherits from it:

function Student(name, age, grade){
  Person.apply(this, [name, age]);
  this.grade = grade
}

Very simple! Student calls(applies) Person on itself with name and age arguments takes care of grade arguments by itself.

Now lets make an instance of Student.

var pete = new Student('Pete', 7, 1);

Out pete object will now contain name, age, grade and sayHello properties. It owns all those properties. They are not uplinked to Person through prototype. If we change Person to this:

function Person(name, age){
  this.name = name;
  this.age = age;
  this.sayHello = function(){
    return "Hello! this is " + this.name + ". I am " this.age + " years old";
  }
}

pete will no recieve the update. If we call pete.sayHello, ti will return Hello! this is pete. It will not get the new update.


if you want privacy and you dont like protyping you may or may-not like this approach:

(note.: it uses jQuery.extend)

var namespace = namespace || {};

// virtual base class
namespace.base = function (sub, undefined) {

    var base = { instance: this };

    base.hierarchy = [];

    base.fn = {

        // check to see if base is of a certain class (must be delegated)
        is: function (constr) {

            return (this.hierarchy[this.hierarchy.length - 1] === constr);
        },

        // check to see if base extends a certain class (must be delegated)
        inherits: function (constr) {

            for (var i = 0; i < this.hierarchy.length; i++) {

                if (this.hierarchy[i] == constr) return true;
            }
            return false;
        },

        // extend a base (must be delegated)
        extend: function (sub) {

            this.hierarchy.push(sub.instance.constructor);

            return $.extend(true, this, sub);
        },

        // delegate a function to a certain context
        delegate: function (context, fn) {

            return function () { return fn.apply(context, arguments); }
        },

        // delegate a collection of functions to a certain context
        delegates: function (context, obj) {

            var delegates = {};

            for (var fn in obj) {

                delegates[fn] = base.fn.delegate(context, obj[fn]);
            }

            return delegates;
        }
    };

    base.public = {
        is: base.fn.is,
        inherits: base.fn.inherits
    };

    // extend a sub-base
    base.extend = base.fn.delegate(base, base.fn.extend);

    return base.extend(sub);
};

namespace.MyClass = function (params) {

    var base = { instance: this };

    base.vars = {
        myVar: "sometext"
    }

    base.fn = {
        init: function () {

            base.vars.myVar = params.myVar;
        },

        alertMyVar: function() {

            alert(base.vars.myVar);
        }

    };

    base.public = {
        alertMyVar: base.fn.alertMyVar
    };

    base = namespace.base(base);

    base.fn.init();

    return base.fn.delegates(base,base.public);
};

newMyClass = new namespace.MyClass({myVar: 'some text to alert'});
newMyClass.alertMyVar();

the only downside is that because of the privacy scope you can only extend the virtual classes and not the instanceable classes.

here is an example of how i extend the namespace.base, to bind/unbind/fire custom events.

// virtual base class for controls
namespace.controls.base = function (sub) {

    var base = { instance: this };

    base.keys = {
        unknown: 0,
        backspace: 8,
        tab: 9,
        enter: 13,
        esc: 27,
        arrowUp: 38,
        arrowDown: 40,
        f5: 116
    }

    base.fn = {

        // bind/unbind custom events. (has to be called via delegate)
        listeners: {

            // bind custom event
            bind: function (type, fn) {

                if (fn != undefined) {

                    if (this.listeners[type] == undefined) {
                        throw (this.type + ': event type \'' + type + '\' is not supported');
                    }

                    this.listeners[type].push(fn);
                }

                return this;
            },

            // unbind custom event
            unbind: function (type) {

                if (this.listeners[type] == undefined) {
                    throw (this.type + ': event type \'' + type + '\' is not supported');
                }

                this.listeners[type] = [];

                return this;
            },

            // fire a custom event
            fire: function (type, e) {

                if (this.listeners[type] == undefined) {
                    throw (this.type + ': event type \'' + type + '\' does not exist');
                }

                for (var i = 0; i < this.listeners[type].length; i++) {

                    this.listeners[type][i](e);
                }

                if(e != undefined) e.stopPropagation();
            }
        }
    };

    base.public = {
        bind: base.fn.listeners.bind,
        unbind: base.fn.listeners.unbind
    };

    base = new namespace.base(base);

    base.fire = base.fn.delegate(base, base.fn.listeners.fire);

    return base.extend(sub);
};


To proper use Javascript-prototype based inheritance you could use fastClass https://github.com/dotnetwise/Javascript-FastClass

You have the simpler inheritWith flavor:

  var Mammal = function (spec) {
    this.spec = spec;
}.define({
    clearThroat: function () { return "Ahem" },
    getName: function () {
        return this.spec.name;
    },
    says: function () {
        return this.clearThroat() + ' ' + spec.saying || '';
    }
});

var Cat = Mammal.inheritWith(function (base, baseCtor) {
    return {
        constructor: function(spec) { 
            spec = spec || {};
            baseCtor.call(this, spec); 
        },
        purr: function() {
            return this.clearThroat() + " purr";
        },
        getName: function() {
            return this.says() + ' ' + this.spec.name + this.says();
        }
    }
});

var kitty = new Cat({ name: "Fluffy" });
kitty.purr(); // Ahem purr
kitty.getName(); // Ahem Fluffy Ahem

And if you are very concerned about performance then you have the fastClass flavor:

var Mammal = function (spec) {
    this.spec = spec;
}.define({
    clearThroat: function () { return "Ahem" },
    getName: function () {
        return this.spec.name;
    },
    says: function () {
        return this.clearThroat() + ' ' + spec.saying || '';
    }
});

var Cat = Mammal.fastClass(function (base, baseCtor) {
    return function() {
        this.constructor = function(spec) { 
            spec = spec || {};
            baseCtor.call(this, spec); 
        };
        this.purr = function() {
            return this.clearThroat() + " purr";
        },
        this.getName = function() {
            return this.says() + ' ' + this.spec.name + this.says();
        }
    }
});

var kitty = new Cat({ name: "Fluffy" });
kitty.purr(); // Ahem purr
kitty.getName(); // Ahem Fluffy Ahem

Btw, your initial code doesn't make any sense but I have respected it literally.

fastClass utility:

Function.prototype.fastClass = function (creator) {
    var baseClass = this, ctor = (creator || function () { this.constructor = function () { baseClass.apply(this, arguments); } })(this.prototype, this)

    var derrivedProrotype = new ctor();

    if (!derrivedProrotype.hasOwnProperty("constructor"))
        derrivedProrotype.constructor = function () { baseClass.apply(this, arguments); }

    derrivedProrotype.constructor.prototype = derrivedProrotype;
    return derrivedProrotype.constructor;
};

inheritWith utility:

Function.prototype.inheritWith = function (creator, makeConstructorNotEnumerable) {
    var baseCtor = this;
    var creatorResult = creator.call(this, this.prototype, this) || {};
    var Derrived = creatorResult.constructor ||
    function defaultCtor() {
        baseCtor.apply(this, arguments);
    }; 
    var derrivedPrototype;
    function __() { };
    __.prototype = this.prototype;
    Derrived.prototype = derrivedPrototype = new __;

    for (var p in creatorResult)
        derrivedPrototype[p] = creatorResult[p];

    if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
        Object.defineProperty(derrivedPrototype, 'constructor', {
            enumerable: false,
            value: Derrived
        });

    return Derrived;
};

define utility:

Function.prototype.define = function (prototype) {
    var extendeePrototype = this.prototype;
    if (prototype)
        for (var p in prototype)
            extendeePrototype[p] = prototype[p];
    return this;
}

[* Disclaimer, I am the author of the open source package and the names of the methods themselves might be renamed in future` *]

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜