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

Я знаю, что это будет работать:

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?

Ответ 1

Прежде всего, помните, что 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, логические значения, числа и строки, технически не являются объектами, поскольку они являются низкоуровневыми реализациями языка.Булевы числа, числа и строки по-прежнему взаимодействуют с цепочкой прототипов, как если бы они были объектами, поэтому для целей этого ответа проще считать их "объектами", даже если они не совсем.

Ответ 2

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

function Foo() {};

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

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

Foo.talk();

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

Ответ 3

Вызвать статический метод из экземпляра:

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

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

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

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

Ответ 4

Вот хороший пример, демонстрирующий, как 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

Ответ 5

В дополнение, теперь это возможно сделать с 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'

Ответ 6

Я использую пространства имен:

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

Ответ 7

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

}

Ответ 8

Если вам нужно написать статические методы в 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/

Ответ 9

Просто дополнительные заметки. Используя класс 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
);

Ответ 10

Когда вы пытаетесь вызвать Foo.talk, JS пытается выполнить поиск функции talk через __proto__ и, конечно, ее не найти.

Foo.__proto__ Function.prototype.

Ответ 11

В вашем случае, если вы хотите 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

Ответ 12

Когда я столкнулся с такой ситуацией, я сделал что-то вроде этого:

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

поэтому теперь я могу вызвать метод info как Logger.info("my Msg", "Tag");

Ответ 13

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

Довольно четкое описание

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

Foo должен быть привязан к вашему классу. Затем, когда вы создаете новый экземпляр, вы можете вызвать myNewInstance.foo(). Если вы импортируете свой класс, вы можете вызвать статический метод.

Ответ 14

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