Wie erstelle ich ein benutzerdefiniertes Objekt in JavaScript?


471

Ich frage mich, wie man am besten ein JavaScript-Objekt mit Eigenschaften und Methoden erstellt.

Ich habe Beispiele gesehen, bei denen die Person alle Funktionen verwendet var self = thisund dann verwendet self., um sicherzustellen, dass der Bereich immer korrekt ist.

Dann habe ich Beispiele für .prototypedas Hinzufügen von Eigenschaften gesehen, während andere dies inline tun.

Kann mir jemand ein geeignetes Beispiel für ein JavaScript-Objekt mit einigen Eigenschaften und Methoden geben?


13
Es gibt keinen "besten" Weg.
Triptychon

Ist das kein selfreserviertes Wort? Wenn nicht, sollte es sein; da selfist eine vordefinierte Variable, die sich auf das aktuelle Fenster bezieht. self === window
Shaz

2
@Shaz: Es ist kein reserviertes Wort mehr als andere Eigenschaften windowim Browser-Objektmodell wie documentoder frames; Sie können den Bezeichner sicherlich als Variablennamen wiederverwenden. Obwohl ich es stilistisch vorziehe var that= this, mögliche Verwirrung zu vermeiden. Obwohl window.selfes letztendlich sinnlos ist, gibt es selten einen Grund, es zu berühren.
Bobince

7
Wenn JS minimiert wird, reduziert das Zuweisen thiszu einer lokalen Variablen (z. B. self) die Dateigröße.
Patrick Fisher

Classjs neuer Link: github.com/divio/classjs
Nikola

Antworten:


889

Es gibt zwei Modelle zum Implementieren von Klassen und Instanzen in JavaScript: das Prototyping und das Schließen. Beide haben Vor- und Nachteile, und es gibt viele erweiterte Variationen. Viele Programmierer und Bibliotheken haben unterschiedliche Ansätze und Dienstprogramme zur Klassenhandhabung, um einige der hässlicheren Teile der Sprache zu dokumentieren.

Das Ergebnis ist, dass Sie in gemischter Gesellschaft eine Mischung aus Metaklassen haben, die sich alle leicht unterschiedlich verhalten. Was noch schlimmer ist, das meiste JavaScript-Tutorial-Material ist schrecklich und bietet eine Art Zwischenkompromiss, um alle Grundlagen abzudecken, was Sie sehr verwirrt. (Wahrscheinlich ist der Autor auch verwirrt. Das Objektmodell von JavaScript unterscheidet sich stark von den meisten Programmiersprachen und ist an vielen Stellen geradezu schlecht gestaltet.)

Beginnen wir mit dem Prototyp . Dies ist die JavaScript-native Version, die Sie erhalten können: Es gibt ein Minimum an Overhead-Code, und instanceof funktioniert mit Instanzen dieser Art von Objekten.

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

Wir können der erstellten Instanz Methoden hinzufügen, indem wir new Shapesie in die prototypeSuche dieser Konstruktorfunktion schreiben :

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

Nun zur Unterklasse, soweit Sie das nennen können, was JavaScript für die Unterklasse tut. Wir tun dies, indem wir diese seltsame magische prototypeEigenschaft vollständig ersetzen :

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

bevor Sie Methoden hinzufügen:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

Dieses Beispiel wird funktionieren und Sie werden Code wie diesen in vielen Tutorials sehen. Aber Mann, das new Shape()ist hässlich: Wir instanziieren die Basisklasse, obwohl keine tatsächliche Form erstellt werden soll. Es passiert Arbeit in diesem einfachen Fall , weil JavaScript so schlampig ist: es erlaubt Null Argumente wobei in diesem Fall übergeben, werden xund ywird undefinedund ist mit dem Prototyp des zugewiesen this.xundthis.y . Wenn die Konstruktorfunktion etwas Komplizierteres tun würde, würde sie flach auf das Gesicht fallen.

Wir müssen also einen Weg finden, ein Prototypobjekt zu erstellen, das die gewünschten Methoden und anderen Elemente auf Klassenebene enthält, ohne die Konstruktorfunktion der Basisklasse aufzurufen. Dazu müssen wir mit dem Schreiben des Hilfecodes beginnen. Dies ist der einfachste Ansatz, den ich kenne:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

