wake-up-neo.com

JavaScript-Klassen

Ich verstehe grundlegende JavaScript-Pseudoklassen:

function Foo(bar) {
    this._bar = bar;
}

Foo.prototype.getBar = function() {
    return this._bar;
};

var foo = new Foo('bar');
alert(foo.getBar()); // 'bar'
alert(foo._bar); // 'bar'

Ich verstehe auch das Modulmuster, das die Kapselung emulieren kann:

var Foo = (function() {
    var _bar;

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
})();

Foo.setBar('bar');
alert(Foo.getBar()); // 'bar'
alert(Foo._bar); // undefined

Es gibt jedoch für beide Muster nicht-OOP-ähnliche Eigenschaften. Ersteres bietet keine Kapselung. Letzteres bietet keine Instantiierung. Beide Muster können geändert werden, um Pseudo-Vererbung zu unterstützen.

Was ich gerne wissen würde, ist, wenn es ein Muster gibt, das Folgendes zulässt:

  • Erbe
  • Encapsulation (Unterstützung für "private" Eigenschaften/Methoden)
  • Instantiation (kann mehrere Instanzen der "Klasse" haben, jede mit eigenem Status)
45
FtDRbwLXw6

was ist damit 

var Foo = (function() {
    // "private" variables 
    var _bar;

    // constructor
    function Foo() {};

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return _bar;
    };
    Foo.prototype.setBar = function(bar) {
        _bar = bar;
    };

    return Foo;
})();

Und jetzt haben wir Instantiierung, Kapselung und Vererbung.
Aber es gibt immer noch ein Problem. Die Variable private ist static, da sie für alle Instanzen von Foo freigegeben ist. Schnelle Demo: 

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'b' :(    

Ein besserer Ansatz könnte die Verwendung von Konventionen für die privaten Variablen sein: Jede private Variable sollte mit einem Unterstrich beginnen. Diese Konvention ist allgemein bekannt und wird häufig verwendet. Wenn ein anderer Programmierer Ihren Code verwendet oder ändert und eine Variable mit Unterstrich beginnt, wird er wissen, dass er privat ist und nur für den internen Gebrauch gedacht ist.
Hier ist das Umschreiben mit dieser Konvention: 

var Foo = (function() {
    // constructor
    function Foo() {
        this._bar = "some value";
    };

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return this._bar;
    };
    Foo.prototype.setBar = function(bar) {
        this._bar = bar;
    };

    return Foo;
})();

Jetzt haben wir Instanziierung, Vererbung, aber wir haben unsere Kapselung zugunsten von Konventionen verloren: 

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'a' :) 
alert(b.getBar()); // alerts 'b' :) 

aber die privaten vars sind zugänglich: 

delete a._bar;
b._bar = null;
alert(a.getBar()); // alerts undefined :(
alert(b.getBar()); // alerts null :(
72
gion_13
7
Joe Davis

Schließungen sind dein Freund!

Fügen Sie einfach die folgende kleine Funktion zu Ihrem Top-Level-Namespace hinzu und schon können Sie mit OOP fertig werden

  • kapselung mit statischen und Instanzvariablen, privaten und öffentlichen Variablen und Methoden 
  • erbe
  • klasse-Level-Injection (zB für Singleton-Dienste)
  • keine Einschränkungen, kein Rahmen, nur altes Javascript

function clazz(_class, _super) {
    var _prototype = Object.create((_super || function() {}).prototype);
    var _deps = Array.isArray(_class) ? _class : [_class]; _class = _deps.pop();
    _deps.Push(_super);
    _prototype.constructor = _class.apply(_prototype, _deps) || _prototype.constructor;
    _prototype.constructor.prototype = _prototype;
    return _prototype.constructor;
}

Die obige Funktion verbindet einfach den Prototyp der angegebenen Klasse und den übergeordneten Konstruktor und gibt den resultierenden Konstruktor für die Instantiierung zurück.

Nun können Sie Ihre Basisklassen (dh, die die Erweiterung {} haben) ganz natürlich in wenigen Codezeilen deklarieren, einschließlich statischer, Instanz-, öffentlicher und privater Eigenschaften und Methoden:

MyBaseClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions declared here are private static variables and methods
    // properties of 'this' declared here are public static variables and methods
    return function MyBaseClass(arg1, ...) { // or: this.constructor = function(arg1, ...) {
        // local variables and functions declared here are private instance variables and methods
        // properties of 'this' declared here are public instance variables and methods
    };
});

Eine Klasse erweitern Umso natürlicher auch:

MySubClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions are private static variables and methods
    // properties of this are public static variables and methods
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        // local variables and functions are private instance variables and methods
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // properties of 'this' are public instance variables and methods
    };
}, MyBaseClass); // extend MyBaseClass

Mit anderen Worten, übergeben Sie den Konstruktor der übergeordneten Klasse an die clazz-Funktion und fügen Sie _super.call(this, arg1, ...) dem Konstruktor der untergeordneten Klasse hinzu, der den Konstruktor der übergeordneten Klasse mit den erforderlichen Argumenten aufruft. Wie bei jedem Standardvererbungsschema muss der Aufruf des übergeordneten Konstruktors im untergeordneten Konstruktor an erster Stelle stehen.

Beachten Sie, dass Sie den Konstruktor entweder explizit mit this.constructor = function(arg1, ...) {...} oder this.constructor = function MyBaseClass(arg1, ...) {...} benennen können, wenn Sie vom Code innerhalb des Konstruktors einfachen Zugriff auf den Konstruktor benötigen, oder den Konstruktor einfach mit return function MyBaseClass(arg1, ...) {...} wie im obigen Code zurückgeben. Mit was auch immer Sie sich am wohlsten fühlen.

Instanziieren Sie einfach Objekte aus solchen Klassen, wie Sie es normalerweise von einem Konstruktor tun würden: myObj = new MyBaseClass();

Beachten Sie, wie Schließungen die gesamte Funktionalität einer Klasse, einschließlich ihres Prototyps und Konstruktors, gut einkapseln und einen natürlichen Namespace für statische und Instanz-, private und öffentliche Eigenschaften und Methoden bereitstellen. Der Code innerhalb eines Klassenabschlusses ist völlig frei von Einschränkungen. Kein Rahmen, keine Einschränkungen, nur altes Javascript. Schließungsregel

Oh, und wenn Sie Singleton-Abhängigkeiten (z. B. Services) in Ihre Klasse einfügen möchten (z. B. Prototyp), wird clazz dies für Sie à la AngularJS tun:

DependentClass = clazz([aService, function(_service, _super) { // class closure, 'this' is the prototype
    // the injected _service dependency is available anywhere in this class
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // the injected _service dependency is also available in the constructor
    };
}], MyBaseClass); // extend MyBaseClass

Wie der obige Code zu veranschaulichen versucht, platzieren Sie zum Einfügen von Singletons in eine Klasse einfach den Klassenabschluss als letzten Eintrag in einem Array mit all seinen Abhängigkeiten. Fügen Sie die entsprechenden Parameter vor dem _super-Parameter und in derselben Reihenfolge wie im Array zum Klassenschluss hinzu. clazz fügt die Abhängigkeiten aus dem Array als Argumente in die Klassenschließung ein. Die Abhängigkeiten sind dann innerhalb des Klassenabschlusses verfügbar, einschließlich des Konstruktors.

Da die Abhängigkeiten in den Prototyp eingefügt werden, stehen sie statischen Methoden sogar zur Verfügung, noch bevor ein Objekt aus der Klasse instanziiert wird. Dies ist sehr nützlich für die Verkabelung Ihrer Apps oder Geräte und für durchgehende Tests. Außerdem müssen keine Singletons in Konstruktoren eingefügt werden, wodurch der Code des Konstruktors sonst unnötig blockiert wird.

Überprüfen Sie diese Geige: http://jsfiddle.net/5uzmyvdq/1/

Feedback und Vorschläge sind herzlich willkommen!

4
Stephane Catala

Javascript ist sicherlich OOP. Sie haben immer Polymorphismus, jedoch müssen Sie entweder die Einkapselung oder die Instantiation opfern, was das Problem ist, dem Sie begegnet sind.

Versuchen Sie dies nur, um Ihre Optionen aufzufrischen . http://www.webmonkey.com/2010/02/make_oop_classes_in_javascript/ Auch eine alte Frage, die ich als Lesezeichen markiert hatte:. Ist JavaScript objektorientiert?

