Metode kelas vs statis dalam JavaScript

262

Saya tahu ini akan berhasil:

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

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

Tetapi jika saya ingin menelepon

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

Saya menemukan beberapa metode untuk membuat Foo.talkpekerjaan,

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

Apakah ada cara lain untuk melakukan ini? Saya tidak tahu apakah itu benar. Apakah Anda menggunakan metode kelas atau metode statis dalam kode JavaScript Anda?

lostyzd
sumber
14
Foo.talk = function ...
Optimalisasi Dini
1
@ downvoterstepintothelight The Foo.walk = function() {}tidak akan mempengaruhi instansnya , karena tidak pada rantai prototipe. Apakah ada cross-browser metode untuk membuat titik fungsi [[prototype]]ke fungsi prototype?
lostyzd
3
mungkin saya tidak tahu apa yang Anda inginkan, karena metode kelas tidak memengaruhi instans menurut definisi.
Optimalisasi Dini
@ downvoterstepintothelight Saya ragu bahwa, metode , dalam bahasa seperti python, sebuah instance dapat memanggil metode kelasnya, perbedaannya adalah thispointer.
lostyzd

Jawaban:

410

Pertama, ingatlah bahwa JavaScript pada dasarnya adalah bahasa prototipe , bukan bahasa berbasis kelas 1 . Foobukan kelas, ini adalah fungsi, yang merupakan objek. Anda dapat instantiate objek dari fungsi itu menggunakan newkata kunci yang akan memungkinkan Anda untuk membuat sesuatu yang mirip dengan kelas dalam bahasa OOP standar.

Saya sarankan untuk mengabaikan __proto__sebagian besar waktu karena memiliki dukungan browser lintas yang buruk, dan alih-alih fokus pada belajar tentang caranyaprototype kerjanya.

Jika Anda memiliki instance objek yang dibuat dari fungsi 2 dan Anda mengakses salah satu anggotanya (metode, atribut, properti, konstanta dll) dengan cara apa pun, akses akan mengalir ke hierarki prototipe sampai salah satu (a) menemukan anggota, atau (b) tidak menemukan prototipe lain.

Hirarki dimulai pada objek yang dipanggil, dan kemudian mencari objek prototipe. Jika objek prototipe memiliki prototipe, itu akan berulang, jika tidak ada prototipe, undefineddikembalikan.

Sebagai contoh:

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

Bagi saya sepertinya Anda setidaknya sudah memahami bagian-bagian "dasar" ini, tetapi saya perlu membuatnya eksplisit hanya untuk memastikan.

Dalam JavaScript, semuanya adalah objek 3 .

segala sesuatu adalah objek.

function Foo(){} tidak hanya mendefinisikan fungsi baru, ia mendefinisikan objek fungsi baru yang dapat diakses menggunakan Foo .

Inilah sebabnya mengapa Anda dapat mengakses Fooprototipe dengan Foo.prototype.

Yang juga dapat Anda lakukan adalah mengatur lebih banyak fungsi pada Foo:

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

Fungsi baru ini dapat diakses menggunakan:

Foo.talk();

Saya harap sekarang Anda memperhatikan kesamaan antara fungsi pada objek fungsi dan metode statis.

Anggap f = new Foo();sebagai membuat instance kelas, Foo.prototype.bar = function(){...}sebagai mendefinisikan metode bersama untuk kelas, dan Foo.baz = function(){...}sebagai mendefinisikan metode statis publik untuk kelas.


ECMAScript 2015 memperkenalkan beragam gula sintaksis untuk deklarasi semacam ini agar lebih mudah diimplementasikan sementara juga lebih mudah dibaca. Oleh karena itu, contoh sebelumnya dapat ditulis sebagai:

class Foo {
  bar() {...}

  static baz() {...}
}

yang memungkinkan bardisebut sebagai:

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

dan bazdisebut sebagai:

Foo.baz()

1: classadalah "Future Reserved Word" dalam spesifikasi ECMAScript 5 , tetapi ES6 memperkenalkan kemampuan untuk mendefinisikan kelas menggunakan classkata kunci.

