Klasse vs. statische Methode in JavaScript

261

Ich weiß, dass das funktionieren wird:

function Foo() {};
Foo.prototype.talk = function () {
    alert('hello~\n');
};

var a = new Foo;
a.talk(); // 'hello~\n'

Aber wenn ich anrufen will

Foo.talk() // this will not work
Foo.prototype.talk() // this works correctly

Ich finde einige Methoden, um Foo.talkArbeit zu machen ,

  1. Foo.__proto__ = Foo.prototype
  2. Foo.talk = Foo.prototype.talk

Gibt es andere Möglichkeiten, dies zu tun? Ich weiß nicht, ob es richtig ist. Verwenden Sie Klassenmethoden oder statische Methoden in Ihrem JavaScript-Code?

lostyzd
quelle
14
Foo.talk = function ...
Vorzeitige Optimierung
1
@downvoterstepintothelight Das Foo.walk = function() {}wirkt sich nicht auf seine Instanzen aus, da es sich nicht in der Prototypenkette befindet. Gibt es eine browserübergreifende Methode, um die Funktion einer Funktion [[prototype]]hervorzuheben prototype?
lostyzd
3
Wahrscheinlich habe ich keine Ahnung, was Sie wollen, da Klassenmethoden per Definition keine Auswirkungen auf Instanzen haben .
Vorzeitige Optimierung
@downvoterstepintothelight Ich bezweifle, dass eine Methode in einer Sprache wie Python eine Klassenmethode aufrufen kann. Der Unterschied besteht im thisZeiger.
lostyzd

Antworten:

409

Denken Sie zunächst daran, dass JavaScript in erster Linie eine prototypische Sprache und keine klassenbasierte Sprache ist 1 . Fooist keine Klasse, es ist eine Funktion, die ein Objekt ist. Sie können ein Objekt aus dieser Funktion mithilfe des newSchlüsselworts instanziieren , mit dem Sie etwas Ähnliches wie eine Klasse in einer Standard-OOP-Sprache erstellen können.

Ich würde vorschlagen, die __proto__meiste Zeit zu ignorieren, da es keine browserübergreifende Unterstützung bietet, und sich stattdessen darauf zu konzentrieren, zu lernen, wie es prototypefunktioniert.

Wenn Sie eine Instanz eines Objekts aus einer Funktion 2 erstellt haben und auf eines ihrer Elemente (Methoden, Attribute, Eigenschaften, Konstanten usw.) zugreifen, fließt der Zugriff durch die Prototyphierarchie, bis entweder (a) das gefunden wird Mitglied, oder (b) findet keinen anderen Prototyp.

Die Hierarchie beginnt mit dem aufgerufenen Objekt und durchsucht dann das Prototypobjekt. Wenn das Prototypobjekt einen Prototyp hat, wird es wiederholt, wenn kein Prototyp vorhanden undefinedist, zurückgegeben.

Beispielsweise:

foo = {bar: 'baz'};
console.log(foo.bar); // logs "baz"

foo = {};
console.log(foo.bar); // logs undefined

function Foo(){}
Foo.prototype = {bar: 'baz'};
f = new Foo();
console.log(f.bar);
// logs "baz" because the object f doesn't have an attribute "bar"
// so it checks the prototype
f.bar = 'buzz';
console.log( f.bar ); // logs "buzz" because f has an attribute "bar" set

Es sieht für mich so aus, als hätten Sie diese "grundlegenden" Teile zumindest schon ein wenig verstanden, aber ich muss sie explizit machen, nur um sicherzugehen.

In JavaScript ist alles ein Objekt 3 .

Alles ist ein Objekt.

function Foo(){}definiert nicht nur eine neue Funktion, sondern ein neues Funktionsobjekt, auf das mit zugegriffen werden kann Foo.

Aus diesem Grund können Sie mit auf Fooden Prototyp zugreifen Foo.prototype.

Was Sie auch tun wird mehr Funktionen auf Foo:

Foo.talk = function () {
  alert('hello world!');
};

Auf diese neue Funktion kann zugegriffen werden mit:

Foo.talk();

Ich hoffe, dass Sie jetzt eine Ähnlichkeit zwischen Funktionen eines Funktionsobjekts und einer statischen Methode feststellen.

Stellen Sie sich vor f = new Foo();, Sie erstellen eine Klasseninstanz, Foo.prototype.bar = function(){...}definieren eine gemeinsame Methode für die Klasse und Foo.baz = function(){...}definieren eine öffentliche statische Methode für die Klasse.


