Phương thức lớp so với phương thức tĩnh trong JavaScript


262

Tôi biết điều này sẽ làm việc:

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

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

Nhưng nếu tôi muốn gọi

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

Tôi tìm thấy một số phương pháp để làm Foo.talkviệc,

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

Có những cách khác để làm điều này? Tôi không biết liệu có đúng không. Bạn có sử dụng các phương thức lớp hoặc phương thức tĩnh trong mã JavaScript của mình không?


14
Foo.talk = function ...
Tối ưu hóa sớm

1
@downvoterstepintothelight Việc Foo.walk = function() {}này sẽ không ảnh hưởng đến các thể hiện của nó, vì nó không nằm trong chuỗi nguyên mẫu. Có trình duyệt chéo nào là một phương pháp để làm cho một chức năng [[prototype]]hướng đến chức năng của nó prototypekhông?
mất

3
có lẽ tôi không biết bạn muốn gì, bởi vì các phương thức lớp không ảnh hưởng đến các thể hiện theo định nghĩa.
Tối ưu hóa sớm

@downvoterstepintothelight Tôi nghi ngờ rằng, phương thức , trong ngôn ngữ như python, một thể hiện có thể gọi phương thức lớp của nó, sự khác biệt là thiscon trỏ.
Lostyzd

Câu trả lời:


410

Trước hết, hãy nhớ rằng JavaScript chủ yếu là ngôn ngữ nguyên mẫu , thay vì ngôn ngữ dựa trên lớp 1 . Fookhông phải là một lớp, nó là một hàm, là một đối tượng. Bạn có thể khởi tạo một đối tượng từ hàm đó bằng cách sử dụng newtừ khóa cho phép bạn tạo một cái gì đó tương tự như một lớp trong ngôn ngữ OOP tiêu chuẩn.

Tôi khuyên bạn nên bỏ qua __proto__hầu hết thời gian vì nó có hỗ trợ trình duyệt chéo kém, và thay vào đó tập trung vào tìm hiểu về cách thức prototypehoạt động.

Nếu bạn có một thể hiện của một đối tượng được tạo từ hàm 2 và bạn truy cập một trong các thành viên của nó (phương thức, thuộc tính, thuộc tính, hằng số, v.v.), truy cập sẽ chảy xuống cấu trúc phân cấp nguyên mẫu cho đến khi (a) tìm thấy thành viên, hoặc (b) không tìm thấy nguyên mẫu khác.

Hệ thống phân cấp bắt đầu trên đối tượng được gọi, và sau đó tìm kiếm đối tượng nguyên mẫu của nó. Nếu đối tượng nguyên mẫu có một nguyên mẫu, nó lặp lại, nếu không có nguyên mẫu nào tồn tại, undefinedđược trả về.

Ví dụ:

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

Có vẻ như tôi ít nhất đã hiểu được những phần "cơ bản" này, nhưng tôi cần phải làm cho chúng rõ ràng chỉ để chắc chắn.

Trong JavaScript, mọi thứ đều là đối tượng 3 .

tất cả mọi thứ là một đối tượng.

function Foo(){}không chỉ định nghĩa một hàm mới, nó định nghĩa một đối tượng hàm mới có thể được truy cập bằng cách sử dụng Foo.

Đây là lý do tại sao bạn có thể truy cập Foonguyên mẫu của Foo.prototype.

Những gì bạn cũng có thể làm là thiết lập nhiều chức năng hơn trên Foo:

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

Chức năng mới này có thể được truy cập bằng cách sử dụng:

Foo.talk();

Tôi hy vọng bây giờ bạn nhận thấy sự tương đồng giữa các hàm trên một đối tượng hàm và một phương thức tĩnh.

Hãy nghĩ về f = new Foo();việc tạo một thể hiện của lớp, Foo.prototype.bar = function(){...}như định nghĩa một phương thức chia sẻ cho lớp và Foo.baz = function(){...}như định nghĩa một phương thức tĩnh công khai cho lớp.


ECMAScript 2015 đã giới thiệu nhiều loại đường cú pháp cho các loại khai báo này để làm cho chúng đơn giản hơn để thực hiện trong khi cũng dễ đọc hơn. Ví dụ trước có thể được viết là:

class Foo {
  bar() {...}

  static baz() {...}
}

cho phép barđược gọi là:

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

bazđược gọi là:

Foo.baz()

1: classlà "Từ dành riêng trong tương lai" trong đặc tả ECMAScript 5 , nhưng ES6 giới thiệu khả năng xác định các lớp bằng classtừ khóa.

