Несколько предложений расширяют существующий синтаксис классов в JavaScript новой функциональностью. Эта статья объясняет новый синтаксис публичных полей классов в V8 v7.2 и Chrome 72, а также грядущих приватных полей.


Вот пример кода, который создает экземпляр класса IncreasingCounter:


const counter = new IncreasingCounter();
counter.value;
// logs 'Getting the current value!'
// > 0
counter.increment();
counter.value;
// logs 'Getting the current value!'
// > 1

Отметим, что обращение к value выполняет некоторый код (вывод сообщения в лог) перед тем, как вернуть значение. Теперь спросите себя: как бы Вы реализовали этот класс на JavaScript?


Классы ES2015


Ниже пример того, как класс IncreasingCounter может быть реализован с помощью синтаксиса ES2015:


class IncreasingCounter {
  constructor() {
    this._count = 0;
  }
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}

Класс предоставляет геттер value и метод для инкремента значения в прототипе. Более любопытно, что класс имеет конструктор, который инициирует свойство _count и выставляет его начальное значение в 0. Сейчас мы используем префикс подчеркивания, чтобы обозначить, что _count не должен использоваться напрямую вне класса, но это просто соглашение; в действительности это не приватное свойство, а эта семантика не определена в самом языке.


const counter = new IncreasingCounter();
counter.value;
// logs 'Getting the current value!'
// > 0

// Nothing stops people from reading or messing with the
// `_count` instance property. 
counter._count;
// > 0
counter._count = 42;
counter.value;
// logs 'Getting the current value!'
// > 42

Публичные поля классов


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


class IncreasingCounter {
  _count = 0;
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}

Свойство _count теперь лаконично объявлено в начале класса. Нам больше не нужен конструктор только для того, чтобы определить некоторые поля. Отлично!


Тем не менее, _count — все еще публичное свойство. А в этом конкретном примере мы хотим предотвратить обращение к этому полю напрямую.


Приватные поля классов


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


class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}

Приватные поля недоступны вне тела класса:


const counter = new IncreasingCounter();
counter.#count;
// > SyntaxError
counter.#count = 42;
// > SyntaxError

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


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


class FakeMath {
  // `PI` is a static public property.
  static PI = 22 / 7; // Close enough.

  // `#totallyRandomNumber` is a static private property.
  static #totallyRandomNumber = 4;

  // `#computeRandomNumber` is a static private method.
  static #computeRandomNumber() {
    return FakeMath.#totallyRandomNumber;
  }

  // `random` is a static public method (ES2015 syntax)
  // that consumes `#computeRandomNumber`.
  static random() {
    console.log('I heard you like random numbers…')
    return FakeMath.#computeRandomNumber();
  }
}

FakeMath.PI;
// > 3.142857142857143
FakeMath.random();
// logs 'I heard you like random numbers…'
// > 4
FakeMath.#totallyRandomNumber;
// > SyntaxError
FakeMath.#computeRandomNumber();
// > SyntaxError

Упрощение работы с подклассами


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


class Animal {
  constructor(name) {
    this.name = name;
  }
}

Чтобы создать подкласс Cat, который добавляет новое свойство для экземпляра, ранее требовалось обратиться к super(), чтобы вызвать конструктор базового класса Animal перед тем, как создать это свойство:


class Cat extends Animal {
  constructor(name) {
    super(name);
    this.likesBaths = false;
  }
  meow() {
    console.log('Meow!');
  }
}

Здесь много шаблонного кода только для того, чтобы указать, что коты не очень любят принимать ванну. К счастью, новый синтаксис полей классов избавляет от необходимости определения этого конструктора с неуклюжим вызовом super():


class Cat extends Animal {
  likesBaths = false;
  meow() {
    console.log('Meow!');
  }
}

Итого


Публичные поля классов доступны, начиная с V8 v7.2 и Chrome 72. Скоро планируется релиз и приватных полей классов.

