Metodo di classe vs. statico in JavaScript


262

So che funzionerà:

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

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

Ma se voglio chiamare

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

Trovo alcuni metodi per far Foo.talkfunzionare,

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

Ci sono altri modi per farlo? Non so se sia giusto farlo. Usi metodi di classe o metodi statici nel tuo codice JavaScript?


14
Foo.talk = function ...
Ottimizzazione precoce

1
@downvoterstepintothelight The Foo.walk = function() {}non avrà effetto sulle sue istanze, in quanto non si trova sulla catena del prototipo. Esiste un metodo cross-browser per far puntare una funzione [[prototype]]alla sua prototype?
lostyzd,

3
probabilmente non ho idea di quello che vuoi, perché i metodi di classe non influenzano le istanze per definizione.
Ottimizzazione prematura

@downvoterstepintothelight Dubito che, metodo , in un linguaggio come Python, un'istanza sia in grado di chiamare il suo metodo di classe, la differenza è thispuntatore.
lostyzd,

Risposte:


410

Prima di tutto, ricorda che JavaScript è principalmente un linguaggio prototipo , piuttosto che un linguaggio di classe 1 . Foonon è una classe, è una funzione, che è un oggetto. Puoi creare un'istanza di un oggetto da quella funzione usando la newparola chiave che ti permetterà di creare qualcosa di simile a una classe in un linguaggio OOP standard.

Suggerirei di ignorare la __proto__maggior parte delle volte perché ha un supporto scarso tra i browser e invece di concentrarmi sull'apprendimento di come prototypefunziona.

Se si dispone di un'istanza di un oggetto creata da una funzione 2 e si accede a uno dei suoi membri (metodi, attributi, proprietà, costanti, ecc.) In qualsiasi modo, l'accesso scorrerà lungo la gerarchia del prototipo fino a quando non ( membro o (b) non trova un altro prototipo.

La gerarchia inizia sull'oggetto che è stato chiamato e quindi cerca il suo oggetto prototipo. Se l'oggetto prototipo ha un prototipo, viene ripetuto, se non esiste alcun prototipo, undefinedviene restituito.

Per esempio:

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

Mi sembra che tu abbia almeno in qualche modo capito già queste parti "di base", ma devo renderle esplicite solo per essere sicuri.

In JavaScript, tutto è un oggetto 3 .

tutto è un oggetto.

function Foo(){}non definisce solo una nuova funzione, ma definisce un nuovo oggetto funzione a cui è possibile accedere utilizzando Foo.

Questo è il motivo per cui è possibile accedere Fooal prototipo con Foo.prototype.

Quello che puoi anche fare è impostare più funzioni su Foo:

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

È possibile accedere a questa nuova funzione utilizzando:

Foo.talk();

Spero ormai che tu stia notando una somiglianza tra le funzioni su un oggetto funzione e un metodo statico.

Pensa f = new Foo();alla creazione di un'istanza di classe, Foo.prototype.bar = function(){...}alla definizione di un metodo condiviso per la classe e Foo.baz = function(){...}alla definizione di un metodo statico pubblico per la classe.


ECMAScript 2015 ha introdotto una varietà di zucchero sintattico per questo tipo di dichiarazioni per renderle più semplici da implementare e allo stesso tempo più facili da leggere. L'esempio precedente può quindi essere scritto come:

class Foo {
  bar() {...}

  static baz() {...}
}

che consente bardi essere chiamato come:

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

e bazdi essere chiamato come:

Foo.baz()

1: classera una "Parola riservata futura" nella specifica ECMAScript 5 , ma ES6 introduce la possibilità di definire le classi usando la classparola chiave.

2: essenzialmente un'istanza di classe creata da un costruttore, ma ci sono molte differenze sfumate che non voglio fuorviare

3: valori primitivi -che comprendono undefined, null, booleani, numeri e stringhe-ITA oggetti tecnicamente perché sono implementazioni linguaggio di basso livello. Booleani, numeri e stringhe interagiscono ancora con la catena del prototipo come se fossero oggetti, quindi ai fini di questa risposta, è più facile considerarli "oggetti" anche se non del tutto.


1
@lostyzd - beh, possono accedervi, attraverso Foo.talk(). Puoi assegnarlo nel costruttore, se vuoi: this.talk = Foo.talk- o, come noti, assegnando Foo.prototype.talk = Foo.talk. Ma non sono sicuro che sia una buona idea - in linea di principio, i metodi di istanza dovrebbero essere specifici dell'istanza.
nrabinowitz,

2
@Doug Avery, Foo.talk()sta solo chiamando una funzione spaziale . Lo useresti in situazioni simili a come vengono chiamati metodi statici in linguaggi OOP come Java / C #. Un buon esempio di un caso d'uso sarebbe una funzione simile Array.isArray().
zzzzBov

7
PS null è il tipo di oggetto null == 'oggetto'
mvladk

1
Il punto fondamentale che ti manca è che i metodi statici vengano ereditati. Foo.talk = function ()...non sarà disponibile per le sottoclassi con il proprio nome di classe. Questo può essere aggirato estendendo le sottoclassi, ma sto ancora cercando un modo più elegante.

1
@nus, solo alcune lingue consentono di ereditare metodi statici. Se si desidera l'ereditarietà, non si dovrebbero usare metodi statici per cominciare.
zzzzBov

67

Puoi raggiungerlo come di seguito:

function Foo() {};

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

Ora puoi invocare la funzione "talk" come di seguito:

Foo.talk();

Puoi farlo perché in JavaScript anche le funzioni sono oggetti.


37

Chiama un metodo statico da un'istanza:

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

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

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

Progetto di classe Javascript semplice: https://github.com/reduardo7/sjsClass


13
Questa non è una chiamata statica. var obj = new Clazz (); crea una nuova istanza di Clazz. Tuttavia, Clazz.staticMethod () ottiene il risultato senza tutte le altre cose.
lunedì

5
@mpemburn: anche Eduardo ha ragione nella sua risposta. Quello che ti sta mostrando non è solo che puoi chiamare il metodo statico dall'esterno, Clazz.staticMethodma ti sta mostrando come collegarti a questi metodi statici all'interno di un oggetto istanziato. Ciò è particolarmente utile in ambienti come Node.js in cui, utilizzando il comando, potresti non avere accesso diretto al costruttore originale. L'unica cosa che vorrei aggiungere èthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford il

1
Assolutamente fantastico, funziona anche all'interno di un costruttore di sceneggiature per il caffè: constructor: (a) -> @constructor.add @(beh quasi, comunque)
Orwellophile,

31

Ecco un buon esempio per dimostrare come Javascript funziona con variabili e metodi statici / di istanza.

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

Un buon punto: potrebbe essere strano non avere accesso alla funzione statica this.
TrapII

Grazie per la soluzione, questo è quello che stavo cercando in quale situazione ci sarà l'accesso alla thisparola chiave
santhosh

30

Inoltre, ora è possibile fare con classestatic

'use strict'

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

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

};