2: về cơ bản là một thể hiện lớp được tạo bởi một nhà xây dựng, nhưng có nhiều khác biệt về sắc thái mà tôi không muốn đánh lừa bạn

3: các giá trị nguyên thủyundefined , bao gồm , nullbooleans, số và chuỗi, không phải là đối tượng kỹ thuật vì chúng là các triển khai ngôn ngữ cấp thấp. Booleans, số và chuỗi vẫn tương tác với chuỗi nguyên mẫu như thể chúng là các đối tượng, vì vậy với mục đích của câu trả lời này, việc xem chúng là "đối tượng" dễ dàng hơn mặc dù chúng không hoàn toàn.


1
@lostyzd - tốt, họ có thể truy cập nó, thông qua Foo.talk(). Bạn có thể gán nó trong hàm tạo, nếu bạn muốn: this.talk = Foo.talk- hoặc, như bạn lưu ý, bằng cách gán Foo.prototype.talk = Foo.talk. Nhưng tôi không chắc đây là một ý tưởng hay - về nguyên tắc, các phương thức thể hiện phải cụ thể theo thể hiện.
nrabinowitz

2
@Doug Avery, Foo.talk()chỉ gọi một hàm được đặt tên. Bạn sẽ sử dụng nó trong các tình huống tương tự như cách các phương thức tĩnh được gọi trong các ngôn ngữ OOP như Java / C #. Một ví dụ tốt về trường hợp sử dụng sẽ là một hàm như thế nào Array.isArray().
zzzzBov

7
PS null là loại đối tượng null == 'object'
mvladk

1
Điểm cơ bản mà bạn đang thiếu là các phương thức tĩnh được kế thừa. Foo.talk = function ()...sẽ không có sẵn cho các lớp con trên đó có tên lớp riêng. Điều này có thể được giải quyết bằng cách "mở rộng" các lớp con, nhưng tôi vẫn đang tìm kiếm một cách thanh lịch hơn.

1
@nus, chỉ một số ngôn ngữ cho phép các phương thức tĩnh được kế thừa. Nếu kế thừa là mong muốn, bạn không nên sử dụng các phương thức tĩnh để bắt đầu.
zzzzBov

67

Bạn có thể đạt được nó như sau:

function Foo() {};

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

Bây giờ bạn có thể gọi chức năng "nói chuyện" như dưới đây:

Foo.talk();

Bạn có thể làm điều này bởi vì trong JavaScript, các hàm cũng là các đối tượng.


37

Gọi một phương thức tĩnh từ một thể hiện:

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

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

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

Dự án lớp Javascript đơn giản: https://github.com/reduardo7/sjsClass


13
Đây không phải là một cuộc gọi tĩnh. var obj = new Clazz (); tạo ra một mới dụ của Clazz. Tuy nhiên, Clazz.staticMethod () đạt được kết quả mà không cần tất cả những thứ khác.
mpemburn

5
@mpemburn: Eduardo cũng đúng trong câu trả lời của mình. Những gì anh ấy chỉ cho bạn không chỉ là bạn có thể gọi phương thức tĩnh từ "bên ngoài" thông qua Clazz.staticMethodmà anh ấy còn chỉ cho bạn cách liên kết với các phương thức tĩnh này từ bên trong một đối tượng được khởi tạo. Điều này đặc biệt hữu ích trong các môi trường như Node.js, khi sử dụng yêu cầu, bạn có thể không có quyền truy cập trực tiếp vào hàm tạo ban đầu. Điều duy nhất tôi muốn thêm làthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford

1
Hoàn toàn tuyệt vời, thậm chí hoạt động bên trong một nhà xây dựng kịch bản cà phê: constructor: (a) -> @constructor.add @(gần như, dù sao đi nữa)
Orwellophile

31

Dưới đây là một ví dụ tốt để chứng minh cách Javascript hoạt động với các biến và phương thức tĩnh / thể hiện.

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

Điểm tốt: Điều này có thể là lạ khi không có quyền truy cập vào chức năng tĩnh thông qua this.
BẫyII

Cảm ơn giải pháp đây là những gì tôi đã tìm kiếm trong tình huống nào sẽ có quyền truy cập thistừ khóa
santhosh

30

Ngoài ra, bây giờ có thể làm với classstatic

'use strict'

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

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

};

sẽ cho

var a = new Foo();
Foo.talk();  // 'talk'
a.talk();    // err 'is not a function'
a.speak();   // 'speak'
Foo.speak(); // err 'is not a function'

