2010-03-14 18 views
12

In Douglas Crockford JavaScript: Die guten Teile er empfiehlt, dass wir funktionale Vererbung verwenden. Hier ein Beispiel:Javascript funktionale Vererbung mit Prototypen

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

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

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

    that.says = function() { 
     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"}); 

Das Hauptproblem ich damit habe, ist, dass jedes Mal, wenn ich ein mammal oder cat die JavaScript-Interpreter machen muss neu kompilieren alle Funktionen drin. Das heißt, Sie können den Code nicht zwischen Instanzen austauschen.

Meine Frage ist: Wie mache ich diesen Code effizienter? Zum Beispiel, wenn ich Tausende von cat Objekte machte, was ist der beste Weg, um dieses Muster zu modifizieren, um das prototype Objekt zu nutzen?

+0

"* muss alle Funktionen neu kompilieren. Das heißt, Sie können den Code nicht zwischen Instanzen teilen * "- Nein. Der Code ist freigegeben, es müssen nur verschiedene Funktionsobjekte mit unterschiedlichen Bereichswerten erstellt werden. Es ist kein so großer Aufwand. – Bergi

Antwort

8

Nun, Sie können es einfach nicht so machen, wenn Sie planen, viele mammal oder cat zu machen. Stattdessen tue es die altmodische Art (Prototyp) und erbt nach Eigenschaft. Sie können die Konstruktoren immer noch so ausführen wie oben, aber anstelle von that und my verwenden Sie den impliziten this und einige Variablen, die die Basisklasse darstellen (in diesem Beispiel this.mammal).

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

würde ich einen anderen Namen als my für Bankzugriff verwenden und speichern sie in this im cat Konstruktor. In diesem Beispiel habe ich mammal verwendet, aber dies ist möglicherweise nicht das beste, wenn Sie statische Zugriff auf das globale Objekt mammal haben möchten. Eine andere Möglichkeit ist, die Variable base zu benennen.

+0

Das Problem mit der Verknüpfung der "Mein" Objekt über das 'This.mammal' ist, dass ich dann die Privatsphäre davon verlieren würde, da jemand einfach' cat.mammal.clearThroat() 'tun und auf die geschützte Methode zugreifen könnte. – cdmckay

+2

Also wahr. Wenn Sie Privatsphäre wünschen dann müssen Sie es per Konvention tun, zum Beispiel, dass Sie Ihre geschützten Methoden mit Unterstrichen versehen haben.Wenn das zu lose erscheint, sollten Sie bedenken, dass selbst dieses "funktionale Vererbungsmuster" selbst eine Konvention ist.Javascript ist keine "ordentliche Freak" -Sprache, das ist es viel mehr "rau und bereit." Es funktioniert eine ordentliche freaky Weg kostet eine Strafe in der Leistung, wie Sie herausgefunden haben.aber machen Sie nicht zu viel von der Strafe - es braucht Zehntausende von Objekten Kreationen, um deutlich zu beeinflussen Ihre Gesamtlaufgeschwindigkeit, gegeben Ihrem e Beispiel. – Plynx

+2

"jemand könnte einfach' cat.mammal.clearThroat() 'tun und auf die geschützte Methode zugreifen" - nicht jedes Problem muss mit Code gelöst werden. Mit dem größtmöglichen Respekt schreiben Sie nicht Windows. Wenn jemand deinen Code missbraucht, ist das großartig, weil er bedeutet, dass jemand deinen Code tatsächlich benutzt. Viel besser, sich darauf zu konzentrieren, Code zu erstellen, der nützlich und einfach zu verwenden ist, anstatt sich vor hypothetischen Problemen zu schützen. –

0

wenn Sie ungestört telefonieren möchten und Sie nicht wie Protyping können Sie oder können-nicht diesen Ansatz mag:

(Anmerkung .: nutzt 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(); 

der einzige Nachteil ist, dass wegen des Datenschutzbereichs können Sie nur die virtuellen Klassen und nicht die instanziierbaren Klassen erweitern.

Hier ist ein Beispiel, wie ich die namespace.base erweitern, um benutzerdefinierte Ereignisse zu binden/zu lösen/zu feuern.

// 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); 
}; 
1

Lassen Sie mich Ihnen die klassische Vererbung einführen, die nie prototype verwendet. Dies ist eine schlechte Codierung Übung, sondern lernen Sie die reale Classical Inheritance, die immer im Vergleich zu prototypal Erbe:

Machen Sie einen custructor:

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

andere cunstructor Stellen, die von ihm erbt:

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

Sehr einfach! Student Aufrufe (gilt) Person auf sich selbst mit name und age Argumente kümmert sich um grade Argumente an sich. Geben Sie eine Instanz von Student ein.

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

Out pete Objekt enthält jetzt name, age, grade und sayHello Eigenschaften. Es besitzt all diese Eigenschaften. Sie sind nicht über Prototyp mit Person verbunden. Wenn wir Person Um dies zu ändern:

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 wird das Update nicht recieve. Wenn wir pete.sayHello anrufen, gibt ti Hello! this is pete zurück. Es wird das neue Update nicht erhalten.

0

Um eine ordnungsgemäße Verwendung Javascript-Prototyp basierte Vererbung Sie fastClasshttps://github.com/dotnetwise/Javascript-FastClass

Sie haben die einfachere inheritWith Geschmack verwenden:

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 

Und wenn Sie sehr besorgt über die Leistung, dann haben Sie den fastClass Geschmack:

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, Ihr ursprünglicher Code macht keine Sinn, aber ich habe es buchstäblich respektiert.

fastClass Dienstprogramm:

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 Dienstprogramm:

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 Dienstprogramm:

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

[* Disclaimer: Ich bin der Autor des Open-Source-Paket und die Namen der Methoden selbst könnte in umbenannt werden future` *]

Verwandte Themen