Metoda klasowa a statyczna w JavaScript

262

Wiem, że to zadziała:

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

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

Ale jeśli chcę zadzwonić

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

Znajduję metody na Foo.talkpracę,

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

Czy są na to inne sposoby? Nie wiem, czy jest to właściwe. Czy używasz metod klasowych lub statycznych w kodzie JavaScript?

lostyzd
źródło
14
Foo.talk = function ...
Optymalizacja przedwczesna
1
@downvoterstepintothelight Foo.walk = function() {}Nie wpłynie na jego wystąpienia, ponieważ nie ma go w łańcuchu prototypów. Czy istnieje przeglądarka internetowa, która pozwala [[prototype]]wskazać funkcję na jej funkcję prototype?
lostyzd
3
prawdopodobnie nie mam pojęcia, czego chcesz, ponieważ metody klasowe z definicji nie wpływają na instancje .
Optymalizacja przedwczesna
@downvoterstepintothelight Wątpię, aby w metodzie takiej jak python instancja mogła wywołać metodę klasową, różnica jest thiswskaźnikiem.
lostyzd

Odpowiedzi:

410

Po pierwsze, pamiętaj, że JavaScript to przede wszystkim język prototypowy , a nie język oparty na klasach 1 . Foonie jest klasą, to funkcja, która jest przedmiotem. Możesz utworzyć obiekt z tej funkcji za pomocą newsłowa kluczowego, które pozwoli ci stworzyć coś podobnego do klasy w standardowym języku OOP.

Sugeruję zignorowanie przez __proto__większość czasu, ponieważ ma słabą obsługę wielu przeglądarek, a zamiast tego skup się na nauczeniu się, jak prototypedziała.

Jeśli masz instancję obiektu utworzoną z funkcji 2 i uzyskujesz dostęp do jednego z jego elementów (metod, atrybutów, właściwości, stałych itp.), Dostęp będzie spływał w dół hierarchii prototypów, dopóki (a) nie znajdzie członek lub (b) nie znajduje innego prototypu.

Hierarchia rozpoczyna się od wywołanego obiektu, a następnie wyszukuje obiekt prototypowy. Jeśli prototypowy obiekt ma prototyp, powtarza się, jeśli prototyp nie istnieje, undefinedjest zwracany.

Na przykład:

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

Wydaje mi się, że przynajmniej trochę zrozumiałeś już te „podstawowe” części, ale muszę je wyrazić, żeby się upewnić.

W JavaScript wszystko jest przedmiotem 3 .

wszystko jest przedmiotem.

function Foo(){} nie tylko definiuje nową funkcję, ale definiuje nowy obiekt funkcji, do którego można uzyskać dostęp za pomocą Foo .

Dlatego możesz uzyskać dostęp do Fooprototypu za pomocą Foo.prototype.

Możesz także ustawić więcej funkcji na Foo:

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

Dostęp do tej nowej funkcji można uzyskać za pomocą:

Foo.talk();

Mam nadzieję, że już teraz zauważasz podobieństwo między funkcjami na obiekcie funkcji a metodą statyczną.

Pomyśl o f = new Foo();tworzeniu instancji klasy, Foo.prototype.bar = function(){...}o definiowaniu wspólnej metody dla klasy oraz Foo.baz = function(){...}o definiowaniu publicznej metody statycznej dla klasy.


ECMAScript 2015 wprowadził szereg cukrów składniowych do tego rodzaju deklaracji, aby ułatwić ich wdrożenie, a jednocześnie ułatwić czytanie. Poprzedni przykład można zatem zapisać jako:

class Foo {
  bar() {...}

  static baz() {...}
}

co pozwala barnazywać się jako:

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

i bazbyć nazywany:

Foo.baz()

1: classbyło „przyszłym zastrzeżonym słowem” w specyfikacji ECMAScript 5 , ale ES6 wprowadza możliwość definiowania klas za pomocą classsłowa kluczowego.

