Приватные методы и свойства, оператор instanceof

Согласно концепции инкапсуляции ООП в классах можно создавать как открытые (публичные) методы и свойства, так и закрытые, доступные только внутри класса или его объектов. До сих пор все методы и свойства у нас были публичные. Но, что если мы хотим некоторые из них указать как приватные, закрытые? Например, в классе Users такими сделать свойства name и old:

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

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

  • _name, _old – приватные свойства;
  • _setProperty(key, value) – приватный метод.

И это именно договоренность. На уровне языка JavaScript никакого отличия между такими и другими свойствами и методами нет. Например, отмечая свойства name и old как приватные:

         constructor(name, old) {
                   this._name = name;
                   this._old = old;
         }

Мы к ним, тем не менее, можем совершенно спокойно обращаться из вне:

console.log( u1._name );

Но это уже остается на совести программиста и, вообще, считается порочной практикой, т.к. такие свойства в будущем (в другой версии модуля) могут измениться или совсем исчезнуть и тогда придется переделывать и саму программу, что не очень приятно. Поэтому, отмеченные таким образом атрибуты, следует использовать исключительно внутри класса. Таково общепринятое соглашение.

Разумеется, как обычные свойства, условно-приватные также наследуются и доступны в дочерних классах. Например, объявим метод getName как приватный:

_getName() { return this._name; }

и создадим дочерний класс Admin:

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

Тогда после создания объекта:

let u2 = new Admin("Кирилл", 22, "admin", "1111");

мы совершенно спокойно можем вызвать метод базового класса:

console.log( u2._getName() );

даже, если он по соглашению приватный. Это еще раз показывает, что закрытые методы – это лишь соглашение между программистами, языком JavaScript они воспринимаются как обычные методы и свойства.

Настоящие приватные свойства

Совсем недавно в JavaScript появилась возможность объявлять «настоящие» приватные свойства, которые становятся защищенными на уровне языка, а не просто соглашения между программистами. Для их обозначения перед именами ставится символ шарп:

#<имя свойства> - защищенное свойство.

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

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

А, затем, уже можем использовать в экземплярах классов:

let u1 = new Users("Михаил", 19);
let u2 = new Admin("Кирилл", 22, "admin", "1111");
 
console.log( u1.getName() ); // Михаил
console.log( u2.getName() ); // Кирилл

Как видите, в консоли выводятся разные имена, следовательно, для каждого объекта приватное свойство является уникальным и хранится непосредственно в нем.

Если же мы попытаемся обратиться к такому свойству из вне:

console.log( u1.#name );

то возникнет ошибка. Это как раз и показывает, что такие свойства по-настоящему приватные и могут изменяться только внутри класса или его объектах.

У таких свойств есть один нюанс: обращаться к ним по синтаксису:

this[<ключ>]

нельзя. Например, если в методе getName записать:

getName() { return this['#name']; }

то получим значение undefined. Тогда, как с обычными свойствами все работало бы без проблем.

Вот так в JavaScript можно объявлять и работать с приватными свойствами и методами.

Оператор instanceof

В JavaScript есть довольно полезный и часто используемый оператор

obj instanceof Class

который проверяет принадлежность объекта obj классу Class, то есть, возвращает true, если obj действительно является экземпляром класса Class (или, любому базовому классу) и false – в противном случае.

Например, в нашей программе можно реализовать следующую проверку:

console.log( u1 instanceof Admin);   // false
console.log( u2 instanceof Admin );   // true

А, вот если вместо Admin написать Users:

console.log( u1 instanceof Users);
console.log( u2 instanceof Users);

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

console.log( u1.constructor == Users);
console.log( u2.constructor == Users);

Здесь свойство constructor (если оно есть и не было искусственно изменено) хранит ссылку на функцию-конструктор, с помощью которой и был создан текущий объект. А, так как класс – это и есть функция-конструктор, то мы, таким образом, проверяем соответствие объекта тому или иному классу.

Вот так в JavaScript можно определять тип объекта с помощью оператор instanceof и простой проверкой на равенство.