Dadurch werden die Mitglieder der Basisklasse in ihrem Prototyp an eine neue Konstruktorfunktion übertragen, die nichts tut, und dann dieser Konstruktor verwendet. Jetzt können wir einfach schreiben:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

statt der new Shape()Ungerechtigkeit. Wir haben jetzt einen akzeptablen Satz von Grundelementen für erstellte Klassen.

Es gibt einige Verbesserungen und Erweiterungen, die wir bei diesem Modell berücksichtigen können. Zum Beispiel ist hier eine syntaktische Zuckerversion:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

Jede Version hat den Nachteil, dass die Konstruktorfunktion nicht vererbt werden kann, wie dies in vielen Sprachen der Fall ist. Selbst wenn Ihre Unterklasse dem Konstruktionsprozess nichts hinzufügt, muss sie daran denken, den Basiskonstruktor mit den von der Basis gewünschten Argumenten aufzurufen. Dies kann leicht automatisiert werden apply, aber Sie müssen immer noch schreiben:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

Eine übliche Erweiterung besteht also darin, das Initialisierungsmaterial in seine eigene Funktion und nicht in den Konstruktor selbst aufzuteilen. Diese Funktion kann dann ganz gut von der Basis erben:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

Jetzt haben wir für jede Klasse das gleiche Konstruktorfunktions-Boilerplate. Vielleicht können wir das in eine eigene Hilfsfunktion verschieben, damit wir es nicht weiter tippen müssen, anstatt es beispielsweise Function.prototype.subclassumzudrehen und die Funktion der Basisklasse Unterklassen ausspucken zu lassen:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

... was ein bisschen mehr wie andere Sprachen aussieht, wenn auch mit etwas ungeschickterer Syntax. Wenn Sie möchten, können Sie einige zusätzliche Funktionen hinzufügen. Vielleicht möchten Sie makeSubclasseinen Klassennamen nehmen und sich daran erinnern und einen Standard toStringverwenden, der ihn verwendet. Vielleicht möchten Sie den Konstruktor erkennen lassen, wenn er versehentlich ohne das aufgerufen wurdenew Operator (was sonst oft zu einem sehr nervigen Debugging führen würde):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

Vielleicht möchten Sie alle neuen Mitglieder übergeben und makeSubclasssie dem Prototyp hinzufügen, damit Sie Class.prototype...nicht so viel schreiben müssen. Viele Klassensysteme tun dies, z.

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

Es gibt viele potenzielle Merkmale, die Sie in einem Objektsystem für wünschenswert halten könnten, und niemand stimmt einer bestimmten Formel wirklich zu.


Der Verschlussweg also . Dies vermeidet die Probleme der prototypbasierten Vererbung von JavaScript, indem überhaupt keine Vererbung verwendet wird. Stattdessen:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

Jetzt hat jede einzelne Instanz von Shapeeine eigene Kopie dertoString Methode (und aller anderen Methoden oder anderen Klassenmitglieder, die wir hinzufügen).

Das Schlechte an jeder Instanz, die eine eigene Kopie jedes Klassenmitglieds hat, ist, dass sie weniger effizient ist. Wenn Sie mit einer großen Anzahl von Instanzen untergeordneter Klassen arbeiten, kann die prototypische Vererbung Ihnen besser dienen. Wie Sie sehen, ist es auch etwas ärgerlich, eine Methode der Basisklasse aufzurufen: Wir müssen uns daran erinnern, was die Methode war, bevor der Unterklassenkonstruktor sie überschrieb, sonst geht sie verloren.

[Auch weil hier keine Vererbung vorhanden ist, instanceoffunktioniert der Operator nicht. Sie müssten Ihren eigenen Mechanismus zum Schnüffeln von Klassen bereitstellen, wenn Sie ihn benötigen. Während Sie die Prototypobjekte auf ähnliche Weise wie bei der Vererbung von Prototypen fummeln könnten , ist es ein bisschen schwierig und es lohnt sich nicht wirklich, es nur zu bekommeninstanceof Arbeit zu gehen.]

Das Gute an jeder Instanz mit einer eigenen Methode ist, dass die Methode dann an die spezifische Instanz gebunden sein kann, der sie gehört. Dies ist nützlich, weil JavaScript auf seltsame Weise thisin Methodenaufrufen bindet. Dies hat zur Folge, dass, wenn Sie eine Methode von ihrem Eigentümer trennen:

var ts= mycircle.toString;
alert(ts());

dann wird thisinnerhalb der Methode nicht wie erwartet die Circle-Instanz sein (es wird tatsächlich das globale windowObjekt sein, was weit verbreitetes Debugging-Leid verursacht). In der Realität geschieht dies normalerweise, wenn eine Methode genommen und einem oder zugewiesen setTimeoutwirdonclickEventListener im Allgemeinen.

Bei der Prototyp-Methode müssen Sie für jede solche Aufgabe einen Abschluss hinzufügen:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

oder in Zukunft (oder jetzt, wenn Sie Function.prototype hacken) können Sie dies auch tun mit function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

Wenn Ihre Instanzen auf die Art des Schließens ausgeführt werden, erfolgt die Bindung kostenlos durch das Schließen über die Instanzvariable (normalerweise aufgerufen thatoder self, obwohl ich persönlich davon abraten würde, da diese selfbereits eine andere, andere Bedeutung in JavaScript hat). Sie erhalten die Argumente 1, 1im obigen Snippet jedoch nicht kostenlos, sodass Sie immer noch einen weiteren Abschluss benötigen oder einen, bind()wenn Sie dies tun müssen.

Auch bei der Verschlussmethode gibt es viele Varianten. Sie können es vorziehen, ganz wegzulassen this, ein neues zu erstellen thatund es zurückzugeben, anstatt den newOperator zu verwenden:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

Welcher Weg ist „richtig“? Beide. Welches das Beste ist"? Das hängt von Ihrer Situation ab. FWIW Ich tendiere zum Prototyping für echte JavaScript-Vererbung, wenn ich stark OO-Sachen mache, und zum Schließen für einfache Wegwerf-Seiteneffekte.

Beide Möglichkeiten sind für die meisten Programmierer jedoch nicht intuitiv. Beide haben viele mögliche unordentliche Variationen. Sie werden beide (sowie viele Zwischen- und allgemein fehlerhafte Schemata) treffen, wenn Sie den Code / die Bibliotheken anderer Personen verwenden. Es gibt keine allgemein akzeptierte Antwort. Willkommen in der wundervollen Welt der JavaScript-Objekte.

[Dies war Teil 94 von Warum JavaScript nicht meine Lieblingsprogrammiersprache ist.]


13
Sehr schöner schrittweiser Schritt von "Klasse" def zur Objektinstanziierung. Und nette Geste beim Umgehen new.
Crescent Fresh

8
Es scheint, dass JavaScript nicht Ihre Lieblingssprache ist, weil Sie es so verwenden möchten, als hätte es Klassen.
Jonathan Feinberg

59
Natürlich tue ich das auch, jeder: Das Klassen- und Instanzmodell ist das natürlichere für die Mehrheit der gängigen Probleme, mit denen Programmierer heute konfrontiert sind. Ich stimme zu, dass eine prototypbasierte Vererbung theoretisch möglicherweise eine flexiblere Arbeitsweise bieten kann, aber JavaScript hält dieses Versprechen überhaupt nicht ein. Sein klobiges Konstruktorfunktionssystem bietet uns das Schlimmste aus beiden Welten, was die klassenähnliche Vererbung erschwert und gleichzeitig keine der Flexibilität oder Einfachheit bietet, die Prototypen bieten könnten. Kurz gesagt, es ist poo.
Bobince

4
Bob, ich denke, das ist eine großartige Antwort - ich habe mich eine Weile mit diesen beiden Mustern auseinandergesetzt und ich denke, Sie haben etwas präziser als ein Resig codiert und mit mehr Einsicht erklärt als ein Crockford. Kein höheres Lob kann ich mir
James Westgate

4
Es schien mir immer, dass die grafische Darstellung klassischer Vererbungsparadigmen auf prototypische Sprachen wie Javascript ein quadratischer Stift und ein rundes Loch ist. Gibt es Zeiten, in denen dies wirklich notwendig ist, oder ist dies nur eine Möglichkeit für die Menschen, die Sprache so zu fisten, wie sie es möchten, anstatt die Sprache einfach so zu verwenden, wie sie ist?
SLF

90