2: zasadniczo instancja klasy utworzona przez konstruktora, ale istnieje wiele niuansowych różnic, których nie chcę was wprowadzać w błąd

3: prymitywne wartości - które obejmują undefined, nulllogiczne, liczby i łańcuchy - nie są technicznie obiektami, ponieważ są implementacjami języka niskiego poziomu. Wartości logiczne, liczby i łańcuchy nadal oddziałują z łańcuchem prototypowym, jakby były obiektami, dlatego dla celów tej odpowiedzi łatwiej jest uznać je za „obiekty”, nawet jeśli nie są całkiem.

zzzzBov
źródło
1
@lostyzd - cóż, mogą uzyskać do nich dostęp za pośrednictwem Foo.talk(). Możesz to przypisać w konstruktorze, jeśli chcesz: this.talk = Foo.talk- lub, jak zauważysz, przypisując Foo.prototype.talk = Foo.talk. Ale nie jestem pewien, czy to dobry pomysł - z zasady metody instancji powinny być specyficzne dla instancji.
nrabinowitz
2
@Doug Avery, Foo.talk()po prostu wywołuje funkcję z przestrzenią nazw. Używałbyś go w sytuacjach podobnych do wywoływania metod statycznych w językach OOP, takich jak Java / C #. Dobrym przykładem przypadku użycia może być funkcja taka jak Array.isArray().
zzzzBov
7
PS null jest typem obiektu null == 'object'
mvladk
1
Podstawowym punktem, którego wszyscy brakuje, jest odziedziczenie metod statycznych. Foo.talk = function ()...nie będą dostępne dla podklas dla własnej nazwy klasy. Można to obejść poprzez „rozszerzanie” podklas, ale wciąż szukam bardziej eleganckiego sposobu.
1
@ nus, tylko niektóre języki pozwalają na dziedziczenie metod statycznych. Jeśli pożądane jest dziedziczenie, nie powinieneś używać metod statycznych.
zzzzBov
67

Możesz to osiągnąć jak poniżej:

function Foo() {};

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

Możesz teraz wywołać funkcję „mów”, jak poniżej:

Foo.talk();

Możesz to zrobić, ponieważ w JavaScript funkcje są również obiektami.

Bipul
źródło
37

Wywołaj metodę statyczną z instancji:

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

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

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

Prosty projekt klasy Javascript: https://github.com/reduardo7/sjsClass

Eduardo Cuomo
źródło
13
To nie jest wywołanie statyczne. var obj = new Clazz (); tworzy nową instancję Clazz. Jednak Clazz.staticMethod () osiąga wynik bez wszystkich innych rzeczy.
mpemburn
5
@mpemburn: Eduardo ma również poprawną odpowiedź. To, co pokazuje, to nie tylko można wywołać metodę statyczną „z zewnątrz”, Clazz.staticMethodale pokazuje on, jak połączyć się z tymi metodami statycznymi z obiektu utworzonego. Jest to szczególnie przydatne w środowiskach takich jak Node.js, w których użycie wymaga, możesz nie mieć bezpośredniego dostępu do oryginalnego konstruktora. Jedyne, co chciałbym dodać tothis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford,
1
Absolutnie niesamowite, działa nawet wewnątrz konstruktora skryptów do kawy: constructor: (a) -> @constructor.add @(i tak
właściwie
31

Oto dobry przykład pokazujący, jak JavaScript działa ze zmiennymi i metodami static / 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
źródło
Dobra uwaga: dziwne może być brak dostępu do funkcji statycznej this.
TrapII
Dzięki za rozwiązanie, właśnie tego szukałem, w której sytuacji będzie dostęp do thissłowa kluczowego
santhosh
30

Dodatkowo można teraz korzystać z classistatic

'use strict'

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

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

};

da

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
źródło
To najlepsza odpowiedź, ponieważ przykłady są warte tysiąca słów. Jednak nie wyjaśnia, dlaczego a.talk()nie działa. Przyjęta odpowiedź mówi, że łańcuch prototypów powinien ją znaleźć, prawda? Ale tak nie jest
Pynchia
11