Комментарии (70)


  1. 1c80
    02.02.2019 14:01
    +1

    А через модули не проще разве? можно экспортнуть только то, что надо, остальное будет закрыто.


    1. Djaler
      02.02.2019 14:10

      как вам модули помогут ограничить доступ к полям классов?


      1. 1c80
        02.02.2019 14:29
        +1

        ну вынести эти поля в константу и не эскспортировать её, а в классе работать через геттер и сеттер


        1. Djaler
          02.02.2019 15:38

          Во-первых, интересно представить, как вы себе представляете сеттер для константы. Ну это ладно, допустим, вы имели в виду переменную. Но что делать с тем, что у класса может быть не один экземпляр?


          1. gnaeus
            02.02.2019 15:44

            Вероятно, имелось в виду, что можно вместо классов использовать фабрики объектов. И публичные поля и методы пихать в объект, а приватные оставлять в замыкании. Ну или все поля / методы задавать в конструкторе, ничего не оставляя в прототипе. Хотя это все дичь конечно =)


            1. Zibx
              02.02.2019 17:56
              +1

              Все приватные выносить в соседний объект и ссылку хранить в замыкании. Основной вопрос — зачем весь этот цирк если дебаггером всё равно можно остановиться внутри функции объекта и изучить\модифицировать всё приватное.


              1. xRevolveRx
                02.02.2019 20:04
                -1

                На backend как это сделаете?


                1. TheShock
                  02.02.2019 20:06
                  +1

                  Если есть доступ к коду — то так же легко подключается.
                  Если нету доступа, то какая разница — приват там или паблик


                  1. xRevolveRx
                    02.02.2019 20:08
                    -2

                    Так нет у Вас доступа, если вы не разработчик, а если есть — то вы ковыряете просто front-end. А для чего на фронте делать приваты???


                    1. TheShock
                      02.02.2019 20:32
                      +3

                      А для чего на беке делать приваты? Если доступа нет — я и паблики не посмотрю. А если доступ есть — приваты мне не помешают.


                      1. xRevolveRx
                        03.02.2019 00:04
                        -1

                        Так от чего они защитят? От вашего чинарика в зубах?


                        1. TheShock
                          03.02.2019 01:20
                          +1

                          Что?


                          1. xRevolveRx
                            03.02.2019 12:40

                            Я к тому, что я не обязан был 10 лет писать приватные поля в JS потому что хватало и замыканий; и теперь не обязан слушать людей, которые утверждают, что это критически нужно для дисциплины и понятности API класса. Всю жизнь все объекты DOM в JS были таки нашпигованы кучей оснастки и расконсоливание об этом прекрасно рассказывает. На сервере, кстати, гораздо проще объекты и сами классы поэтому, я по привычке даже не задумаюсь об этом. Минусуете, как будто 300 лет в лесу жили.


                            1. TheShock
                              03.02.2019 19:37

                              Минусуют, потому что вы речь построить не можете.


                      1. Sabubu
                        03.02.2019 07:06
                        -1

                        Приватные поля не для защиты от просмотра их разработчиком… это не права доступа на файлы. Приватные поля нужны для реализации инкапсуляции, которая служит для таких целей:


                        • ускорение понимания кода другим разработчиком (ему не надо смотреть на приватные поля и методы или читать весь код, чтобы понять, как использовать класс, а достаточно пробежаться взглядом по заголовкам публичных методов)
                        • возможность автору класса обеспечить гарантии. Например, гарантию, что в определенное ситуации класс ведет себя определенным образом. Если поле публичное, в него можно снаружи записать что угодно, и гарантировать корректную работу класса нельзя. А вот если приватное — то доступ к нему возможен только через методы, которые не позволят туда записать что-то некорректное. Условно говоря, если у вас есть объект, представляющий дату, то вы не сможете в нем сделать 32 февраля. А в примере счетчика выше — с приватным полем автор может гарантировать, что счетчик нельзя "открутить" назад.

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


                        1. TheShock
                          03.02.2019 07:13
                          +1

                          Эмс. Вы веткой ошиблись? Я как раз и старался объяснить человеку, что «приват» модификатор никак не защищает данные. Более того, с разделением «на сервере защищает, а на клиенте — нет». Это вообще не имеет никакой логики. Я задавал наводящие вопросы и надеялся, что человек дойдет до этого.

                          Жаль, что вы сразу бросились в оскорбления не разобравшись в ситуации.

                          А модификатор private, который физически не дает доступа к данным считаю ненужным и даже, иногда, вредным. Для любого программиста достаточно модификатора private, который является удобным способом записать сограшение, что переменная или метод приватны, а проверка пусть происходит на стороне IDE или линтера, как строгая рекомендация.

                          И да, то, что модификатор «приват» НУЖЕН для реализации инкапсуляции — поверхностное мнение от попсового (упрощенного) определения инкапсуляции.


          1. 1c80
            02.02.2019 15:49

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


      1. justboris
        02.02.2019 16:27

        Есть такой старый добрый паттерн "Модуль": сохраняем приватные значения в замыкании, возвращая наружу только публичное API.


        Что-то похожее можно сделать и на современных ES-модулях


        1. Djaler
          02.02.2019 18:13
          -2

          ну это костыли. Я уж не говорю про удобство этого дела


          1. justboris
            02.02.2019 19:15
            +1

            Кому как. Для меня это давнее и вполне логичное использование фич языка.


        1. ankh1989
          03.02.2019 06:40

          Сей паттерн ест сильно много памяти по сравнению с прототипами. Замыкания не бесплатны.


          1. justboris
            03.02.2019 16:12

            А есть подтверждение этой информации?


            Например, я вижу, что Redux использует как раз замыкание для своего createStore. Сокращенно код выглядит как-то так.


            function createStore() {
               let currentState = ...;
               function getState() {
                 return currentState;
               }
               function dispatch() {
               }
               function subscribe() {
               }
            
               return {
                 dispatch,
                 subscribe,
                 getState
               };
            }

            Библиотека используется много где, и в React и Angular проектах, так что если бы здесь были заметные проблемы с производительностью, то это бы давно заметили.


            1. VolCh
              04.02.2019 01:22

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


        1. Sabubu
          03.02.2019 07:09

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


          1. aamonster
            03.02.2019 08:57

            А современные ES-модули внутри не устроены так же, как этот костыль? С классами, помнится, именно так.


    1. Sabubu
      03.02.2019 07:00

      Модуль это не класс и вы не можете создать несколько экземпляров модуля с разными настройками, например.


      1. 1c80
        03.02.2019 11:52

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


  1. gnaeus
    02.02.2019 14:30
    +6

    Больше всего, конечно, смущает синтаксис с решетками. Потому что ломается совместимость с TypeScript, в котором используются ключевые слова private / public / protected.


    Да и в самом JavaScript эти слова зарезервированы. Правда теперь уже получается, что зря зарезервированы.


    1. axifive
      02.02.2019 14:48

      Возможно еще в будущем добавят их в качестве синонимов.


    1. cerberus_ab Автор
      02.02.2019 14:50
      +1

      Это просто синтаксис меньшего зла ) комментарий по этому поводу


      1. gnaeus
        02.02.2019 15:19
        +2

        "Так не доставайся ж ты никому!" — весьма распространенное явление, когда в деле замешаны всяческие комитеты.


        Часто бывает нужно не private, а internal (видимость на уровне модуля или NPM-пакета). И теперь приходится для private использовать #, для internalSymbol и computed properties.


        А можно просто настроить минификатор, чтобы он манглил все свойтсва объектов, начинающиеся с нижнего подчеркивания, как делали и раньше, и не насиловать себе мозг.


        Имхо, они приняли поспешное решение. И теперь нам всем придется с этим жить.


        1. cerberus_ab Автор
          02.02.2019 15:56
          +1

          Да, ситуация несколько досадная. Учитывая, что часто JavaScript — не единственный язык, на котором приходится писать… и в нем все больше и больше специфики.


          Возможно, многие читали этот issue о кейворде: там довольно много обсуждения и недовольств.


          Например, мол сложно внедрять

          Think of what you might need to do to make that work.


          • You'd have to somehow encode a "private key" into each lexical environment.
          • You'd have to change the semantics of each and every property access (because any property access might result in a private field). Engines are highly optimized around the current property lookup semantics.
          • Would for-in enumerate over these properties?
          • Can someone shadow a private property with a normal property lower on the prototype chain? What about the reverse?
          • How do you prevent leaking the names of private fields to clients that shouldn't know that information? This is probably a fatal information leak.


          1. gnaeus
            02.02.2019 16:10

            Кстати, а что будет, если я добавлю новый метод в прототип класса (да, monkey patching)? И попытаюсь прочитать приватное поле:


            class Test {
               #foo = 123;
            }
            Test.prototype.getFoo = function() {
              return this.#foo;
            }


            1. justboris
              02.02.2019 16:33

              Не сработает. Можно самому в этом убедиться, если включить в хроме флаг "Experimental JavaScript" и попытаться запустить этот код. При попытке вызова getFoo выскочит ошибка


              Uncaught SyntaxError: Undefined private field #foo: must be declared in an enclosing class


            1. cerberus_ab Автор
              03.02.2019 01:22

              Тоже об этом подумал ) Но синтаксис приватных полей класса работает только внутри тела определения класса.


            1. Sabubu
              03.02.2019 07:12

              Доступ к приватному полю ограничивается на уровне расположения в исходном коде: он возможен только между открывающий и закрывающей скобкой блока class. Если вы, например, вызовете анонимную функцию, которая определена внутри class (неважно, откуда вызовете) — то у нее будет доступ к приватным полям. Если же эта функция определена на пределами класса — то доступа не будет.


      1. symbix
        03.02.2019 03:05
        +1

        What do you mean by "encapsulation" / "hard private"?
        It means that private fields are purely internal: no JS code outside of a class can detect or affect the existence, name, or value of any private field of instances of said class without directly inspecting the class's source, unless the class chooses to reveal them. (This includes subclasses and superclasses.)
        This means that reflection methods like getOwnPropertySymbols must not reveal private fields.

        Ох уж эти комитетчики. Одни теоретики. Как они представляют себе работу persistance/hydration с таким подходом? Даже пусть не ORM, а банальную сериализацию? А, кстати, что будет видеть JSON.stringify? :-)


    1. xRevolveRx
      02.02.2019 14:52

      Мне кажется для TypeScript это не смертельно. Ведь они не дождались нормальной реализации стандарта и начали нашпиговывать язык своими, как правило не очень то и критичными фичами.


      1. gnaeus
        02.02.2019 15:39

        А private — критическая фича? Вон в питоне как-то живут без нее. И хорошо живут. Все это нужно только для самодисциплины разработчиков.

        Вот декораторы — это существенное увеличение выразительности языка. Но они зависли в недрах комитета на долгие годы. И неизвестно, вылезут ли из stage-2.

        А есть еще такая крутая штука как pipeline operator (тоже несколько лет в stage-1). С ней работа в функциональном стиле упростилась бы в разы. Но без статической типизации она превращается в тыкву. А команда TypeScript, обжегшись на взаимодействии с комитетом по поводу декораторов, отказалась реализовывать этот оператор =(


        1. xRevolveRx
          02.02.2019 16:03
          -8

          Я с вами согласен. Я на PHP тоже не использую private ибо толку от него ноль. Только видимость защищенности данных.


          1. gnaeus
            02.02.2019 16:15

            Да я тут не на private нападаю, а на тормозной комитет.


        1. Zibx
          02.02.2019 18:02
          +3

          Языку со времён ES5 не хватало только возможности заводить типизированные переменные в целях увеличения производительности, эдакое развитие в сторону ActionScript — сейчас скорость бы уже сравнялась с нативными языками, но нет, теперь у нас есть особые классы, куча синтаксиса написания функций, замыкания на уровне блоков чтоб GC подавился. Всё кроме переменных можно было решить транспайлерами, даже перегрузку операторов которую давно жду.


    1. serf
      04.02.2019 07:11

      TypeScript является суперсетом ES/JS, то есть они поддержат изменение github.com/Microsoft/TypeScript/issues/9950, но когда придет время. Обычно TS вводит поддержку новых штуковин начиная со stage 3. Но видимо в этот раз они не спешат ввиду притиворечивости текущего состояния стандарта:
      github.com/tc39/proposal-class-fields/issues/100
      github.com/tc39/proposal-class-fields/issues/144
      github.com/tc39/proposal-class-fields/issues/177
      github.com/tc39/proposal-class-fields/issues/203
      — и тд


  1. immchenko
    02.02.2019 15:01
    +10

    Тоже грустно без private / public / protected. С решеткой какой-то маня-монёвр уровня IE


  1. zim32
    02.02.2019 17:34

    Главное что не так как у всех


  1. TheShock
    02.02.2019 19:33
    +2

    Сейчас мы используем префикс подчеркивания, чтобы обозначить, что _count не должен использоваться напрямую вне класса, но это просто соглашение;
    Ну и что? В Джавах и Шарпах тоже можно поменять и прочитать приватное свойство через рефлексию и его не читают «потому-что соглашение». И читают, когда в этом действительно есть необходимость


    1. serf
      04.02.2019 07:16

      И читают, когда в этом действительно есть необходимость

      При этом явно оттадавая себе отчет в том что рефлексия медленная и в том что даже следующий минорный релиз допустим библиотеки которая «хакается» рефлексией может сломать их хаки тк приватные поля не являются частью контракта.


      1. TheShock
        04.02.2019 07:20
        +1

        И? Джаваскрипт медленный, а поля, помеченные «просто соглашением» как приватные не являются частью контракта.


  1. pesh1983
    02.02.2019 21:46
    +2

    Мне одному кажется странным использование префикса вместо зарезервированного слова, как это сделано во всех остальных нормальных языках?


    1. justboris
      02.02.2019 21:51

      достаточно почитать комментарии выше, чтобы убедиться, что нет, не одному.


      1. pesh1983
        02.02.2019 21:56

        Где-то и когда-то развитие языка свернуло явно не туда (


        1. justboris
          03.02.2019 01:46
          +1

          Я себе это представляю как-то так:


          TC39: дорогие разработчики, какие новые фичи языка вы хотите?
          Сообщество: приватные поля! Больше строгости в наш Javascript!
          TC39: тут такое дело… из-за обратной совместимости и других загвоздок приватные поля должны обладать специальными именами, модификатор private сделать не получится.
          Сообщество: ну и не очень-то нам нужно такое решение


          Интересно, как в этой ситуации выкрутится Typescript, у которого интеграция с ЕS-приватными полями уже значится в планах на будущее


          1. pesh1983
            03.02.2019 19:16
            +1

            TS может сказать, что у нас будет отдельный модификатор для приватного поля, но мы поддерживаем и синтаксис js, однако рекомендуем использовать нормальный подход, который предоставляем мы. Я бы использовал их вариант.


    1. TheShock
      03.02.2019 01:24

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


      1. Zibx
        03.02.2019 02:53

        Модификатор добавить легко, сложнее придумать ту напасть от которой нам нужно будет изолироваться с его помощью. Единственный кейс который я вижу — коммерческие библиотеки собранные в бинари, но и тут для «защиты» сейчас имеется ровно столько же инструментов.


        1. Sabubu
          03.02.2019 07:14

          Приватные поля придуманы не для защиты коммерческих секретов, а для инкапсуляции, которая упрощает понимание кода и повышает его качество и надежность.


          1. pesh1983
            03.02.2019 11:56
            +1

            Заменяете приватное свойство соглашением. Так уже давно используют нижнее подчеркивание. Мало того, что оно решает проблему (используешь приватные свойства — ССЗБ, разработчик кода не гарантирует совместимость). Так ещё и помогают, когда нужно поменять что-то в сторонней библиотеке, а нормальной возможности это сделать нет. Тогда на свой страх и риск. Но зато возможность есть, а это лучше ее отсутствия.


      1. pesh1983
        03.02.2019 11:35

        Вы явно объявляете свойство при объявлении класса. Не совсем понимаю, какие тут проблемы могут быть, если вы явно свойство объявили. Я так же не понимаю, чем объявление приватного свойства с префиксом с точки зрения реализации концептуально отличается от объявления с ключевым словом.


  1. xPomaHx
    03.02.2019 11:59
    +1

    Так и не смог понять зачем вообще нужно делать приватные поля кроме как разве для работы ide, если ты работаешь с кодом как с либой, то кроме как, то что есть в документации ты нечего как бы и не сможешь использовать. А если приходится лезть внутрь кода то какая разница, тебе и так и так придется разбираться в нем, да мб легче будет разобраться в коде, но если разраб не пишет коменты или доки, то он так же может и приватными поля не делать.


    1. gnaeus
      03.02.2019 12:37

      Еще бывает нужно для ABI при динамической линковке. В смысле, чтобы при компиляции *.dll / *.so / whatever не экспортировать метаданные о приватных полях. Но для этого нужна компиляция хотя бы в байт-код. Таким образом вы правы — для JavaScript это не нужно =)


    1. zim32
      03.02.2019 12:39

      А зачем к примеру в C++ есть ключевое слово volatile. Чтобы уменьшить вероятность что что-то пойдет не так.


    1. VolCh
      04.02.2019 01:30

      Не у всего есть документация, как минимум.


  1. angly
    03.02.2019 13:28

    К слову, сейчас это предложение находится на 3 стадии принятия в стандарт.
    Опробовать в других браузерах можно с помощью бабеля, используя либо плагин @babel/plugin-proposal-private-methods, либо пресет stage-3.