Ich benutze dieses Muster ziemlich häufig - ich habe festgestellt, dass es mir ziemlich viel Flexibilität gibt, wenn ich es brauche. Im Gebrauch ähnelt es eher Klassen im Java-Stil.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

Dies verwendet eine anonyme Funktion, die beim Erstellen aufgerufen wird und eine neue Konstruktorfunktion zurückgibt. Da die anonyme Funktion nur einmal aufgerufen wird, können Sie darin private statische Variablen erstellen (sie befinden sich innerhalb des Abschlusses und sind für die anderen Mitglieder der Klasse sichtbar). Die Konstruktorfunktion ist im Grunde ein Standard-Javascript-Objekt - Sie definieren darin private Attribute und öffentliche Attribute werden an die thisVariable angehängt .

Grundsätzlich kombiniert dieser Ansatz den Crockfordschen Ansatz mit Standard-Javascript-Objekten, um eine leistungsfähigere Klasse zu erstellen.

Sie können es wie jedes andere Javascript-Objekt verwenden:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

4
Das sieht interessant aus, weil es ziemlich nahe an meinem "Heimrasen" liegt, der C # ist. Ich denke auch, dass ich anfange zu verstehen, warum privateStaticVariable wirklich privat ist (wie es im Rahmen einer Funktion definiert und am Leben erhalten wird, solange es Verweise darauf gibt?)
Michael Stum

thisMuss es trotzdem instanziiert werden, da es nicht verwendet wird new?
Jordan Parmer

Eigentlich this nicht erhalten in der verwendeten constructorFunktion, die wird Fooim Beispiel.
ShZ

4
Das Problem hierbei ist, dass jedes Objekt eine eigene Kopie aller privaten und öffentlichen Funktionen erhält.
virtualnobi

2
@virtualnobi: Dieses Muster hindert Sie nicht daran, Protytpe-Methoden zu schreiben : constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin

25

Douglas Crockford diskutiert dieses Thema ausführlich in The Good Parts . Er empfiehlt, den neuen Operator zu vermeiden , um neue Objekte zu erstellen. Stattdessen schlägt er vor, angepasste Konstruktoren zu erstellen. Zum Beispiel:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

In Javascript ist eine Funktion ein Objekt und kann verwendet werden, um Objekte zusammen mit dem neuen Operator zu erstellen . Konventionell beginnen Funktionen, die als Konstruktoren verwendet werden sollen, mit einem Großbuchstaben. Sie sehen oft Dinge wie:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

Falls Sie vergessen Sie verwenden neue Betreiber , während ein neues Objekt zu erhalten, was Sie ist eine ganz normale Funktion Anruf erhalten, und dies wird auf das globale Objekt stattdessen auf das neue Objekt gebunden.


5
Bin ich es oder denke ich, dass Crockford absolut keinen Sinn macht, wenn er den neuen Betreiber verprügelt?
Meder Omuraliev

3
@meder: Nicht nur du. Zumindest denke ich, dass mit dem neuen Operator nichts falsch ist. Und es gibt eine implizite newin var that = {};sowieso.
Tim Down

17
Crockford ist ein launischer alter Mann, und ich stimme ihm in vielen Punkten nicht zu, aber er fördert zumindest einen kritischen Blick auf JavaScript, und es lohnt sich, zuzuhören, was er zu sagen hat.
Bobince

2
@ Bobince: Einverstanden. Sein Schreiben über Verschlüsse hat mir vor ungefähr 5 Jahren die Augen für viele Dinge geöffnet, und er ermutigt zu einem nachdenklichen Ansatz.
Tim Down

20
Ich stimme Crockford zu. Das Problem mit dem neuen Operator ist, dass JavaScript den Kontext von "this" sehr unterschiedlich macht, als wenn sonst eine Funktion aufgerufen wird. Trotz der richtigen Fallkonvention gibt es Probleme, die bei größeren Codebasen auftreten, wenn Entwickler vergessen, neue zu verwenden, Groß- und Kleinschreibung zu verwenden usw. Um pragmatisch zu sein, können Sie alles, was Sie tun müssen, ohne das neue Schlüsselwort tun - warum also und? Weitere Fehlerquellen in den Code einführen? JS ist eine prototypische, nicht klassenbasierte Sprache. Warum soll es sich also wie eine statisch typisierte Sprache verhalten? Ich sicherlich nicht.
Joshua Ramirez

