Javascript functional inheritance with prototypes

后端 未结 4 1712
心在旅途
心在旅途 2021-02-01 08:58

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

var mammal = function(spec, my         


        
相关标签:
4条回答
  • 2021-02-01 09:03

    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);
    };
    
    0 讨论(0)
  • 2021-02-01 09:05

    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.

    0 讨论(0)
  • 2021-02-01 09:20

    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.

    0 讨论(0)
  • 2021-02-01 09:29

    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 讨论(0)
提交回复
热议问题