darà

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

Questa è la risposta migliore, poiché gli esempi valgono più di mille parole. Tuttavia, non spiega perché a.talk()non funziona. La risposta accettata dice che la catena di prototipi dovrebbe trovarla, giusto? Ma non è così
Pynchia

11

Uso gli spazi dei nomi:

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

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

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

E per usarlo:

Foo.Talk("Testing");

O

Foo.ChangeElement();

6

ES6 supporta ora classe staticparole chiave come un fascino:

class Foo {
    constructor() {}

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

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

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

}

Stavo cercando una risposta come questa. Il metodo statico può chiamare metodi / variabili non statici?
Tomasz Mularczyk,

1
I metodi statici di @Tomasz non avranno "questo" impostato su nessuna istanza della classe, ma piuttosto sulla classe stessa. Quindi, ovviamente, un metodo statico può chiamare un metodo di istanza, ma solo se in qualche modo ha accesso a un'istanza, come ´static staticMethod () {new Foo (). Talk (); } ´
JHH l'

3

Se devi scrivere metodi statici in ES5 ho trovato un ottimo tutorial per questo:

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

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


2

Solo note aggiuntive. Usando la classe ES6, quando creiamo metodi statici ... il motore Javacsript imposta l'attributo descrittore un po 'diverso dal metodo "statico" della vecchia scuola

function Car() {

}

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

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

imposta l'attributo interno (proprietà descrittore) per brand () su

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

}
..

rispetto a

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

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

che imposta l'attributo interno per brand () su

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

..

vedere che enumerable è impostato su false per il metodo statico in ES6.

significa che non puoi usare il ciclo for-in per controllare l'oggetto

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

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

il metodo statico in ES6 è trattato come la proprietà privata di altre classi (nome, lunghezza, costruttore) tranne per il fatto che il metodo statico è ancora scrivibile, quindi il descrittore scrivibile è impostato su true { writable: true } . significa anche che possiamo ignorarlo

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

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

1

Quando si tenta di chiamare Foo.talk, il JS tenta di cercare una funzione talktramite __proto__e, naturalmente, non può essere trovato.

Foo.__proto__lo è Function.prototype.


1

Le chiamate ai metodi statici vengono effettuate direttamente sulla classe e non sono richiamabili su istanze della classe. I metodi statici vengono spesso utilizzati per creare funzioni di utilità

Descrizione abbastanza chiara

Tratto direttamente da mozilla.org

Foo deve essere associato alla tua classe Quindi quando crei una nuova istanza puoi chiamare myNewInstance.foo () Se importi la tua classe puoi chiamare un metodo statico


0

Quando ho affrontato una situazione del genere, ho fatto qualcosa del genere:

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 () {}
};

così ora posso chiamare il metodo info come Logger.info("my Msg", "Tag");


Lo faccio sempre, ma fondamentalmente è solo uno spazio per i nomi. Non ti consente di creare istanze con istanze var?
dcsan,

0

Nel tuo caso, se vuoi Foo.talk():

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

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

Ma è un modo inefficiente per implementare, usare prototypeè meglio.


Un altro modo, My way è definito come classe statica:

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

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

La classe statica di cui sopra non deve essere utilizzata prototypeperché verrà costruita una sola volta come utilizzo statico.

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


@jvitoroc Grazie!
Nick Tsai,

0

Javascript non ha classi reali, ma utilizza un sistema di ereditarietà prototipale in cui gli oggetti "ereditano" da altri oggetti attraverso la loro catena di prototipi. Questo è meglio spiegato tramite il codice stesso:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.