ECMAScript 2015 führte eine Vielzahl von syntaktischen Zuckern für diese Art von Deklarationen ein, um deren Implementierung zu vereinfachen und gleichzeitig die Lesbarkeit zu verbessern. Das vorherige Beispiel kann daher wie folgt geschrieben werden:

class Foo {
  bar() {...}

  static baz() {...}
}

was erlaubt bar, als bezeichnet zu werden:

const f = new Foo()
f.bar()

und bazgenannt werden als:

Foo.baz()

1: classwar ein "Future Reserved Word" in der ECMAScript 5-Spezifikation , aber ES6 bietet die Möglichkeit, Klassen mithilfe des classSchlüsselworts zu definieren .

2: Im Wesentlichen eine Klasseninstanz, die von einem Konstruktor erstellt wurde, aber es gibt viele nuancierte Unterschiede, die ich Sie nicht irreführen möchte

3: Primitive Werte , zu denen Boolesche Werte , Zahlen und Zeichenfolgen gehören undefined, nullsind technisch gesehen keine Objekte, da es sich um Sprachimplementierungen auf niedriger Ebene handelt. Boolesche Werte, Zahlen und Zeichenfolgen interagieren immer noch mit der Prototypenkette, als wären sie Objekte. Für die Zwecke dieser Antwort ist es daher einfacher, sie als "Objekte" zu betrachten, obwohl sie nicht ganz sind.

zzzzBov
quelle
1
@lostyzd - nun, sie können durch darauf zugreifen Foo.talk(). Sie können dies im Konstruktor zuweisen, wenn Sie möchten: this.talk = Foo.talk- oder, wie Sie bemerken, durch Zuweisen Foo.prototype.talk = Foo.talk. Ich bin mir jedoch nicht sicher, ob dies eine gute Idee ist. Grundsätzlich sollten Instanzmethoden spezifisch für die Instanz sein.
Nrabinowitz
2
@ Doug Avery Foo.talk()ruft gerade eine Namespace-Funktion auf. Sie würden es in Situationen verwenden, in denen statische Methoden in OOP-Sprachen wie Java / C # aufgerufen werden. Ein gutes Beispiel für einen Anwendungsfall wäre eine Funktion wie Array.isArray().
zzzzBov
7
PS null ist der Objekttyp von null == 'Objekt'
mvladk
1
Der grundlegende Punkt, den Sie alle vermissen, ist, dass statische Methoden vererbt werden. Foo.talk = function ()...wird für Unterklassen mit eigenem Klassennamen nicht verfügbar sein. Dies kann durch "Erweitern" von Unterklassen umgangen werden, aber ich suche auch immer noch nach einem eleganteren Weg.
1
@nus, nur einige Sprachen erlauben die Vererbung statischer Methoden. Wenn eine Vererbung gewünscht wird, sollten Sie zunächst keine statischen Methoden verwenden.
zzzzBov
67

Sie können es wie folgt erreichen:

function Foo() {};

Foo.talk = function() { alert('I am talking.'); };

Sie können jetzt die "Talk" -Funktion wie folgt aufrufen:

Foo.talk();

Sie können dies tun, da Funktionen in JavaScript ebenfalls Objekte sind.

Bipul
quelle
37

Rufen Sie eine statische Methode aus einer Instanz auf:

function Clazz() {};
Clazz.staticMethod = function() {
    alert('STATIC!!!');
};

Clazz.prototype.func = function() {
    this.constructor.staticMethod();
}

var obj = new Clazz();
obj.func(); // <- Alert's "STATIC!!!"

Einfaches Javascript-Klassenprojekt: https://github.com/reduardo7/sjsClass

