Статические методы и свойства классов

На этом занятии мы с вами поговорим о статических методах и свойствах. Что это такое? Давайте предположим, что имеется класс для описания пользователя. Он будет очень простым: хранить имя, возраст и иметь метод, возвращающий имя человека:

class Users {
         constructor(name, old) {
                   this.name = name;
                   this.old = old;
         }
 
         getName() { return this.name; }
}

Далее, можно создавать объекты этого класса, следующим образом:

let u1 = new Users("Михаил", 19);
let u2 = new Users("Федор", 19);

Теперь, приходит заказчик и говорит, что надо бы добавить функционал для сравнения пользователей, например, по возрасту. И мы начинаем думать: как это лучше сделать? Из того, что нам известно на этот момент, было бы логично добавить метод для такого сравнения, например, так:

         compareOld(user2) {
                   return this.old == user2.old;
         }

И, затем, использовать его:

console.log( u1.compareOld(u2) );

Отлично, все работает. Мы потираем руки и радуемся легкости исполнения задания. Но, тут приходит опытный программист, смотрит на весь этот код, и начинает изрыгать нелитературные выражения в наш адрес! Что ему не понравилось? Дело вот в чем. Правильнее было бы реализовать функционал для сравнения пользователей на более обобщенном уровне, не привязывая его к объектам каждого пользователя. Потому что эта привязка приведет к добавлению метода compareOld в каждый объект Users, что приведет к неэффективному использованию памяти:

Чтобы это поправить, как раз и можно воспользоваться статическими методами. Если записать метод compareOld как статический (перед его именем ставится ключевое слово static):

         static compareOld(user1, user2) {
                   return user1.old == user2.old;
         }

то он будет принадлежать только классу, но не объектам:

Соответственно, из объектов его вызвать уже нельзя и вот такая строчка приведет к ошибке:

console.log( u1.compareOld(u1, u2) );

Вызвать его можно только непосредственно из класса Users:

console.log( Users.compareOld(u1, u2) );

Это и означает, что статические методы принадлежат только классам, но не объектам и доступ к таким методам возможен только из класса. В результате, мы получаем некую общую реализацию для сравнения возрастов двух произвольных пользователей. И в контексте нашей задачи, такое решение будет лучшим.

Фактически, слово static «указывает» создавать метод по синтаксису:

Users.compareOld = function(user1, user2) {
         return user1.old == user2.old;
}

Тогда как обычные методы объявляются как свойства объекта prototype:

Users.prototype.getName = function() { return this.name; }

Наследование статических методов

Но как будут вести себя статические методы при наследовании классов? Предположим, что мы хотим добавить еще одного специализированного пользователя Admin:

class Admin extends Users {
         constructor(name, old, login, psw) {
                   super(name, old);
                   this.login = login;
                   this.psw = psw;
         }
}

Мы расширяем базовый класс Users и добавляем еще два свойства: login и psw. Будет ли статический метод compareOld доступен в дочернем классе Admin? Да, будет и, далее, мы можем создать такого пользователя:

let u2 = new Admin("Федор", 19, "aaa", "0123");

и сравнить их, вызывая статический метод через класс Admin:

console.log( Admin.compareOld(u1, u2) );

То есть, метод compareOld можно вызывать и через класс Users и через класс Admin. Разницы никакой не будет. Это происходит по той причине, что свойство __proto__ класса Admin ссылается на класс Users:

Если же добавить еще один статический метод, но уже в класс Admin:

         static createAdmin(name, old) {
                   return new this(name, old, "admin", "root");
         }

то картина будет такой:

В методе createAdmin мы создаем нового пользователя Admin с использованием только двух параметров: name, old. Остальные два задаются по умолчанию как: "admin", "root". Причем, ключевое слово this здесь будет ссылаться на класс, указанный перед точкой, при вызове данного метода. Например:

let u3 = Admin.createAdmin("Сергей", 33);

Здесь this ссылается на Admin, поэтому будет создан новый объект класса Admin. А вот если мы в методе compareOld добавим вывод:

console.log(this == Admin);

и вызовем его двумя способами:

Users.compareOld(u1, u2);  // false
Admin.compareOld(u1, u2);  // true

то в первом случае this будет ссылаться на Users, а во втором – на Admin.

Статические свойства

Недавно в JavaScript была добавлена возможность объявления статических свойств внутри класса, например, так:

class Users {
         static countUsers = 0;}

Это свойство будет принадлежать только классу Users и не копироваться в его объекты. Например, так можно сделать подсчет числа созданных пользователей в программе. Добавим в конструктор строчку:

Users.countUsers++;

И, затем, выведем это свойство в консоль:

console.log( Users.countUsers );

Увидим число созданных пользователей.