Клас проти статичного методу в JavaScript


262

Я знаю, що це спрацює:

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

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

Але якщо я хочу зателефонувати

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

Я знаходжу деякі методи, щоб зробити Foo.talkроботу,

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

Чи є інші способи зробити це? Я не знаю, чи правильно це робити. Чи використовуєте у своєму коді JavaScript методи класу чи статичні методи?


14

1
@downvoterstepintothelight Foo.walk = function() {}Не вплине на його екземпляри, оскільки це не в ланцюзі прототипу. Чи існує крос-браузерний метод зробити [[prototype]]точку функції на її prototype?
lostyzd

3
напевно, я поняття не маю, чого ви хочете, тому що методи класу не впливають на екземпляри за визначенням.
Передчасна оптимізація

@downvoterstepintothelight Я сумніваюся, що метод таким мовою, як python, екземпляр може викликати метод свого класу, різниця - thisпокажчик.
lostyzd

Відповіді:


410

По-перше, пам’ятайте, що JavaScript - це передусім прототипна мова , а не мова на основі класу 1 . Fooце не клас, це функція, яка є об'єктом. Ви можете створити об'єкт із цієї функції за допомогою newключового слова, яке дозволить вам створити щось подібне до класу на стандартній мові OOP.

Я б запропонував ігнорувати __proto__більшу частину часу, тому що він має погану підтримку перехресного веб-переглядача, а замість цього зосередитись на вивченні того, як prototypeпрацює.

Якщо у вас є примірник об'єкта, створеного з функції 2, і ви будь-яким чином отримуєте доступ до одного з його членів (методів, атрибутів, властивостей, констант тощо), доступ буде протікати вниз по ієрархії прототипу, поки він (a) не знайде член, або (b) не знайде іншого прототипу.

Ієрархія запускається на об'єкт, який був викликаний, а потім здійснює пошук його об’єкта-прототипу. Якщо об'єкт прототипу має прототип, він повторюється, якщо прототип не існує, undefinedповертається.

Наприклад:

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

Мені здається, ви вже хоча б дещо зрозуміли ці "основні" частини, але мені потрібно зробити їх явними лише для того, щоб бути впевненим.

У JavaScript все є об'єктом 3 .

все є об’єктом.

function Foo(){}не просто визначає нову функцію, вона визначає новий об’єкт функції, до якого можна отримати доступ Foo.

Ось чому ви можете отримати доступ Fooдо прототипу за допомогою Foo.prototype.

Те , що ви також можете зробити , це встановити додаткові функції по Foo:

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

Доступ до цієї нової функції можна за допомогою:

Foo.talk();

Я сподіваюся, що зараз ви помітили подібність між функціями на об’єкті функції та статичним методом.

Розгляньте це f = new Foo();як створення екземпляра класу, Foo.prototype.bar = function(){...}як визначення загального методу для класу та Foo.baz = function(){...}як визначення публічного статичного методу для класу.


ECMAScript 2015 представила різноманітні синтаксичні цукру для таких видів декларацій, щоб зробити їх більш простими в застосуванні, а також було легше читати. Тому попередній приклад можна записати як:

class Foo {
  bar() {...}

  static baz() {...}
}

що дозволяє barназиватися як:

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

і bazназиватися так:

Foo.baz()

1: classбуло "Майбутнім зарезервованим словом" у специфікації ECMAScript 5 , але ES6 запроваджує можливість визначати класи за допомогою classключового слова.

2: по суті, екземпляр класу, створений конструктором, але є багато нюансованих відмінностей, які я не хочу вас вводити в оману

3: примітивні значення - що включають undefined, nullбулеві числа, числа та рядки - технічно не є об'єктами, оскільки вони є мовними реалізаціями низького рівня. Булеви, числа та рядки все ще взаємодіють з ланцюгом прототипу так, ніби вони були об'єктами, тому для цілей цієї відповіді легше вважати їх "об'єктами", хоча вони не зовсім.


1
@lostyzd - ну, вони можуть отримати доступ до нього, через Foo.talk(). Ви можете призначити це в конструкторі, якщо хочете: this.talk = Foo.talk- або, як зазначаєте, шляхом призначення Foo.prototype.talk = Foo.talk. Але я не впевнений, що це гарна ідея - в принципі, методи екземпляра повинні бути специфічними для цього примірника.
nrabinowitz

2
@Doug Avery, Foo.talk()просто викликає функцію простору імен. Ви можете використовувати його в ситуаціях, подібних до того, як статичні методи викликаються мовами OOP, такими як Java / C #. Хорошим прикладом випадку використання буде така функція Array.isArray().
zzzzBov

7
PS null є об'єктом typeof null == 'object'
mvladk

1
Принциповий момент, який вам все не вистачає, - це те, що статичні методи передаються у спадок. Foo.talk = function ()...не буде доступним для підкласів на власному імені класу. Це можна вирішити шляхом "розширення" підкласів, але я все ще шукаю більш елегантний спосіб.

1
@nus, лише деякі мови дозволяють успадковувати статичні методи. Якщо спадщина бажана, вам не слід використовувати статичні методи для початку.
zzzzBov

67

Ви можете досягти цього, як показано нижче:

function Foo() {};

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