13

Um von Bobince's Antwort fortzufahren

In es6 können Sie jetzt tatsächlich eine erstellen class

Jetzt können Sie also Folgendes tun:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

Erweitern Sie also einen Kreis (wie in der anderen Antwort), den Sie tun können:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

Endet in es6 etwas sauberer und etwas leichter zu lesen.


Hier ist ein gutes Beispiel dafür in Aktion:


6

Sie können dies auch mithilfe von Strukturen tun:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

Dann :

var counter1 = createCounter();
counter1.increaseBy(4);

6
Ich mag das nicht, weil Leerzeichen wichtig sind. Das Curly nach der Rückkehr muss aus Gründen der Cross-Browser-Kompatibilität in derselben Zeile stehen.
Geowa4

5

Ein anderer Weg wäre http://jsfiddle.net/nnUY4/ (ich weiß nicht, ob diese Art der Handhabung der Objekterstellung und der Enthüllungsfunktionen einem bestimmten Muster folgt)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

4

Wenn man den Trick verwendet, während eines Konstruktoraufrufs "this" zu schließen, dient dies dazu, eine Funktion zu schreiben, die von einem anderen Objekt als Rückruf verwendet werden kann, das keine Methode für ein Objekt aufrufen möchte. Es hat nichts mit "den Umfang korrekt machen" zu tun.

Hier ist ein Vanille-JavaScript-Objekt:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Wenn Sie lesen, was Douglas Crockford über JavaScript zu sagen hat, können Sie viel davon lesen . John Resig ist auch brillant. Viel Glück!


1
Äh, das Herumschließen thishat alles damit zu tun, "den Umfang korrekt zu machen".
Roatin Marth

3
Jonathan hat recht. Der Umfang einer js-Funktion ist das, wofür Sie sie entwerfen. Das Selbst = dieser Trick ist eine Möglichkeit, ihn an eine bestimmte Instanz zu binden, damit er sich nicht ändert, wenn er in einem anderen Kontext aufgerufen wird. Aber manchmal ist es das, was Sie tatsächlich wollen. Kommt auf den Kontext an.
Marco

Ich denke, Sie sagen alle dasselbe. self=thisObwohl es nicht zwingt, thiszu bestehen, ermöglicht es leicht ein "korrektes" Scoping über einen Verschluss.
Crescent Fresh

2
Der Grund, warum Sie dies tun, besteht darin, verschachtelten Funktionen Zugriff auf den Bereich zu gewähren, der in der Konstruktorfunktion vorhanden ist. Wenn sich verschachtelte Funktionen in Konstruktorfunktionen befinden, wird der Bereich "this" auf den globalen Bereich zurückgesetzt.
Joshua Ramirez

4

Closureist vielseitig. bobince hat die Ansätze von Prototyp und Verschluss beim Erstellen von Objekten gut zusammengefasst . Sie können jedoch einige Aspekte der OOPVerwendung von Closure in einer funktionalen Programmierweise nachahmen . Denken Sie daran, dass Funktionen Objekte in JavaScript sind . Verwenden Sie die Funktion also anders als Objekt.

Hier ist ein Beispiel für die Schließung:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

Vor einiger Zeit bin ich auf den Artikel von Mozilla über Closure gestoßen. Folgendes springt mir in die Augen: "Mit einem Abschluss können Sie einige Daten (die Umgebung) einer Funktion zuordnen, die diese Daten verarbeitet. Dies weist offensichtliche Parallelen zur objektorientierten Programmierung auf, bei der Objekte es uns ermöglichen, einige Daten (die Eigenschaften des Objekts) zuzuordnen ) mit einer oder mehreren Methoden ". Es war das erste Mal, dass ich eine Parallelität zwischen Verschluss und klassischem OOP ohne Bezug zum Prototyp las.

Wie?

Angenommen, Sie möchten die Mehrwertsteuer einiger Artikel berechnen. Die Mehrwertsteuer bleibt wahrscheinlich während der Laufzeit eines Antrags stabil. Eine Möglichkeit, dies in OOP (Pseudocode) zu tun:

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

