Класс против статического метода в 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?

lostyzd
источник
14
Foo.talk = function ...
Преждевременная оптимизация
1
@downvoterstepintothelight Это Foo.walk = function() {}не повлияет на его экземпляры, так как он не входит в цепочку прототипов. Существует ли кросс-браузерный метод для [[prototype]]указания функции на нее prototype?
lostyzd
3
Вероятно, я понятия не имею, что вы хотите, потому что методы класса не влияют на экземпляры по определению.
Преждевременная оптимизация
@ downvoterstepintothelight Я сомневаюсь, что метод в языке, подобном python, экземпляр может вызывать метод своего класса, разница thisуказатель.
lostyzd

Ответы:

410

Прежде всего, помните, что JavaScript - это в первую очередь прототипный язык , а не язык на основе классов 1 . Fooэто не класс, это функция, которая является объектом. Вы можете создать экземпляр объекта из этой функции, используя newключевое слово, которое позволит вам создать нечто похожее на класс на стандартном языке ООП.

Я бы посоветовал игнорировать __proto__большую часть времени, потому что он плохо поддерживает кросс-браузер, и вместо этого сосредоточиться на изучении того, как prototypeработает

Если у вас есть экземпляр объекта, созданный из функции 2, и вы обращаетесь к одному из его членов (методы, атрибуты, свойства, константы и т. Д.) Любым способом, доступ будет течь вниз по иерархии прототипа, пока он (а) не найдет участник, или (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было «Future Reserved Word» в спецификации ECMAScript 5 , но ES6 вводит возможность определять классы, используя classключевое слово.

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

3: примитивные значения, которые включают undefined, nullлогические, числа и строки, технически не являются объектами, потому что они являются низкоуровневыми реализациями языка. Булевы числа, числа и строки по-прежнему взаимодействуют с цепочкой прототипов, как если бы они были объектами, поэтому для целей этого ответа легче считать их «объектами», даже если они не совсем.

zzzzBov
источник
1
@lostyzd - они могут получить к нему доступ через Foo.talk(). Вы можете назначить это в конструкторе, если хотите: this.talk = Foo.talk- или, как вы заметили, присваивая Foo.prototype.talk = Foo.talk. Но я не уверен, что это хорошая идея - в принципе, методы экземпляра должны быть специфическими для экземпляра.
Нрабиновиц
2
@Doug Avery, Foo.talk()просто вызывает функцию пространства имен. Вы бы использовали его в ситуациях, подобных тому, как статические методы вызываются в языках ООП, таких как 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.'); };

Теперь вы можете вызвать функцию «talk», как показано ниже:

Foo.talk();

Вы можете сделать это, потому что в JavaScript функции также являются объектами.

Bipul
источник
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 = new Clazz (); создает новый экземпляр Clazz. Однако Clazz.staticMethod () достигает результата без всего остального.
mpemburn
5
@mpemburn: Эдуардо также прав в своем ответе. Он показывает вам не только то, что вы можете вызывать статический метод извне, Clazz.staticMethodно он показывает вам, как связать эти статические методы из экземпляра объекта. Это особенно полезно в таких средах, как Node.js, где при использовании require у вас может не быть прямого доступа к исходному конструктору. Единственное, что я хотел бы добавить,this.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford
1
Абсолютно круто, даже работает внутри конструктора сценариев кофе: constructor: (a) -> @constructor.add @(ну, почти, во всяком случае)
Orwellophile
31

Вот хороший пример, чтобы продемонстрировать, как Javascript работает с переменными и методами 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
источник
Хорошая мысль: это может быть странно - не иметь доступа к статической функции через 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()не работает. Принятый ответ говорит, что цепочка прототипов должна найти его, верно? Но это не тот случай
Пинкья
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();
A-Sharabiani
источник
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 не будут иметь «this» для какого-либо экземпляра класса, а для самого класса. Поэтому, конечно, статический метод может вызывать метод экземпляра, но только если он каким-то образом имеет доступ к экземпляру, например, «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 устанавливает атрибут дескриптора немного, отличный от "статического" метода старой школы

function Car() {

}

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

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

он устанавливает внутренний атрибут (свойство дескриптора) для brand () в

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

}
..

по сравнению с

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

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

который устанавливает внутренний атрибут для brand () в

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

..

Посмотрите, что для перечисляемого установлено значение false для статического метода в ES6.

это означает, что вы не можете использовать цикл for для проверки объекта

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

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

Статический метод в ES6 обрабатывается как личное свойство другого класса (имя, длина, конструктор), за исключением того, что статический метод все еще доступен для записи, поэтому дескриптор доступный для записи установлен в значение true { writable: true } . это также означает, что мы можем переопределить его

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

console.log(
  Car2.brand() // is now changed to toyota
);
ngakak
источник
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");

Вишну
источник
Я делаю это все время, но в основном это просто пространство имен. Это не позволяет вам создавать экземпляры с экземплярами VAR?
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

Виллем ван дер Веен
источник