Clase versus método estático en JavaScript

262

Sé que esto funcionará:

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

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

Pero si quiero llamar

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

Encuentro algunos métodos para hacer el Foo.talktrabajo,

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

¿Hay otras formas de hacer esto? No sé si es correcto hacerlo. ¿Utiliza métodos de clase o métodos estáticos en su código JavaScript?

lostyzd
fuente
14
Foo.talk = function ...
Optimización prematura
1
@downvoterstepintothelight The Foo.walk = function() {}no afectará sus instancias, ya que no está en la cadena de prototipos. ¿Existe un navegador cruzado para hacer que una función [[prototype]]señale prototype?
lostyzd
3
probablemente no tengo idea de lo que quieres, porque los métodos de clase no afectan las instancias por definición.
Optimización prematura
@downvoterstepintothelight Dudo que, método , en lenguaje como python, una instancia pueda llamar a su método de clase, la diferencia es thispuntero.
lostyzd

Respuestas:

410

En primer lugar, recuerde que JavaScript es principalmente un lenguaje prototípico , en lugar de un lenguaje basado en clases 1 . Foono es una clase, es una función, que es un objeto. Puede crear una instancia de un objeto de esa función utilizando la newpalabra clave que le permitirá crear algo similar a una clase en un lenguaje estándar de OOP.

Sugeriría ignorar la __proto__mayor parte del tiempo porque tiene poca compatibilidad entre navegadores y, en su lugar, centrarse en aprender cómo prototypefunciona.

Si tiene una instancia de un objeto creado a partir de una función 2 y accede a uno de sus miembros (métodos, atributos, propiedades, constantes, etc.) de alguna manera, el acceso fluirá por la jerarquía del prototipo hasta que (a) encuentre el miembro, o (b) no encuentra otro prototipo.

La jerarquía comienza en el objeto que se llamó y luego busca su objeto prototipo. Si el objeto prototipo tiene un prototipo, se repite, si no existe ningún prototipo, undefinedse devuelve.

Por ejemplo:

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

Me parece que ya has entendido al menos estas partes "básicas", pero necesito hacerlas explícitas solo para estar seguro.

En JavaScript, todo es un objeto 3 .

Todo es un objeto.

function Foo(){}no solo define una nueva función, sino que define un nuevo objeto de función al que se puede acceder utilizando Foo.

Es por eso que puede acceder Fooal prototipo con Foo.prototype.

Lo que también puede hacer es establecer más funciones en Foo:

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

Se puede acceder a esta nueva función usando:

Foo.talk();

Espero que ahora esté notando una similitud entre las funciones de un objeto de función y un método estático.

Piense f = new Foo();como crear una instancia de clase, Foo.prototype.bar = function(){...}como definir un método compartido para la clase y Foo.baz = function(){...}como definir un método público estático para la clase.


ECMAScript 2015 introdujo una variedad de azúcar sintáctica para este tipo de declaraciones para hacerlas más simples de implementar y al mismo tiempo más fáciles de leer. Por lo tanto, el ejemplo anterior se puede escribir como:

class Foo {
  bar() {...}

  static baz() {...}
}

que permite barser llamado como:

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

y bazser llamado como:

Foo.baz()

1: classera una "Palabra reservada para el futuro" en la especificación ECMAScript 5 , pero ES6 introduce la capacidad de definir clases usando la classpalabra clave.

2: esencialmente una instancia de clase creada por un constructor, pero hay muchas diferencias matizadas que no quiero engañarlo

3: valores primitivos -que incluyen undefined, null, booleanos, números y cadenas de HAYA objetos técnicamente porque son implementaciones de lenguajes de bajo nivel. Los booleanos, los números y las cadenas aún interactúan con la cadena del prototipo como si fueran objetos, por lo que a los efectos de esta respuesta, es más fácil considerarlos "objetos" aunque no lo sean del todo.

zzzzBov
fuente
1
@lostyzd: bueno, pueden acceder a él a través de Foo.talk(). Puede asignar eso en el constructor, si lo desea: this.talk = Foo.talk- o, como observa, mediante la asignación Foo.prototype.talk = Foo.talk. Pero no estoy seguro de que sea una buena idea: en principio, los métodos de instancia deben ser específicos de la instancia.
nrabinowitz
2
@Doug Avery, Foo.talk()solo está llamando a una función de espacio de nombres. Lo usaría en situaciones similares a cómo se llaman los métodos estáticos en lenguajes OOP como Java / C #. Un buen ejemplo de un caso de uso sería una función como Array.isArray().
zzzzBov
77
PS nulo es tipo de objeto nulo == 'objeto'
mvladk
1
El punto fundamental que le falta a todos es que los métodos estáticos se heredan. Foo.talk = function ()...no estará disponible para subclases en su propio nombre de clase. Esto puede solucionarse "ampliando" las subclases, pero también sigo buscando una forma más elegante.
1
@nus, solo algunos idiomas permiten que se hereden métodos estáticos. Si se desea la herencia, no debería usar métodos estáticos para empezar.
zzzzBov
67

Puede lograrlo de la siguiente manera:

function Foo() {};

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

Ahora puede invocar la función "hablar" de la siguiente manera:

Foo.talk();

Puede hacer esto porque en JavaScript, las funciones también son objetos.

Bipul
fuente
37

Llamar a un método estático desde una instancia:

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

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

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

Proyecto de clase Javascript simple: https://github.com/reduardo7/sjsClass

Eduardo Cuomo
fuente
13
Esta no es una llamada estática. var obj = new Clazz (); crea una nueva instancia de Clazz. Sin embargo, Clazz.staticMethod () logra el resultado sin todas esas otras cosas.
mpemburn
55
@mpemburn: Eduardo también tiene razón en su respuesta. Lo que le está mostrando no solo puede llamar al método estático desde "afuera" a través de, Clazz.staticMethodsino que le está mostrando cómo vincular estos métodos estáticos desde un objeto instanciado. Esto es especialmente útil en entornos como Node.js donde, usando require, es posible que no tenga acceso directo al constructor original. Lo único que agregaría esthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford el
1
Absolutamente increíble, incluso funciona dentro de un constructor de guiones de café: constructor: (a) -> @constructor.add @(bueno casi, de todos modos)
Orwellophile
31

Aquí hay un buen ejemplo para demostrar cómo funciona Javascript con variables y métodos estáticos / de instancia.

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
fuente
Buen punto: Esto podría ser extraño no tener acceso a la función estática a través this.
TrapII
Gracias por la solución, esto es lo que estaba buscando en qué situación habrá acceso a la thispalabra clave
santhosh
30

Además, ahora es posible hacer con classystatic

'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'
Dima Fomin
fuente
Esta es la mejor respuesta, ya que los ejemplos valen más que mil palabras. Sin embargo, no explica por qué a.talk()no funciona. La respuesta aceptada dice que la cadena de prototipos debería encontrarlo, ¿verdad? Pero no es el caso
Pynchia
11

Yo uso espacios de nombres:

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

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

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

Y para usarlo:

Foo.Talk("Testing");

O

Foo.ChangeElement();
A-Sharabiani
fuente
6

ES6 admite ahora classy staticpalabras clave como un 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
fuente
Estaba buscando una respuesta como esta. ¿Puede el método estático llamar a métodos / variables no estáticos?
Tomasz Mularczyk
1
Los métodos estáticos de @Tomasz no tendrán "esto" establecido en ninguna instancia de la clase, sino más bien la clase misma. Por supuesto, un método estático puede llamar a un método de instancia, pero solo si de alguna manera tiene acceso a una instancia, como 'static staticMethod () {new Foo (). Talk (); } ´
JHH
3

Si tiene que escribir métodos estáticos en ES5, encontré un gran tutorial para eso:

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

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

Combinar
fuente
2

Solo notas adicionales. Usando la clase ES6, cuando creamos métodos estáticos ... el motor Javacsript establece el atributo del descriptor un poco diferente del método "estático" de la vieja escuela

function Car() {

}

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

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

establece el atributo interno (propiedad del descriptor) para brand () en

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

}
..

comparado con

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

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

que establece el atributo interno para brand () en

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

..

vea que enumerable se establece en falso para el método estático en ES6.

significa que no puede usar el bucle for-in para verificar el objeto

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

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

El método estático en ES6 se trata como la propiedad privada de otra clase (nombre, longitud, constructor), excepto que el método estático todavía se puede escribir, por lo que el descriptor de escritura se establece en verdadero { writable: true } . también significa que podemos anularlo

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

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

Cuando se intenta llamar Foo.talk, el JS intenta buscar una función talka través __proto__y, por supuesto, no puede ser encontrado.

Foo.__proto__es Function.prototype.

Issac Young
fuente
1

Las llamadas a métodos estáticos se realizan directamente en la clase y no son invocables en instancias de la clase. Los métodos estáticos se utilizan a menudo para crear funciones de utilidad.

Descripción bastante clara

Tomado directamente de mozilla.org

Foo debe estar vinculado a su clase. Luego, cuando cree una nueva instancia, puede llamar a myNewInstance.foo () Si importa su clase, puede llamar a un método estático

Dave Keane
fuente
0

Cuando me enfrenté a tal situación, hice algo como esto:

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

así que ahora puedo llamar al método de información como Logger.info("my Msg", "Tag");

Vishnu
fuente
Hago esto todo el tiempo, pero básicamente es solo espacio de nombres. ¿No te permite crear instancias con vars de instancia?
dcsan
0

En su caso, si desea Foo.talk():

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

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

Pero es una forma ineficiente de implementar, usar prototypees mejor.


Otra forma, My way se define como clase estática:

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

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

La clase estática anterior no necesita usar prototypeporque solo se construirá una vez como uso estático.

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

Nick Tsai
fuente
@jvitoroc ¡Gracias!
Nick Tsai
0

Javascript no tiene clases reales, sino que utiliza un sistema de herencia prototípica en el que los objetos 'heredan' de otros objetos a través de su cadena de prototipo. Esto se explica mejor a través del código en sí:

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
fuente