Grundsätzlich übergeben Sie einen Mehrwertsteuerwert an Ihren Konstruktor und Ihre Berechnungsmethode kann ihn über die Schließung bearbeiten . Anstatt jetzt eine Klasse / einen Konstruktor zu verwenden, übergeben Sie Ihre Mehrwertsteuer als Argument an eine Funktion. Da Sie nur an der Berechnung selbst interessiert sind, wird eine neue Funktion zurückgegeben, bei der es sich um die Berechnungsmethode handelt:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

Identifizieren Sie in Ihrem Projekt Werte der obersten Ebene, die ein guter Kandidat für die Berechnung der Mehrwertsteuer sind. Als Faustregel gilt, wenn Sie dieselben Argumente immer wieder weitergeben, gibt es eine Möglichkeit, sie mithilfe von Closure zu verbessern. Keine Notwendigkeit, traditionelle Objekte zu erstellen.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures


3

Ein Objekt erstellen

Der einfachste Weg, ein Objekt in JavaScript zu erstellen, ist die Verwendung der folgenden Syntax:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

Dies eignet sich hervorragend zum strukturierten Speichern von Daten.

Bei komplexeren Anwendungsfällen ist es jedoch häufig besser, Instanzen von Funktionen zu erstellen:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Auf diese Weise können Sie mehrere Objekte erstellen, die denselben "Entwurf" verwenden, ähnlich wie Sie Klassen in z. Java.

Dies kann jedoch mithilfe eines Prototyps noch effizienter durchgeführt werden.

Wenn verschiedene Instanzen einer Funktion dieselben Methoden oder Eigenschaften verwenden, können Sie sie in den Prototyp dieses Objekts verschieben. Auf diese Weise hat jede Instanz einer Funktion Zugriff auf diese Methode oder Eigenschaft, muss jedoch nicht für jede Instanz dupliziert werden.

In unserem Fall ist es sinnvoll, die Methode fauf den Prototyp zu verschieben:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Erbe

Eine einfache, aber effektive Methode zur Vererbung in JavaScript ist die Verwendung des folgenden Zweiliners:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Das ist ähnlich wie folgt:

B.prototype = new A();

Der Hauptunterschied zwischen beiden besteht darin, dass der Konstruktor von Abei Verwendung nicht ausgeführt wird Object.create, was intuitiver und der klassenbasierten Vererbung ähnlicher ist.

Sie können jederzeit festlegen, dass der Konstruktor von optional ausgeführt werden soll, Awenn Sie eine neue Instanz von erstellen B, indem Sie ihn dem Konstruktor hinzufügen von B:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

Wenn Sie alle Argumente von Ban übergeben möchten A, können Sie auch Folgendes verwenden Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

Wenn Sie ein anderes Objekt in den Konstruktor Kette mixin wollen B, können Sie kombinieren Object.createmit Object.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

Demo

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


Hinweis

Object.createkann sicher in jedem modernen Browser verwendet werden, einschließlich IE9 +. Object.assignfunktioniert weder in einer IE-Version noch in einigen mobilen Browsern. Es wird empfohlen, Polyfill Object.create und / oder Object.assignwenn Sie sie verwenden und Browser unterstützen möchten, die sie nicht implementieren.

Eine Polyfüllung finden Sie Object.create hier und eine Object.assign hier .


0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);

4
Was trägt dazu zu den zahlreichen bereits angebotenen umfangreichen Antworten bei?
Blm

Ich mag diese Antwort, da sie kurz ist und die drei Teile der Implementierung zeigt: 1) Definieren Sie das Objekt, 2) Instanziieren Sie eine Instanz des Objekts, 3) Verwenden Sie die Instanz - sie zeigt alles auf einen Blick, anstatt die Analyse durchzuführen durch all die ausführlichen Antworten oben (die natürlich alle extrem gute Antworten mit all den relevanten Details sind, die man sich wünschen würde) - eine Art einfache Zusammenfassung hier
G-Man

0

Zusätzlich zu der akzeptierten Antwort aus dem Jahr 2009. Wenn Sie moderne Browser ansprechen können, können Sie die Object.defineProperty verwenden .

Die Object.defineProperty () -Methode definiert eine neue Eigenschaft direkt für ein Objekt oder ändert eine vorhandene Eigenschaft für ein Objekt und gibt das Objekt zurück. Quelle: Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