Đây là câu trả lời tốt nhất, vì các ví dụ có giá trị bằng một ngàn từ. Tuy nhiên, nó không giải thích tại sao a.talk()không hoạt động. Câu trả lời được chấp nhận nói rằng chuỗi nguyên mẫu sẽ tìm thấy nó, phải không? Nhưng nó không phải là trường hợp
Pynchia

11

Tôi sử dụng không gian tên:

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

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

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

Và để sử dụng nó:

Foo.Talk("Testing");

Hoặc là

Foo.ChangeElement();

6

ES6 hỗ trợ ngay bây giờ classstaticcác từ khóa như một cơ duyên:

class Foo {
    constructor() {}

    talk() {
        console.log("i am not static");
    }

    static saying() {
        console.log(this.speech);
    }

    static get speech() {
        return "i am static method";
    }

}

Tôi đã tìm kiếm câu trả lời như thế này. Phương thức tĩnh có thể gọi các phương thức / biến không tĩnh không?
Tomasz Mularchot

1
Các phương thức tĩnh của @Tomasz sẽ không được cài đặt cho bất kỳ trường hợp nào của lớp, mà là chính lớp đó. Vì vậy, tất nhiên, một phương thức tĩnh có thể gọi một phương thức cá thể, nhưng chỉ khi nó bằng cách nào đó có quyền truy cập vào một cá thể, chẳng hạn như odestatic staticMethod () {new Foo (). Talk (); }
Ạc

3

Nếu bạn phải viết các phương thức tĩnh trong ES5, tôi đã tìm thấy một hướng dẫn tuyệt vời cho điều đó:

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

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


2

Chỉ cần ghi chú bổ sung. Sử dụng lớp ES6, Khi chúng ta tạo các phương thức tĩnh..cơ công cụ Javacsript đặt thuộc tính mô tả một chút khác với phương thức "tĩnh" của trường học cũ

function Car() {

}

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

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

nó đặt thuộc tính bên trong (thuộc tính mô tả) cho brand () thành

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

}
..

so với

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

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

đặt thuộc tính bên trong cho nhãn hiệu () thành

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

..

thấy rằng liệt kê được đặt thành false cho phương thức tĩnh trong ES6.

nó có nghĩa là bạn không thể sử dụng vòng lặp for-in để kiểm tra đối tượng

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

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

Phương thức tĩnh trong ES6 được xử lý như thuộc tính riêng của lớp khác (tên, độ dài, hàm tạo) ngoại trừ phương thức tĩnh vẫn có thể ghi được do đó mô tả có thể ghi được được đặt thành đúng { writable: true } . nó cũng có nghĩa là chúng ta có thể ghi đè lên nó

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

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

1

Khi bạn cố gắng gọi Foo.talk, JS cố gắng tìm kiếm một chức năng talkthông qua __proto__và tất nhiên, nó không thể được tìm thấy.

Foo.__proto__Function.prototype.


1

Các cuộc gọi phương thức tĩnh được thực hiện trực tiếp trên lớp và không thể gọi được trong các phiên bản của lớp. Các phương thức tĩnh thường được sử dụng để tạo chức năng tiện ích

Mô tả khá rõ ràng

Lấy trực tiếp từ mozilla.org

Foo cần được ràng buộc với lớp của bạn Sau đó, khi bạn tạo một cá thể mới, bạn có thể gọi myNewInstance.foo () Nếu bạn nhập lớp của mình, bạn có thể gọi một phương thức tĩnh


0

Khi tôi đối mặt với một tình huống như vậy, tôi đã làm một cái gì đó như thế này:

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

vì vậy bây giờ tôi có thể gọi phương thức thông tin là Logger.info("my Msg", "Tag");


Tôi làm điều này mọi lúc, nhưng về cơ bản nó chỉ là không gian tên. Nó không cho phép bạn tạo cá thể với vars dụ?
dcsan

0

Trong trường hợp của bạn, nếu bạn muốn Foo.talk():

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

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

Nhưng đó là một cách không hiệu quả để thực hiện, sử dụng prototypelà tốt hơn.


Một cách khác, cách của tôi được định nghĩa là lớp tĩnh:

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

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

Lớp tĩnh trên không cần sử dụng prototypevì nó sẽ chỉ được xây dựng một lần dưới dạng sử dụng tĩnh.

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


@jvitoroc Cảm ơn!
Nick Tsai

0

Javascript không có các lớp thực tế thay vào đó nó sử dụng một hệ thống kế thừa nguyên mẫu trong đó các đối tượng 'kế thừa' từ các đối tượng khác thông qua chuỗi nguyên mẫu của chúng. Điều này được giải thích tốt nhất thông qua chính mã:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.