Używam przestrzeni nazw:

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

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

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

I aby go użyć:

Foo.Talk("Testing");

Lub

Foo.ChangeElement();
A-Sharabiani
źródło
6

ES6 obsługuje teraz classi staticsłowa kluczowe takie jak urok:

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
źródło
Szukałem takiej odpowiedzi. Czy metoda statyczna może wywoływać metody / zmienne niestatyczne?
Tomasz Mularczyk,
1
@Tomasz metody statyczne nie ustawią „tego” na żadną instancję klasy, ale na samą klasę. Oczywiście metoda statyczna może wywołać metodę instancji, ale tylko wtedy, gdy w jakiś sposób ma dostęp do instancji, na przykład ´static staticMethod () {new Foo (). Talk (); } ´
JHH
3

Jeśli musisz pisać metody statyczne w ES5, znalazłem świetny samouczek:

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

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

Połączyć
źródło
2

Tylko dodatkowe uwagi. Używając klasy ES6, kiedy tworzymy metody statyczne .. silnik Javacsript ustawił atrybut deskryptora nieco inaczej niż w starej szkole „statycznej”

function Car() {

}

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

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

ustawia wewnętrzny atrybut (właściwość deskryptora) dla brand () na

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

}
..

w porównaniu do

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

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

który ustawia wewnętrzny atrybut dla brand () na

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

..

sprawdź, czy dla parametru enumerable ustawiono wartość false dla metody statycznej w ES6.

oznacza to, że nie można użyć pętli for-in, aby sprawdzić obiekt

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

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

Metoda statyczna w ES6 jest traktowana jak własność prywatna innej klasy (nazwa, długość, konstruktor), z wyjątkiem tego, że metoda statyczna jest nadal zapisywalna, więc zapisywalny deskryptor ma wartość true { writable: true } . oznacza to również, że możemy to zmienić

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

console.log(
  Car2.brand() // is now changed to toyota
);
ngakak
źródło
1

Podczas próby wywołania Foo.talk, JS próbuje szukać funkcję talkprzez __proto__i, oczywiście, nie można go znaleźć.

Foo.__proto__jest Function.prototype.

Issac Young
źródło
1

Wywołania metod statycznych są wykonywane bezpośrednio w klasie i nie można ich wywoływać w instancjach klasy. Do tworzenia funkcji narzędziowej często stosuje się metody statyczne

Całkiem jasny opis

Wykonano bezpośrednio z mozilla.org

Foo musi być związany z twoją klasą. Następnie, kiedy tworzysz nową instancję, możesz wywołać myNewInstance.foo () Jeśli importujesz klasę, możesz wywołać metodę statyczną

Dave Keane
źródło
0

Kiedy spotkałem się z taką sytuacją, zrobiłem coś takiego:

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

więc teraz mogę wywołać metodę info jako Logger.info("my Msg", "Tag");

Wisznu
źródło
Robię to cały czas, ale w zasadzie to tylko przestrzeń nazw. Nie pozwala ci tworzyć instancji za pomocą zmiennych instancji?
dcsan
0

W twoim przypadku, jeśli chcesz Foo.talk():

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

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

Ale jest to nieefektywny sposób wdrożenia, użycie prototypejest lepsze.


Innym sposobem, Mój sposób jest zdefiniowany jako klasa statyczna:

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

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

Powyższa klasa statyczna nie musi być używana, prototypeponieważ zostanie zbudowana tylko raz jako użycie statyczne.

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

Nick Tsai
źródło
@jvitoroc Thanks!
Nick Tsai,
0

JavaScript nie ma żadnych rzeczywistych klas, a raczej korzysta z systemu dziedziczenia prototypowego, w którym obiekty „dziedziczą” po innych obiektach poprzez łańcuch prototypów. Można to najlepiej wyjaśnić za pomocą samego kodu:

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
źródło