Eine Liste der Desktop- und Mobilkompatibilität finden Sie in der Browserkompatibilitätsliste von Mozilla . Ja, IE9 + unterstützt es ebenso wie Safari Mobile.


0

Sie können dies auch versuchen

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();

0
Ein Muster, das mir gut dient
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

Erstens können Sie Ihre Präferenz ändern Methoden hinzufügen , anstatt den Konstruktor der auf die Instanz - prototypeObjekt . Ich deklariere fast immer Methoden innerhalb des Konstruktors, weil ich Constructor Hijacking verwende sehr häufig für Zwecke in Bezug auf Vererbung und Dekoratoren verwende.

So entscheide ich, wo welche Erklärungen geschrieben werden:

  • Deklarieren Sie niemals eine Methode direkt im Kontextobjekt ( this)
  • Lassen Sie varErklärungen Vorrang vor functionErklärungen haben
  • Lassen Sie Primitive Vorrang vor Objekten haben ( {}und [])
  • Lassen Sie publicErklärungen Vorrang vor privateErklärungen haben
  • Bevorzugen Function.prototype.bindüber thus, self, vm,etc
  • Vermeiden Sie es, eine Klasse innerhalb einer anderen Klasse zu deklarieren, es sei denn:
    • Es sollte offensichtlich sein, dass die beiden untrennbar miteinander verbunden sind
    • Die innere Klasse implementiert das Befehlsmuster
    • Die innere Klasse implementiert das Singleton-Muster
    • Die innere Klasse implementiert das Zustandsmuster
    • Die innere Klasse implementiert ein anderes Entwurfsmuster, das dies rechtfertigt
  • Kehren Sie immer thisaus dem Lexikalischen Bereich des Sperrraums zurück.

Hier ist, warum diese helfen:

Konstrukteur entführt
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Modelldesign
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
Designmuster
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

Wie Sie sehen können, brauche ich das wirklich nicht, thusda ich es vorziehe Function.prototype.bind(oder .calloder .apply)thus . In unserer SingletonKlasse nennen wir es nicht einmal, thusweil es INSTANCEmehr Informationen vermittelt. Denn Modelwir kehren zurück, thisdamit wir den Konstruktor aufrufen können, indem .callwir die Instanz zurückgeben, die wir an ihn übergeben haben. Redundant haben wir es der Variablen zugewiesen updated, obwohl es in anderen Szenarien nützlich ist.

Außerdem ziehe ich es vor, Objektliterale mit dem newSchlüsselwort gegenüber {Klammern} zu erstellen:

Bevorzugt
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Nicht bevorzugt
var klass = Super.apply({
    override: x
});

Wie Sie sehen können, kann letzterer die "Override" -Eigenschaft seiner Superklasse nicht überschreiben.

Wenn ich dem prototypeObjekt der Klasse Methoden hinzufüge , bevorzuge ich ein Objektliteral - mit oder ohne Verwendung des newSchlüsselworts:

Bevorzugt
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
Nicht bevorzugt
Klass.prototype.method = function m() {...};

0

Ich möchte erwähnen, dass wir entweder einen Titel oder einen String verwenden können , um ein Objekt zu deklarieren.
Es gibt verschiedene Möglichkeiten, jeden Typ von ihnen aufzurufen. Siehe unten:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));


-1

Grundsätzlich gibt es in JS kein Klassenkonzept, daher verwenden wir die Funktion als Klassenkonstruktor, der für die vorhandenen Entwurfsmuster relevant ist.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

Bis jetzt hat JS keine Ahnung, dass Sie ein Objekt erstellen möchten. Hier kommt das neue Schlüsselwort.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ref: Professionelles JS für Webentwickler - Nik Z.


Ablehnung angenommen: Mit einem gültigen Grund wäre informativer gewesen und hätte eine Gelegenheit zur Verbesserung geboten.
Airwind711

Es gibt das Konzept eines classin JS, wie Sie es in Ihrer Überschrift mit dem functionSchlüsselwort erwähnt haben. Es ist kein Entwurfsmuster, sondern ein beabsichtigtes Merkmal der Sprache. Ich habe Sie nicht abgelehnt, aber es sieht so aus, als hätte es jemand anderes getan, weil die Frage knapp und fast irrelevant ist. Hoffe dieses Feedback hilft.
Cody
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.