Classe vs méthode statique en JavaScript

262

Je sais que cela fonctionnera:

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

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

Mais si je veux appeler

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

Je trouve des méthodes pour faire du Foo.talktravail,

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

Y a-t-il d'autres façons de procéder? Je ne sais pas s'il est juste de le faire. Utilisez-vous des méthodes de classe ou des méthodes statiques dans votre code JavaScript?

lostyzd
la source
14
Foo.talk = function ...
Optimisation prématurée
1
@downvoterstepintothelight Le n'affectera Foo.walk = function() {}pas ses instances, car il ne se trouve pas sur la chaîne prototype. Y at - il cross-browser une méthode pour faire d'une fonction [[prototype]]point à son prototype?
lostyzd
3
je n'ai probablement aucune idée de ce que vous voulez, car les méthodes de classe n'affectent pas les instances par définition.
Optimisation prématurée
@downvoterstepintothelight Je doute que, méthode , dans un langage comme python, une instance soit capable d'appeler sa méthode de classe, la différence est thispointeur.
lostyzd

Réponses:

410

Tout d'abord, rappelez-vous que JavaScript est principalement un langage prototypique , plutôt qu'un langage basé sur une classe 1 . Foon'est pas une classe, c'est une fonction, qui est un objet. Vous pouvez instancier un objet à partir de cette fonction en utilisant lenew mot clé qui vous permettra de créer quelque chose de similaire à une classe dans un langage OOP standard.

Je suggérerais d'ignorer la __proto__plupart du temps, car il ne prend pas en charge plusieurs navigateurs, et plutôt de se concentrer sur l'apprentissageprototype fonctionnement.

Si vous avez une instance d'un objet créé à partir d'une fonction 2 et que vous accédez à l'un de ses membres (méthodes, attributs, propriétés, constantes, etc.) de quelque manière que ce soit, l'accès descendra dans la hiérarchie du prototype jusqu'à ce qu'il (a) trouve le membre, ou (b) ne trouve pas un autre prototype.

La hiérarchie commence sur l'objet appelé, puis recherche son objet prototype. Si l'objet prototype a un prototype, il se répète, si aucun prototype n'existe, undefinedest retourné.

Par exemple:

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

Il me semble que vous avez déjà au moins quelque peu compris ces parties "de base", mais je dois les rendre explicites juste pour être sûr.

En JavaScript, tout est un objet 3 .

tout est un objet.

function Foo(){} ne définit pas seulement une nouvelle fonction, il définit un nouvel objet fonction accessible à l'aide de Foo .

C'est pourquoi vous pouvez accéder Fooau prototype de avec Foo.prototype.

Vous pouvez également définir plus de fonctions sur Foo:

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

Cette nouvelle fonction est accessible en utilisant:

Foo.talk();

J'espère que vous remarquez maintenant une similitude entre les fonctions d'un objet fonction et une méthode statique.

Pensez f = new Foo();à créer une instance de classe, Foo.prototype.bar = function(){...}à définir une méthode partagée pour la classe et Foo.baz = function(){...}à définir une méthode statique publique pour la classe.


ECMAScript 2015 a introduit une variété de sucre syntaxique pour ces types de déclarations afin de les rendre plus simples à mettre en œuvre tout en étant plus faciles à lire. L'exemple précédent peut donc s'écrire:

class Foo {
  bar() {...}

  static baz() {...}
}

qui permet bard'être appelé comme:

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

et bazêtre appelé comme:

Foo.baz()

1: classétait un "futur mot réservé" dans la spécification ECMAScript 5 , mais ES6 introduit la possibilité de définir des classes à l'aide du classmot - clé.

2: essentiellement une instance de classe créée par un constructeur, mais il existe de nombreuses différences nuancées que je ne veux pas vous induire en erreur

3: les valeurs primitives - qui incluent undefined, les nullbooléens, les nombres et les chaînes - ne sont pas techniquement des objets car ce sont des implémentations de langage de bas niveau. Les booléens, les nombres et les chaînes interagissent toujours avec la chaîne prototype comme s'ils étaient des objets, donc pour les besoins de cette réponse, il est plus facile de les considérer comme des "objets" même s'ils ne le sont pas tout à fait.

zzzzBov
la source
1
@lostyzd - eh bien, ils peuvent y accéder, via Foo.talk(). Vous pouvez attribuer cela dans le constructeur, si vous le souhaitez: this.talk = Foo.talk- ou, comme vous le notez, en l'attribuant Foo.prototype.talk = Foo.talk. Mais je ne suis pas sûr que ce soit une bonne idée - en principe, les méthodes d'instance doivent être spécifiques à l'instance.
nrabinowitz
2
@Doug Avery, Foo.talk()appelle simplement une fonction à espace de noms. Vous l'utiliseriez dans des situations similaires à la façon dont les méthodes statiques sont appelées dans les langages OOP comme Java / C #. Un bon exemple de cas d'utilisation serait une fonction comme Array.isArray().
zzzzBov
7
PS null est le type d'objet de null == 'object'
mvladk
1
Le point fondamental qui vous manque tous est que les méthodes statiques sont héritées. Foo.talk = function ()...ne sera pas disponible pour les sous-classes sur leur propre nom de classe. Cela peut être contourné en "étendant" les sous-classes, mais je recherche également une manière plus élégante.
1
@nus, seuls certains langages permettent d'hériter de méthodes statiques. Si l'héritage est souhaité, vous ne devez pas utiliser de méthodes statiques pour commencer.
zzzzBov
67

Vous pouvez le réaliser comme ci-dessous:

function Foo() {};

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

Vous pouvez maintenant invoquer la fonction "talk" comme ci-dessous:

Foo.talk();

Vous pouvez le faire car en JavaScript, les fonctions sont également des objets.

Bipul
la source
37

Appelez une méthode statique à partir d'une instance:

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

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

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

Projet de classe Javascript simple: https://github.com/reduardo7/sjsClass

Eduardo Cuomo
la source
13
Ce n'est pas un appel statique. var obj = new Clazz (); crée une nouvelle instance de Clazz. Cependant, Clazz.staticMethod () obtient le résultat sans toutes ces autres choses.
mpemburn
5
@mpemburn: Eduardo a également raison dans sa réponse. Ce qu'il vous montre est non seulement vous pouvez appeler la méthode statique de "l'extérieur" via Clazz.staticMethodmais il vous montre comment établir un lien vers ces méthodes statiques à partir d'un objet instancié. Ceci est particulièrement utile dans des environnements tels que Node.js où, en utilisant require, vous ne pouvez pas avoir un accès direct au constructeur d'origine. La seule chose que j'ajouterais estthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford
1
Absolument génial, fonctionne même à l'intérieur d'un constructeur de scénario de café: constructor: (a) -> @constructor.add @(enfin presque, de toute façon)
Orwellophile
31

Voici un bon exemple pour montrer comment Javascript fonctionne avec les variables et méthodes statiques / d'instance.

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
la source
Bon point: cela pourrait être bizarre de ne pas avoir accès à la fonction statique this.
TrapII
Merci pour la solution c'est ce que je cherchais dans quelle situation il y aura accès de thismot
santhosh
30

En plus, il est maintenant possible de faire avec classetstatic

'use strict'

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

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

};

va donner

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
la source
C'est la meilleure réponse, car les exemples valent mille mots. Cependant, cela n'explique pas pourquoi a.talk()cela ne fonctionne pas. La réponse acceptée dit que la chaîne de prototypes devrait le trouver, non? Mais ce n'est pas le cas
Pynchia
11

J'utilise des espaces de noms:

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

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

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

Et pour l'utiliser:

Foo.Talk("Testing");

Ou

Foo.ChangeElement();
A-Sharabiani
la source
6

ES6 prend en charge maintenant classet les staticmots clés comme un charme:

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
la source
Je cherchais une réponse comme celle-ci. La méthode statique peut-elle appeler des méthodes / variables non statiques?
Tomasz Mularczyk
1
Les méthodes statiques de @Tomasz n'auront pas "ce" défini sur n'importe quelle instance de la classe, mais plutôt la classe elle-même. Donc, bien sûr, une méthode statique peut appeler une méthode d'instance, mais seulement si elle a en quelque sorte accès à une instance, comme ´static staticMethod () {new Foo (). Talk (); } ´
JHH
3

Si vous devez écrire des méthodes statiques dans ES5, j'ai trouvé un excellent tutoriel pour cela:

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

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

Combiner
la source
2

Juste des notes supplémentaires. En utilisant la classe ES6, lorsque nous créons des méthodes statiques .. le moteur Javacsript définit l'attribut descripteur un peu différent de la méthode "statique" de la vieille école

function Car() {

}

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

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

il définit l'attribut interne (propriété du descripteur) pour brand () sur

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

}
..

par rapport à

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

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

qui définit l'attribut interne de brand () sur

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

..

voir que l' énumération est définie sur false pour la méthode statique dans ES6.

cela signifie que vous ne pouvez pas utiliser la boucle for-in pour vérifier l'objet

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

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

La méthode statique dans ES6 est traitée comme la propriété privée des autres classes (nom, longueur, constructeur), sauf que la méthode statique est toujours accessible en écriture, ainsi le descripteur accessible en écriture est défini sur true { writable: true } . cela signifie également que nous pouvons le remplacer

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

console.log(
  Car2.brand() // is now changed to toyota
);
ngakak
la source
1

Lorsque vous essayez d'appeler Foo.talk, le JS essaie de rechercher une fonction à talktravers __proto__et, bien sûr, elle ne peut pas être trouvée.

Foo.__proto__est Function.prototype.

Issac Young
la source
1

Les appels de méthode statique sont effectués directement sur la classe et ne peuvent pas être appelés sur les instances de la classe. Les méthodes statiques sont souvent utilisées pour créer une fonction d'utilité

Description assez claire

Tiré directement de mozilla.org

Foo doit être lié à votre classe Ensuite, lorsque vous créez une nouvelle instance, vous pouvez appeler myNewInstance.foo () Si vous importez votre classe, vous pouvez appeler une méthode statique

Dave Keane
la source
0

Quand j'ai fait face à une telle situation, j'ai fait quelque chose comme ceci:

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

alors maintenant je peux appeler la méthode info comme Logger.info("my Msg", "Tag");

Vishnu
la source
Je le fais tout le temps, mais il s'agit essentiellement d'espaces de noms. Cela ne vous permet pas de créer des instances avec des variables d'instance?
dcsan
0

Dans votre cas, si vous souhaitez Foo.talk():

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

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

Mais c'est une manière inefficace de mettre en œuvre, utiliser prototypec'est mieux.


Une autre façon, My way est définie comme une classe statique:

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

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

La classe statique ci-dessus n'a pas besoin d'être utilisée prototypecar elle ne sera construite qu'une seule fois en tant qu'utilisation statique.

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

Nick Tsai
la source
@jvitoroc Merci!
Nick Tsai
0

Javascript n'a pas de classes réelles, il utilise plutôt un système d'héritage prototypique dans lequel les objets «héritent» d'autres objets via leur chaîne de prototypes. Cela s'explique mieux par le code lui-même:

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
la source