2: pada dasarnya instance kelas yang dibuat oleh konstruktor, tetapi ada banyak perbedaan nuansa yang saya tidak ingin menyesatkan Anda

3: nilai-nilai primitif — yang meliputi undefined,, nullboolean, angka, dan string — bukan objek teknis karena mereka adalah implementasi bahasa tingkat rendah. Boolean, angka, dan string masih berinteraksi dengan rantai prototipe seolah-olah mereka objek, jadi untuk keperluan jawaban ini, lebih mudah untuk menganggap mereka "objek" meskipun mereka tidak cukup.

zzzzBov
sumber
1
@lostyzd - baik, mereka dapat mengaksesnya melalui Foo.talk(). Anda bisa menetapkan itu di konstruktor, jika Anda ingin: this.talk = Foo.talk- atau, seperti yang Anda perhatikan, dengan menetapkan Foo.prototype.talk = Foo.talk. Tapi saya tidak yakin ini ide yang bagus - pada prinsipnya, metode instance harus spesifik untuk instance tersebut.
nrabinowitz
2
@ Doug Avery, Foo.talk()hanya memanggil fungsi namespaced. Anda akan menggunakannya dalam situasi yang mirip dengan bagaimana metode statis dipanggil dalam bahasa OOP seperti Java / C #. Contoh kasus penggunaan yang baik adalah fungsi seperti Array.isArray().
zzzzBov
7
PS null adalah typeof null == 'object'
mvladk
1
Poin mendasar yang Anda semua lewatkan adalah metode statis diwariskan. Foo.talk = function ()...tidak akan tersedia untuk subclass di sana nama kelasnya sendiri. Ini bisa diselesaikan dengan subclass "extending", tapi saya juga masih mencari cara yang lebih elegan.
1
@nus, hanya beberapa bahasa yang memungkinkan metode statis diwariskan. Jika warisan diinginkan, Anda seharusnya tidak menggunakan metode statis untuk memulai.
zzzzBov
67

Anda dapat mencapainya seperti di bawah ini:

function Foo() {};

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

Anda sekarang dapat menjalankan fungsi "bicara" seperti di bawah ini:

Foo.talk();

Anda dapat melakukan ini karena dalam JavaScript, fungsi juga merupakan objek.

Bipul
sumber
37

Panggil metode statis dari sebuah instance:

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

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

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

Proyek Kelas Javascript Sederhana: https://github.com/reduardo7/sjsClass

Eduardo Cuomo
sumber
13
Ini bukan panggilan statis. var obj = Clazz baru (); menciptakan instance baru Clazz. Namun, Clazz.staticMethod () mencapai hasilnya tanpa semua hal lainnya.
mpemburn
5
@mpemburn: Eduardo juga benar dalam jawabannya. Apa yang dia tunjukkan kepada Anda tidak hanya dapat Anda panggil metode statis dari "luar" melalui Clazz.staticMethodtetapi ia menunjukkan kepada Anda cara menautkan metode statis ini dari dalam objek yang dipakai. Ini sangat berguna di lingkungan seperti Node.js di mana, menggunakan mengharuskan, Anda mungkin tidak memiliki akses langsung ke konstruktor asli. Satu-satunya hal yang akan saya tambahkan adalahthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford
1
Benar-benar luar biasa, bahkan bekerja di dalam konstruktor skrip kopi: constructor: (a) -> @constructor.add @(yah hampir, pokoknya)
Orwellophile
31

Berikut adalah contoh yang baik untuk menunjukkan bagaimana Javascript bekerja dengan variabel / instance variabel dan metode.

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
sumber
Poin bagus: Ini bisa aneh untuk tidak memiliki akses ke fungsi statis this.
TrapII
Terima kasih atas solusinya, inilah yang saya cari dalam situasi mana akan ada akses thiskata kunci
santhosh
30

Selain itu, sekarang mungkin dilakukan dengan classdanstatic

'use strict'

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

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

};

