Frage 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

Aber es gibt un-OOP-ähnliche Eigenschaften für beide Muster. Erstere bietet keine Verkapselung. Letzteres bietet keine Instanziierung. Beide Muster können modifiziert werden, um Pseudo-Vererbung zu unterstützen.

Was ich gerne wissen würde, ist, ob es ein Muster gibt, das erlaubt:

  • Erbe
  • Kapselung (Unterstützung für "private" Eigenschaften / Methoden)
  • Instanziierung (kann mehrere Instanzen der "Klasse" haben, jede mit einem eigenen Status)

44
2017-09-26 21:05


Ursprung


Antworten:


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 Instanziierung, Verkapselung und Vererbung.
Aber es gibt immer noch ein Problem. Das private Variable ist static weil es in allen Instanzen geteilt wird Foo. 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 weit verbreitet. Wenn also ein anderer Programmierer Ihren Code verwendet oder ändert und eine Variable mit Unterstrich beginnt, weiß er, dass sie privat ist und nur intern verwendet wird.
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 Verkapselung 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 :(

71
2017-09-26 21:16



Ich denke, wonach du suchst, ist das "Enthüllungsprototypmuster".

Dan Wahlin hat einen tollen Blogeintrag: http://weblogs.asp.net/dwahlin/archive/2011/08/03/techniques-strategies-and-patterns-for-strukturing-javascript-code-revealing-prototype-pattern.aspx

und noch besser Pluralsight-Kurs zu dieser und anderen verwandten JavaScript-Strukturen: http://pluralalsight.com/training/courses/TableOfContents?courseName=structuring-javascript&highlight=dan-wahlin_structuring-javascript-module1!dan-wahlin_structuring-javascript-module2!dan-wahlin_structuring-javascript-module5!dan-wahlin_structuring-javascript- module4! dan-wahlin_strukturieren-javascript-modul3 # strukturieren-javascript-modul1


7
2017-09-26 21:13



Verschlüsse sind dein Freund!

Fügen Sie Ihrem Namespace auf oberster Ebene einfach die folgende kleine Funktion hinzu und Sie sind bereit für OOP, komplett mit

  • Verkapselung mit statischen und Instanz-, privaten und öffentlichen Variablen und Methoden
  • Erbe
  • Injektion auf Klassenebene (z. B. für Singleton-Dienste)
  • keine Einschränkungen, kein Framework, einfach 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 verdrahtet einfach den Prototyp und den eventuellen Elternkonstruktor der gegebenen Klasse und gibt den resultierenden Konstruktor zur Instanziierung zurück.

Jetzt können Sie natürlich Ihre Basisklassen deklarieren (dh. Extend {}) in ein paar Zeilen Code, komplett mit statischen, Instanz, öffentlichen und privaten 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:

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

Übergeben Sie den übergeordneten Klassenkonstruktor mit anderen Worten an die clazz-Funktion und fügen Sie ihn hinzu _super.call(this, arg1, ...)zum Konstruktor der Kindklasse, der den Konstruktor der Elternklasse mit den erforderlichen Argumenten aufruft. Wie bei jedem Standardvererbungsschema muss der Aufruf des übergeordneten Konstruktors im Kindkonstruktor an erster Stelle stehen.

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

Implementieren Sie einfach Objekte aus solchen Klassen wie normalerweise von einem Konstruktor: myObj = new MyBaseClass();

Beachten Sie, dass Closures die gesamte Funktionalität einer Klasse, einschließlich des Prototyps und des Konstruktors, gut kapseln und einen natürlichen Namespace für statische und Instanz-, private und öffentliche Eigenschaften und Methoden bereitstellen. Der Code innerhalb einer Klassenschließung ist völlig frei von Einschränkungen. Kein Framework, keine Einschränkungen, einfach nur altes Javascript. Verschlüsse regeln!

Oh, und wenn du Singleton-Abhängigkeiten (zB Services) in deine Klasse einspeisen willst (zB Prototyp), clazz mache das für dich à la AngularJS:

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, plazieren Sie Singletons in eine Klasse, indem Sie einfach den Klassenabschluss als letzten Eintrag in ein Array mit all seinen Abhängigkeiten platzieren. Fügen Sie auch die entsprechenden Parameter zum Klassenabschluss vor dem _super Parameter und in der gleichen Reihenfolge wie im Array. clazz wird die Abhängigkeiten vom Array als Argumente in die Klassenschließung einfügen. Die Abhängigkeiten sind dann überall innerhalb der Klassenschließung verfügbar, einschließlich des Konstruktors.

Da die Abhängigkeiten in den Prototyp injiziert werden, sind sie sogar für statische Methoden verfügbar, noch bevor ein Objekt aus der Klasse instanziiert wird. Dies ist sehr nützlich für die Verkabelung Ihrer Apps oder Unit- und End-to-End-Tests. Es beseitigt auch die Notwendigkeit, Singletons in Konstruktoren zu injizieren, die ansonsten den Code des Konstruktors unnötigerweise überdecken.

Prüfe diese Geige: http://jsfiddle.net/5uzmyvdq/1/

Feedback und Anregungen sind herzlich willkommen!


4
2018-03-22 09:58



Javascript ist sicherlich OOP. Sie haben immer Polymorphie, aber Sie müssen entweder Verkapselung oder Instanziierung opfern, was das Problem ist, in das Sie geraten sind.

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


3
2017-09-26 21:12



JavaScript-Klassen werden in ECMAScript 6 eingeführt und sind syntaktischer Zucker gegenüber der bestehenden 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-Gemeinschaft

Github


1
2018-01-29 14:46



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

Es scheint die Probleme mit Vererbung, Instanziierung und Verkapselung zu lösen (zumindest aus Tests mit Google Chrome v.24), obwohl es wahrscheinlich 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
2018-02-19 13:21



Ein Problem mit vielen JS-Klassen besteht darin, dass sie ihre Felder und Methoden nicht sichern, was bedeutet, dass jemand, der sie benutzt, 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 ausgegeben:

ugly
BOSS
replaced 
replaced 

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

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

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 Gettern und Sätzen wird immer ausgeführt, wenn versucht wird, in das Feld zu lesen oder zu schreiben.


0
2017-09-04 22:41