Eduardo Cuomo
quelle
13
Dies ist kein statischer Aufruf. var obj = new Clazz (); erstellt eine neue Instanz von Clazz. Clazz.staticMethod () erzielt das Ergebnis jedoch ohne all diese anderen Dinge.
mpemburn
5
@mpemburn: Eduardo ist auch in seiner Antwort richtig. Er zeigt Ihnen nicht nur, dass Sie die statische Methode von "außen" über aufrufen können, Clazz.staticMethodsondern er zeigt Ihnen auch, wie Sie innerhalb eines instanziierten Objekts eine Verknüpfung zu diesen statischen Methoden herstellen. Dies ist besonders nützlich in Umgebungen wie Node.js, in denen Sie mit require möglicherweise keinen direkten Zugriff auf den ursprünglichen Konstruktor haben. Das einzige, was ich hinzufügen möchte, istthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford
1
Absolut fantastisch, funktioniert sogar in einem Kaffee-Skript-Konstruktor: constructor: (a) -> @constructor.add @(
na ja,
31

Hier ist ein gutes Beispiel, um zu demonstrieren, wie Javascript mit statischen / Instanzvariablen und -methoden funktioniert.

function Animal(name) {
    Animal.count = Animal.count+1||1;// static variables, use function name "Animal"
    this.name = name; //instance variable, using "this"
}

Animal.showCount = function () {//static method
    alert(Animal.count)
}

Animal.prototype.showName=function(){//instance method
    alert(this.name);
}

var mouse = new Animal("Mickey");
var elephant = new Animal("Haddoop");

Animal.showCount();  // static method, count=2
mouse.showName();//instance method, alert "Mickey"
mouse.showCount();//Error!! mouse.showCount is not a function, which is different from  Java
Jaskey
quelle
Guter Punkt: Es könnte seltsam sein, keinen Zugriff auf statische Funktionen zu haben this.
Falle II
Vielen Dank für die Lösung Dies ist, was ich gesucht habe, in welcher Situation wird es Zugriff auf thisSchlüsselwort geben
Santhosh
29

Zusätzlich ist es jetzt möglich, mit classund zu tunstatic

'use strict'

class Foo {
 static talk() {
     console.log('talk')
 };

 speak() {
     console.log('speak')
 };

};

wird geben

var a = new Foo();
Foo.talk();  // 'talk'
a.talk();    // err 'is not a function'
a.speak();   // 'speak'
Foo.speak(); // err 'is not a function'
Dima Fomin
quelle
11

Ich benutze Namespaces:

var Foo = {
     element: document.getElementById("id-here"),

     Talk: function(message) {
            alert("talking..." + message);
     },

     ChangeElement: function() {
            this.element.style.color = "red";
     }
};

Und um es zu benutzen:

Foo.Talk("Testing");

Oder

Foo.ChangeElement();
A-Sharabiani
quelle
6

ES6 unterstützt jetzt class& staticSchlüsselwörter wie ein Zauber:

class Foo {
    constructor() {}

    talk() {
        console.log("i am not static");
    }

    static saying() {
        console.log(this.speech);
    }

    static get speech() {
        return "i am static method";
    }

}
Abdennour TOUMI
quelle
Ich suchte nach einer solchen Antwort. Kann eine statische Methode nicht statische Methoden / Variablen aufrufen?
Tomasz Mularczyk
1
Bei den statischen Methoden von @Tomasz wird ´this´ nicht auf eine Instanz der Klasse festgelegt, sondern auf die Klasse selbst. So natürlich eine statische Methode kann eine Instanz - Methode aufrufen, aber nur , wenn es irgendwie Zugriff auf eine Instanz hat, wie'static Static () {new Foo () talk (). } ´
JHH
3

Wenn Sie statische Methoden in ES5 schreiben müssen, habe ich dafür ein großartiges Tutorial gefunden:

//Constructor
var Person = function (name, age){
//private properties
var priv = {};

//Public properties
this.name = name;
this.age = age;

//Public methods
this.sayHi = function(){
    alert('hello');
}
}


// A static method; this method only 
// exists on the class and doesn't exist  
// on child objects
Person.sayName = function() {
   alert("I am a Person object ;)");  
};

siehe @ https://abdulapopoola.com/2013/03/30/static-and-instance-methods-in-javascript/

Kombinieren
quelle
2

Nur zusätzliche Notizen. Verwenden der Klasse ES6: Wenn wir statische Methoden erstellen, setzt die Javacsript-Engine das Deskriptorattribut ein kleines bisschen anders als die "statische" Methode der alten Schule

function Car() {

}

Car.brand = function() {
  console.log('Honda');
}

console.log(
  Object.getOwnPropertyDescriptors(Car)
);

Es setzt das interne Attribut (Deskriptoreigenschaft) für brand () auf

..
brand: [object Object] {
    configurable: true,
    enumerable: true,
    value: ..
    writable: true

}
..

verglichen mit

class Car2 {
   static brand() {
     console.log('Honda');
   }
}

console.log(
  Object.getOwnPropertyDescriptors(Car2)
);

Damit wird das interne Attribut für brand () auf gesetzt

..
brand: [object Object] {
    configurable: true,
    enumerable: false,
    value:..
    writable: true
  }

..

Sehen Sie, dass enumerable für die statische Methode in ES6 auf false gesetzt ist .

Dies bedeutet, dass Sie die For-In-Schleife nicht zum Überprüfen des Objekts verwenden können

for (let prop in Car) {
  console.log(prop); // brand
}

for (let prop in Car2) {
  console.log(prop); // nothing here
}

Die statische Methode in ES6 wird wie die private Eigenschaft einer anderen Klasse (Name, Länge, Konstruktor) behandelt, mit der Ausnahme, dass die statische Methode weiterhin beschreibbar ist und der beschreibbare Deskriptor auf true gesetzt ist { writable: true } . es bedeutet auch, dass wir es überschreiben können

Car2.brand = function() {
   console.log('Toyota');
};

console.log(
  Car2.brand() // is now changed to toyota
);
Ngakak
quelle
1

Wenn Sie zu nennen versuchen Foo.talk, versucht der JS eine Funktion zur Suche talkdurch __proto__und, natürlich, es kann nicht gefunden werden.

Foo.__proto__ist Function.prototype.

Issac Young
quelle
1

Statische Methodenaufrufe werden direkt in der Klasse ausgeführt und können in Instanzen der Klasse nicht aufgerufen werden. Statische Methoden werden häufig verwendet, um Dienstprogrammfunktionen zu erstellen

Ziemlich klare Beschreibung

Direkt von mozilla.org genommen

Foo muss an Ihre Klasse gebunden sein Wenn Sie dann eine neue Instanz erstellen, können Sie myNewInstance.foo () aufrufen. Wenn Sie Ihre Klasse importieren, können Sie eine statische Methode aufrufen

Dave Keane
quelle
0

Als ich in eine solche Situation geriet, habe ich so etwas getan:

Logger = {
    info: function (message, tag) {
        var fullMessage = '';        
        fullMessage = this._getFormatedMessage(message, tag);
        if (loggerEnabled) {
            console.log(fullMessage);
        }
    },
    warning: function (message, tag) {
        var fullMessage = '';
        fullMessage = this._getFormatedMessage(message, tag);
        if (loggerEnabled) {
            console.warn(fullMessage);`enter code here`
        }
    },
    _getFormatedMessage: function () {}
};

so kann ich jetzt die info methode als aufrufen Logger.info("my Msg", "Tag");

Vishnu
quelle
Ich mache das die ganze Zeit, aber es ist im Grunde nur ein Namespace. Sie können keine Instanzen mit Instanzvariablen erstellen.
dcsan
0

In Ihrem Fall, wenn Sie möchten Foo.talk():

function Foo() {};
// But use Foo.talk would be inefficient
Foo.talk = function () {
    alert('hello~\n');
};

Foo.talk(); // 'hello~\n'

Aber es ist eine ineffiziente Art zu implementieren, die Verwendung prototypeist besser.


Ein anderer Weg, Mein Weg ist als statische Klasse definiert:

var Foo = new function() {
  this.talk = function () {
    alert('hello~\n');
    };
};

Foo.talk(); // 'hello~\n'

Die oben genannte statische Klasse muss nicht verwendet werden, prototypeda sie nur einmal als statische Verwendung erstellt wird.

https://github.com/yidas/js-design-patterns/tree/master/class

Nick Tsai
quelle
@jvitoroc Danke!
Nick Tsai
0

Javascript hat keine tatsächlichen Klassen, sondern verwendet ein System der prototypischen Vererbung, bei dem Objekte über ihre Prototypenkette von anderen Objekten "erben". Dies lässt sich am besten über den Code selbst erklären:

function Foo() {};
// creates a new function object

Foo.prototype.talk = function () {
    console.log('hello~\n');
};
// put a new function (object) on the prototype (object) of the Foo function object

var a = new Foo;
// When foo is created using the new keyword it automatically has a reference 
// to the prototype property of the Foo function

// We can show this with the following code
console.log(Object.getPrototypeOf(a) === Foo.prototype); 

a.talk(); // 'hello~\n'
// When the talk method is invoked it will first look on the object a for the talk method,
// when this is not present it will look on the prototype of a (i.e. Foo.prototype)

// When you want to call
// Foo.talk();
// this will not work because you haven't put the talk() property on the Foo
// function object. Rather it is located on the prototype property of Foo.

// We could make it work like this:
Foo.sayhi = function () {
    console.log('hello there');
};

Foo.sayhi();
// This works now. However it will not be present on the prototype chain 
// of objects we create out of Foo

Willem van der Veen
quelle