akan memberi

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
sumber
Ini adalah jawaban terbaik, karena contoh bernilai ribuan kata. Namun, itu tidak menjelaskan mengapa a.talk()tidak berhasil. Jawaban yang diterima mengatakan rantai prototipe harus menemukannya, kan? Tapi itu tidak terjadi
Pynchia
11

Saya menggunakan ruang nama:

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

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

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

Dan untuk menggunakannya:

Foo.Talk("Testing");

Atau

Foo.ChangeElement();
A-Sharabiani
sumber
6

ES6 mendukung sekarang class& statickata kunci seperti pesona:

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
sumber
Saya mencari jawaban seperti ini. Dapatkah metode statis memanggil metode / variabel non-statis?
Tomasz Mularczyk
1
@Tomasz metode statis tidak akan memiliki 'ini' diatur ke instance kelas apa pun, melainkan kelas itu sendiri. Jadi tentu saja, metode statis dapat memanggil metode instance, tetapi hanya jika entah bagaimana memiliki akses ke sebuah instance, seperti ´static staticMethod () {new Foo (). Talk (); } ´
JHH
3

Jika Anda harus menulis metode statis di ES5, saya menemukan tutorial yang bagus untuk itu:

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

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

Menggabungkan
sumber
2

Catatan tambahan saja. Menggunakan kelas ES6, Ketika kita membuat metode statis .. mesin Javacsript mengatur atribut deskriptor sedikit berbeda dari metode "statis" old-school

function Car() {

}

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

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

itu menetapkan atribut internal (properti deskriptor) untuk merek () menjadi

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

}
..

dibandingkan dengan

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

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

yang menetapkan atribut internal untuk merek () ke

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

..

lihat bahwa enumerable disetel ke false untuk metode statis di ES6.

itu berarti Anda tidak dapat menggunakan loop masuk untuk memeriksa objek

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

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

Metode statis di ES6 diperlakukan seperti milik pribadi kelas lain (nama, panjang, konstruktor) kecuali bahwa metode statis masih dapat ditulis sehingga deskriptor yang dapat ditulis diatur ke true { writable: true } . itu juga berarti bahwa kita dapat menimpanya

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

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

Ketika Anda mencoba menelepon Foo.talk, JS mencoba mencari fungsi talkmelalui __proto__dan, tentu saja, itu tidak dapat ditemukan.

Foo.__proto__adalah Function.prototype.

Issac Young
sumber
1

Panggilan metode statis dilakukan langsung di kelas dan tidak dapat dipanggil pada instance kelas. Metode statis sering digunakan untuk membuat fungsi utilitas

Deskripsi yang cukup jelas

Diambil Langsung dari mozilla.org

Foo perlu terikat ke kelas Anda Kemudian ketika Anda membuat contoh baru Anda dapat memanggil myNewInstance.foo () Jika Anda mengimpor kelas Anda, Anda dapat memanggil metode statis

Dave Keane
sumber
0

Ketika saya menghadapi situasi seperti itu, saya telah melakukan sesuatu seperti ini:

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

jadi sekarang saya dapat memanggil metode info sebagai Logger.info("my Msg", "Tag");

Wisnu
sumber
Saya melakukan ini sepanjang waktu, tetapi pada dasarnya hanya namespacing. Itu tidak memungkinkan Anda untuk membuat instance dengan vars instance?
dcsan
0

Dalam kasus Anda, jika Anda ingin Foo.talk():

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

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

Tapi ini cara yang tidak efisien untuk diterapkan, menggunakan prototypelebih baik.


Cara lain, Cara saya didefinisikan sebagai kelas statis:

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

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

Kelas statis di atas tidak perlu digunakan prototypekarena hanya akan dibangun sekali sebagai penggunaan statis.

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

Nick Tsai
sumber
@ jvitoroc Terima kasih!
Nick Tsai
0

Javascript tidak memiliki kelas yang sebenarnya melainkan menggunakan sistem pewarisan prototypal di mana objek 'mewarisi' dari objek lain melalui rantai prototipe mereka. Ini paling baik dijelaskan melalui kode itu sendiri:

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
sumber