Classe vs. método estático em JavaScript

262

Eu sei que isso vai funcionar:

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

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

Mas se eu quiser ligar

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

Eu encontro alguns métodos para fazer o Foo.talktrabalho,

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

Existem outras maneiras de fazer isso? Não sei se é certo fazê-lo. Você usa métodos de classe ou métodos estáticos no seu código JavaScript?

lostyzd
fonte
14
Foo.talk = function ...
Otimização prematura
1
@downvoterstepintothelight O Foo.walk = function() {}não afetará suas instâncias, pois não está na cadeia de protótipos. Existe cross-browser um método para fazer uma função [[prototype]]ponto a seu prototype?
lostyzd
3
provavelmente não tenho idéia do que você deseja, porque os métodos de classe não afetam as instâncias por definição.
Otimização prematura
@downvoterstepintothelight duvido que, método , em linguagem como python, uma instância é capaz de chamar o método de classe, a diferença é thisponteiro.
Lostyzd 8/10

Respostas:

410

Primeiro, lembre-se de que o JavaScript é principalmente uma linguagem prototípica , e não uma linguagem baseada em classe 1 . Foonão é uma classe, é uma função, que é um objeto. Você pode instanciar um objeto de que a função usando a newpalavra-chave que lhe permitirá criar algo semelhante a uma classe em uma linguagem OOP padrão.

Eu sugiro que ignore a __proto__maior parte do tempo, porque ele tem um suporte ruim entre navegadores e, em vez disso, concentre-se em aprender como prototypefunciona.

Se você tiver uma instância de um objeto criado a partir de uma função 2 e acessar um de seus membros (métodos, atributos, propriedades, constantes etc.) de qualquer forma, o acesso fluirá pela hierarquia do protótipo até que (a) encontre o membro, ou (b) não encontra outro protótipo.

A hierarquia inicia no objeto que foi chamado e, em seguida, pesquisa seu objeto de protótipo. Se o objeto protótipo tiver um protótipo, ele será repetido, se não houver protótipo, undefinedserá retornado.

Por exemplo:

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

Parece-me que você pelo menos já entendeu um pouco essas partes "básicas", mas preciso explicitá-las apenas para ter certeza.

Em JavaScript, tudo é um objeto 3 .

tudo é um objeto.

function Foo(){} não apenas define uma nova função, ele define um novo objeto de função que pode ser acessado usando Foo .

É por isso que você pode acessar Fooo protótipo com Foo.prototype.

O que você também pode fazer é definir mais funções em Foo:

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

Esta nova função pode ser acessada usando:

Foo.talk();

Espero que agora você esteja percebendo uma semelhança entre funções em um objeto de função e um método estático.

Pense f = new Foo();em criar uma instância de classe, Foo.prototype.bar = function(){...}como definir um método compartilhado para a classe e Foo.baz = function(){...}como definir um método estático público para a classe.


O ECMAScript 2015 introduziu uma variedade de açúcar sintático para esse tipo de declaração, para torná-los mais simples de implementar e também para facilitar a leitura. O exemplo anterior pode, portanto, ser escrito como:

class Foo {
  bar() {...}

  static baz() {...}
}

que permite barser chamado como:

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

e bazser chamado como:

Foo.baz()

1: classera uma "palavra reservada futura" na especificação do ECMAScript 5 , mas o ES6 introduz a capacidade de definir classes usando a classpalavra - chave.

2: essencialmente uma instância de classe criada por um construtor, mas há muitas diferenças sutis que eu não quero enganar você

3: valores primitivos - que incluem undefined, nullbooleanos, números e seqüências de caracteres - não são tecnicamente objetos porque são implementações de linguagem de baixo nível. Booleanos, números e seqüências de caracteres ainda interagem com a cadeia de protótipos como se fossem objetos. Portanto, para os fins desta resposta, é mais fácil considerá-los "objetos", mesmo que não sejam exatamente assim.

zzzzBov
fonte
1
@lostyzd - bem, eles podem acessá-lo através Foo.talk(). Você pode atribuir isso no construtor, se desejar: this.talk = Foo.talk- ou, como você observa, atribuindo Foo.prototype.talk = Foo.talk. Mas não tenho certeza de que seja uma boa ideia - por princípio, os métodos de instância devem ser específicos para a instância.
Nrabinowitz
2
@ Doug Avery, Foo.talk()está apenas chamando uma função no espaço de nome. Você o usaria em situações semelhantes à forma como os métodos estáticos são chamados nas linguagens OOP, como Java / C #. Um bom exemplo de um caso de uso seria uma função como Array.isArray().
ZzzzBov 08/10/11
7
PS nula é objeto typeof == null 'objeto'
mvladk
1
O ponto fundamental que falta é que os métodos estáticos sejam herdados. Foo.talk = function ()...não estará disponível para subclasses no próprio nome da classe. Isso pode ser contornado através da extensão de subclasses, mas também estou procurando uma maneira mais elegante.
1
@nus, apenas alguns idiomas permitem que métodos estáticos sejam herdados. Se a herança é desejada, você não deve usar métodos estáticos para começar.
zzzzBov
67

Você pode alcançá-lo como abaixo:

function Foo() {};

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

Agora você pode chamar a função "talk" como abaixo:

Foo.talk();

Você pode fazer isso porque, em JavaScript, as funções também são objetos.

Bipul
fonte
37

Chame um método estático de uma instância:

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

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

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

Projeto de classe Javascript simples: https://github.com/reduardo7/sjsClass

Eduardo Cuomo
fonte
13
Esta não é uma chamada estática. var obj = novo Clazz (); cria uma nova instância do Clazz. No entanto, Clazz.staticMethod () alcança o resultado sem todas essas outras coisas.
mpemburn
5
@mpemburn: Eduardo também está correto em sua resposta. O que ele está mostrando a você não é apenas você pode chamar o método estático de "fora" via, Clazz.staticMethodmas ele está mostrando como vincular esses métodos estáticos a partir de um objeto instanciado. Isso é especialmente útil em ambientes como o Node.js, onde, usando o require, você pode não ter acesso direto ao construtor original. A única coisa que gostaria de acrescentar éthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford
1
Absolutamente incrível, funciona até mesmo dentro de um construtor coffeescript: constructor: (a) -> @constructor.add @(bem, quase, de qualquer maneira)
Orwellophile
31

Aqui está um bom exemplo para demonstrar como o Javascript funciona com variáveis ​​e métodos estáticos / instâncias.

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
fonte
Ponto positivo: Pode ser estranho não ter acesso à função estática this.
TrapII # 16/15
Agradecimentos para a solução isto é o que eu estava procurando em que situação haverá acesso de thispalavra-chave
santhosh
30

Além disso, agora é possível fazer com classestatic

'use strict'

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

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

};

darei

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
fonte
Esta é a melhor resposta, pois os exemplos valem mais que mil palavras. No entanto, isso não explica por a.talk()que não funciona. A resposta aceita diz que a cadeia de protótipos deve encontrá-lo, certo? Mas não é o caso
Pynchia
11

Eu uso namespaces:

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

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

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

E para usá-lo:

Foo.Talk("Testing");

Ou

Foo.ChangeElement();
A-Sharabiani
fonte
6

O ES6 suporta agora classe staticpalavras - chave como um encanto:

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
fonte
Eu estava procurando por uma resposta como esta. O método estático pode chamar métodos / variáveis ​​não estáticos?
Tomasz Mularczyk
1
Os métodos estáticos do @Tomasz não terão esse valor definido para nenhuma instância da classe, mas sim a própria classe. Portanto, é claro, um método estático pode chamar um método de instância, mas apenas se, de alguma forma, tiver acesso a uma instância, como staticStatic staticMethod () {new Foo (). Talk (); } ´
JHH 11/01
3

Se você precisar escrever métodos estáticos no ES5, encontrei um ótimo tutorial para isso:

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

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

Combinar
fonte
2

Apenas notas adicionais. Usando a classe ES6, Quando criamos métodos estáticos ... o mecanismo Javacsript define o atributo descritor um pouco diferente do método "estático" da velha escola

function Car() {

}

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

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

define atributo interno (propriedade do descritor) da marca () como

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

}
..

comparado com

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

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

que define o atributo interno da marca () como

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

..

veja que enumerable está definido como false para o método estático no ES6.

significa que você não pode usar o loop for-in para verificar o objeto

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

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

O método estático no ES6 é tratado como a propriedade privada da classe de outros (nome, comprimento, construtor), exceto que o método estático ainda é gravável, portanto o gravador do descritor é definido como verdadeiro { writable: true } . isso também significa que podemos substituí-lo

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

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

Quando você tenta chamar Foo.talk, a JS tenta procurar uma função talkatravés de __proto__e, é claro, não pode ser encontrada.

Foo.__proto__é Function.prototype.

Issac Young
fonte
1

As chamadas de método estático são feitas diretamente na classe e não podem ser chamadas em instâncias da classe. Métodos estáticos são frequentemente usados ​​para criar funções utilitárias

Descrição bastante clara

Tomado diretamente de mozilla.org

Foo precisa estar vinculado à sua classe Então, quando você cria uma nova instância, pode chamar myNewInstance.foo () Se você importar sua classe, pode chamar um método estático

Dave Keane
fonte
0

Quando enfrentei tal situação, fiz algo parecido com isto:

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

então agora eu posso chamar o método info como Logger.info("my Msg", "Tag");

Vishnu
fonte
Faço isso o tempo todo, mas é basicamente apenas espaço para nome. Não permite criar instâncias com vars de instância?
dcsan
0

No seu caso, se você quiser Foo.talk():

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

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

Mas é uma maneira ineficiente de implementar, usar prototypeé melhor.


Outra maneira, Meu caminho é definido como classe estática:

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

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

A classe estática acima não precisa ser usada, prototypepois será construída apenas uma vez como uso estático.

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

Nick Tsai
fonte
@jvitoroc Thanks!
Nick Tsai
0

Javascript não possui classes reais, ao contrário, usa um sistema de herança prototípica no qual os objetos 'herdam' de outros objetos por meio de sua cadeia de protótipos. Isso é melhor explicado através do próprio código:

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
fonte