Тепер ви можете викликати функцію "розмови", як показано нижче:

Foo.talk();

Це можна зробити, оскільки в JavaScript функції також є об'єктами.


37

Виклик статичного методу з екземпляра:

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

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

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

Простий проект класу Javascript: https://github.com/reduardo7/sjsClass


13
Це не статичний дзвінок. var obj = новий Clazz (); створює новий екземпляр Клаза. Однак Clazz.staticMethod () досягає результату без усього іншого.
mpemburn

5
@mpemburn: Едуардо також правильний у своїй відповіді. Те, що він вам показує, це не тільки ви можете викликати статичний метод ззовні "через", Clazz.staticMethodале він показує вам, як зв’язатись із цими статичними методами всередині об'єкта, що створюється. Це особливо корисно в таких програмах, як Node.js, де, використовуючи вимогу, ви не можете мати прямий доступ до оригінального конструктора. Єдине, що я хотів би додати,this.constructor.staticMethod.apply(this, arguments);
Маувіс Ледфорд,

1
Абсолютно приголомшливий, навіть працює всередині конструктора сценаріїв з кавою: constructor: (a) -> @constructor.add @(ну майже так чи інакше)
Orwellophile

31

Ось хороший приклад, щоб продемонструвати, як Javascript працює зі статичними / екземплярами змінних та методів.

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

Хороший момент: Це може бути дивно не мати доступ до статичної функції через this.
TrapII

Дякую за рішення, це те, що я шукав, в якій ситуації буде доступ до thisключового слова
santhosh,

30

У доповненнях, тепер це можливо зробити з classіstatic

'use strict'

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

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

};

дасть

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

Це найкраща відповідь, оскільки приклади варті тисячі слів. Однак це не пояснює, чому a.talk()це не працює. Прийнята відповідь говорить, що ланцюжок прототипів повинен її знайти, правда? Але це не так
Pynchia

11

Я використовую простори імен:

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

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

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

І використовувати його:

Foo.Talk("Testing");

Або

Foo.ChangeElement();

6

ES6 підтримує зараз class& staticключові слова, як шарм:

class Foo {
    constructor() {}

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

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

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

}

Я шукав відповідь так. Чи може статичний метод викликати нестатичні методи / змінні?
Томаш Муларчик

1
Статичні методи @Tomasz не матимуть «встановленого» жодного примірника класу, а навпаки, самого класу. Так що, звичайно, статичний метод може викликати метод екземпляра, але лише в тому випадку, якщо він якось має доступ до екземпляра, наприклад, ´static staticMethod () {new Foo (). Talk (); } ´
JHH

3

Якщо вам доведеться писати статичні методи в ES5, я знайшов для цього чудовий підручник:

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

дивіться @ https://abdulapopoola.com/2013/03/30/static-and-instance-methods-in-javascript/


2

Просто додаткові замітки. Використовуючи клас ES6, коли ми створюємо статичні методи. Двигун Javacsript встановлює атрибут дескриптора біт lil, який відрізняється від "статичного" методу старої школи

function Car() {

}

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

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

він встановлює внутрішній атрибут (властивість дескриптора) для бренду () до

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

}
..

у порівнянні з

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

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

що встановлює внутрішній атрибут для бренду () до

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

..

бачимо це незліченно для статичного методу в ES6 для встановлено значення false .

це означає, що ви не можете використовувати цикл for-in для перевірки об'єкта

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

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

статичний метод в ES6 трактується як приватна власність іншого класу (ім'я, довжина, конструктор), за винятком того, що статичний метод все ще записується, тому дескриптор, що записується , встановлений на істинне { writable: true } . це також означає, що ми можемо її перекрити

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

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

1

Коли ви намагаєтеся викликати Foo.talk, то JS намагається знайти функцію talkчерез __proto__та, звичайно ж , він не може бути знайдений.

Foo.__proto__є Function.prototype.


1

Виклики статичного методу здійснюються безпосередньо в класі і не можуть викликатись в екземплярах класу. Статичні методи часто використовуються для створення корисної функції

Досить чіткий опис

Взято безпосередньо з mozilla.org

Foo потрібно прив’язати до вашого класу. Тоді, коли ви створюєте новий екземпляр, ви можете зателефонувати myNewInstance.foo () Якщо ви імпортуєте свій клас, ви можете викликати статичний метод


0

Коли я стикався з такою ситуацією, я зробив щось подібне:

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

тож тепер я можу зателефонувати як інфо-метод Logger.info("my Msg", "Tag");


Я роблю це постійно, але це в основному просто простір імен. Це не дозволяє вам створювати екземпляри за допомогою vars екземпляра?
dcsan

0

У вашому випадку, якщо ви хочете Foo.talk():

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

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

Але це неефективний спосіб реалізації, використання prototypeкраще.


Інший спосіб, Мій шлях визначається як статичний клас:

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

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

Вище статичний клас використовувати не потрібно, prototypeоскільки він буде побудований лише один раз як статичне використання.

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


@jvitoroc Дякую!
Нік Цай

0

У Javascript немає фактичних класів, скоріше він використовує систему прототипічного успадкування, в якій об'єкти "успадковують" від інших об'єктів через прототип ланцюга. Найкраще це пояснити через сам код:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.