4

Как можно создать в классе (ecma6 class) приватную переменную?

Юрий
  • 834

4 Answers4

6

Для приватных свойств надо использовать замыкания и символы.

 const Animal = (function () {
     const privatePropSymbol = Symbol("privateProp");

     class Animal {
         // гетеры и сеттеры для свойства
         get PrivateProp() { return this[privatePropSymbol]; }
         set PrivateProp(value) { this[privatePropSymbol] = value } 
     }

     Animal.prototype[privatePropSymbol] = "default value";
     return Animal;
 })();

Фокус в том, что два символа с одинаковым названием остаются двумя разными символами - поэтому не имея переменной privatePropSymbol внешний код не сможет получить к нему доступ.

Pavel Mayorov
  • 58,537
  • вы наверное не знаете что в ES6 можно писать выражения в именах полей в квадратных скобках? class Cat { [1+2](){ функция с именем '3' } } – Maxmaxmaximus Jul 18 '16 at 05:59
  • @Maxmaxmaximus, причем тут это, если в данном случае имя известно заранее? – Grundy Jul 18 '16 at 06:41
  • @Grundy при чем тут известно зарание если мы про Symbol говорим, как он может быть известен зарание? Вначале создал класс, а потом ему вручную прототип расширяет. – Maxmaxmaximus Jul 18 '16 at 06:42
  • @Maxmaxmaximus, так в каком месте примера из данного ответа нужно использовать вычисляемое имя метода? – Grundy Jul 18 '16 at 06:45
  • @PavelMayorov, а так можно? – Grundy Jul 18 '16 at 12:56
  • @PavelMayorov, тем что он предлагает метод сделать [1+2]() с полями как я понял пока такое не работает – Grundy Jul 18 '16 at 12:58
4

В es6 такой возможности нет. Многие думают, что классы es6 это какая-то новая конструкция или реализация , нет. Это обычный , новый синтаксис для старого доброго прототипного наследования.

class Animal {
    constructor() {
        this.name = "dog"
    }
    say()  { alert("gaf") }
}

То же самое, что и

 function Animal() {
     this.name = "dog"
 }
 Animal.prototype.say = function () { alert("gaf") }

Поэтому приватных методов в классе не реализовано, вам придется решать эту проблему самому. Как? Например, через замыкание

 const Animal = function () {
     let privateProp = "i am private";

     class Animal {
         constructor() {
             this.name = "dog";
         }
         // гетеры и сеттеры для свойства
         get PrivateProp() { return privateProp }
         set PrivateProp(value) { privateProp = value } 
     }

     return new Animal();
 };

 let dog = new Animal();
 dog.privateProp; // приватное свойство
 dog.privateProp = "new private prop"; // меняем приватное свойство

Тут методы get/set просто для примера, в реальности скорее всего их не будет, если уже переменная приватная. Тут get/set единственные способы достучатся до privateProp, больше никак, уберете get/set privateProp станет настоящим приватным способом.

UPD
Чуть ошибся, переменная создавалась бы одна для всех экземпляров, как написали в комментарии, поправил ответ. Теперь всегда будет создаваться свой экземпляр приватной переменной

ThisMan
  • 12,261
  • 1
    Ну нельзя же так делать! У вас теперь privateProp не только приватная, но и статическая... – Pavel Mayorov Apr 24 '16 at 07:11
  • @PavelMayorov можно поподробнее, почему так нельзя делать? – Юрий Apr 24 '16 at 08:14
  • @Юрий, в первом варианте создавалась одна переменная для всех экземпляров. Поэтому если мы где то поменяли бы ее, то она поменялась бы для всех экземпляров класса. – ThisMan Apr 24 '16 at 08:26
  • @ThisMan, понятно,спасибо! Я не успел увидеть, там var privateProp было? – Юрий Apr 24 '16 at 08:33
  • 1
    @Юрий, нет, в первом варианте Animal сам был ф-цие конструктором, сейчас же эта функция, которая вызывает ф-цию конструктор) Поэтому при каждом вызове создается новое замыкание с переменной privateProp – ThisMan Apr 24 '16 at 08:34
  • @ThisMan теперь разобрался, спасибо! – Юрий Apr 24 '16 at 08:44
  • @ThisMan а как можно в такой реализации проверить, что (dog instanceof Animal)? Просто так почему-то не работает – Юрий Apr 24 '16 at 09:01
  • @Юрий В такой реализации - нельзя. Потому что тут Animal - не конструктор, а обычная функция. – Pavel Mayorov Apr 24 '16 at 09:58
  • ThisMan, теперь у вас создается по новому классу на каждый вызов "конструктора". Зачем в такой схеме вообще нужны классы? – Pavel Mayorov Apr 24 '16 at 09:59
  • @PavelMayorov чтобы сделать приватные поля – Юрий Apr 24 '16 at 15:18
  • @Юрий я вас не понимаю. Вы сейчас кому ответили? – Pavel Mayorov Apr 24 '16 at 15:19
  • @PavelMayorov, не увидел, подумал, Вы меня спросили – Юрий Apr 24 '16 at 15:20
  • Что за бред? Даже переменные созданные в конструкторе вины лишь в области его видимости, и не видны остальным методам, надо свойства класса хранить в this и только в this, давая лишь уникальные имена, для этого и придумали Symbol ну или Math.random() как полифилл – Maxmaxmaximus Jul 18 '16 at 06:02
  • 3
    Раскладываем запас костылей на будущее? Даже если забить на производительность, то за что какому-то бедному программисту должно достаться dog instanceof Animal = false? – Qwertiy Jan 30 '17 at 12:01