3
user1701047

JavaScript-Klassen werden in ECMAScript 6 eingeführt und sind syntaktischer Zuwachs gegenüber der vorhandenen prototypbasierten Vererbung von JavaScript. Die Klassensyntax führt kein neues objektorientiertes Vererbungsmodell für JavaScript ein. JavaScript-Klassen bieten eine viel einfachere und klarere Syntax zum Erstellen von Objekten und zum Umgang mit Vererbung.

Sie können mehr in diesem Link sehen Mozilla Community

Github

1
user1938455

Ein Problem bei vielen JS-Klassen ist, dass sie ihre Felder und Methoden nicht sichern, was bedeutet, dass jeder, der sie verwendet, versehentlich eine Methode ersetzt. Zum Beispiel der Code:

function Class(){
    var name="Luis";
    var lName="Potter";
}

Class.prototype.changeName=function(){
    this.name="BOSS";
    console.log(this.name);
};

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced");
};
test.changeName();
test.changeName();

wird ausgeben:

ugly
BOSS
replaced 
replaced 

Wie Sie sehen, wird die changeName-Funktion überschrieben. Der folgende Code würde die Klassenmethoden und -felder sichern, und die Getter und Setter würden verwendet, um auf sie zuzugreifen, wodurch diese eher einer "regulären" Klasse in anderen Sprachen entspricht.

function Class(){
    var name="Luis";
    var lName="Potter";

    function getName(){
         console.log("called getter"); 
         return name;
    };

    function setName(val){
         console.log("called setter"); 
         name = val
    };

    function getLName(){
         return lName
    };

    function setLName(val){
        lName = val;
    };

    Object.defineProperties(this,{
        name:{
            get:getName, 
            set:setName, 
            enumerable:true, 
            configurable:false
        },
        lastName:{
            get:getLName, 
            set:setLName, 
            enumerable:true, 
            configurable:false
        }
    });
}

Class.prototype.changeName=function(){
    this.name="BOSS";
};   

Object.defineProperty(Class.prototype, "changeName", {
    writable:false, 
    configurable:false
});

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced")
};
test.changeName();
test.changeName();

Dies gibt aus:

called getter
Luis
called setter 
called getter 
ugly 
called setter 
called setter 
called setter 

Nun können Ihre Klassenmethoden nicht durch zufällige Werte oder Funktionen ersetzt werden, und der Code in den Get- und Setzern wird immer ausgeführt, wenn Sie versuchen, ein Feld zu lesen oder zu schreiben.

0
Piacenti

Ich habe kürzlich über dieses spezielle Thema nachgedacht und die Grenzen der verschiedenen Ansätze. Die beste Lösung, die ich finden konnte, ist unten. 

Es scheint die Probleme mit Vererbung, Instantiierung und Ecapsulation zu lösen (zumindest bei Tests in Google Chrome V.24), obwohl dies möglicherweise zu Lasten der Speichernutzung geht.

function ParentClass(instanceProperty) {
  // private
  var _super = Object.create(null),
      privateProperty = "private " + instanceProperty;
  // public
  var api = Object.create(_super);
  api.constructor = this.constructor;
  api.publicMethod = function() {
     console.log( "publicMethod on ParentClass" );
     console.log( privateProperty );
  };
  api.publicMethod2 = function() {
     console.log( "publicMethod2 on ParentClass" );
     console.log( privateProperty );
  };
  return api;
}

function SubClass(instanceProperty) {
    // private
    var _super = ParentClass.call( this, instanceProperty ),
        privateProperty = "private sub " + instanceProperty;
    // public
    var api = Object.create(_super);
    api.constructor = this.constructor;
    api.publicMethod = function() {
       _super.publicMethod.call(this); // call method on ParentClass
        console.log( "publicMethod on SubClass" );
        console.log( privateProperty );
    }
    return api;
}

var par1 = new ParentClass(0),
    par2 = new ParentClass(1),
    sub1 = new SubClass(2),
    sub2 = new SubClass(3);

par1.publicMethod();
par2.publicMethod();
sub1.publicMethod();
sub2.publicMethod();
par1.publicMethod2();
par2.publicMethod2();
sub1.publicMethod2();
sub2.publicMethod2();
0
BrettJephson