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?


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.


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.


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


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

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'

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

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

}

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/


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
);

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.


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ą


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


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


@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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.