2

Теперь это делается намного проще с помощью синтаксиса #:

class WithPrivateFields {
  #privateField = 'Private Field';
  static #staticPrivateField = 'Static Private Field';

get privateField() { return this.#privateField; }

static get staticPrivateField() { return this.#staticPrivateField; } }

const obj = new WithPrivateFields();

console.log(obj.privateField); console.log(obj['privateField']); console.log(obj['#privateField']);

console.log(WithPrivateFields.staticPrivateField); console.log(WithPrivateFields['staticPrivateField']); console.log(WithPrivateFields['#staticPrivateField']);

EzioMercer
  • 6,178
  • 2
  • 9
  • 28
0

А ВОТ ТАК (при чем так можно делать уже 200 лет отроду):

ES6:

var say = Symbol()

class Cat {

   constructor(){
      this[say]() // call private
   }

   [say](){
      alert('im private')
   }

}

ES5:

var say = Math.random() // pollyfill Symbol()

function Cat(){
  this[say]() // call private methods
}

Cat.prototype[say] = function(){ alert('im a private') }

Пример использования ES6:

var handlers = Symbol()

class EventEmitter {

  constructor(){
    this[handlers] = [] 
  }

  on(handler){
    this[handlers].push(handler) 
  }

  emit(){
    for(let handler of this[handlers]) handler()
  }

}


class Cat extends EventEmitter {

}


var q = new Cat()
q.on // function
q.emit // function
q.handlers // undefined cuz PRIVATE ;)

И не обязательно больше учить названия свойств из реализации класса, таких как внутреннее свойство handlers, боясь их случайно перекрыть в классах наследниках. Использую приватные уже 6 лет. И ни каких проблем с утечками. Не понимаю людей которые говорят что в яваскрипте приватных нет.

Наслаждайтесь, и поменьше слушайте всяких неумех ;)

  • пример с ES5 - не аналог примера с Symbol – Grundy Jul 18 '16 at 06:45
  • А какие проблемы с утечками ожидались? – Vladimir Gamalyan Jul 18 '16 at 07:40
  • 1
    В JavaScript приватных свойств нет, так уж устроен язык. Все, что вы привели, это обходные пути, позволяющие эмулировать приватные свойства. Это не одно и тоже. Ну и ваш вариант ES5 будет приводить к неожиданным для новичков эффектам при использовании for in или for of. От меня минус. – Dmitriy Simushev Jul 18 '16 at 12:29
  • "Ни единого разрыва." Слишком много бравады. Как насчёт q[handlers]? – vp_arth Feb 02 '17 at 13:46
  • Однако, справедливости ради, хочу заметить, что символы - хорошая эмуляция приватных свойств. Только вот нежелательно сорить ими в глобальном scope – vp_arth Feb 02 '17 at 13:48
  • И по поводу приватности: даже в таком варианте, можно получить все ключи с помощью Object.getOwnPropertySymbols – vp_arth Feb 02 '17 at 14:01
  • @vp_arth Тогда это уже будет осознанно, а мы хотим защитить от случайного пересечения имен. Ну либо можно создать новый приметив типа privates – Maxmaxmaximus Feb 03 '17 at 01:18