Статьи

Новые поля частного класса в JavaScript и как их использовать

ES6 ввел классы в JavaScript, но они могут быть слишком простыми для сложных приложений. Поля класса (также называемые свойствами класса ) предназначены для предоставления более простых конструкторов с закрытыми и статическими членами. Предложение в настоящее время является этапом TC39 3: кандидат и, вероятно, будет добавлено к ES2019 (ES10). В настоящее время частные поля поддерживаются в Node.js 12, Chrome 74 и Babel.

Краткий обзор классов ES6 полезен, прежде чем мы посмотрим, как реализованы поля классов.

Основы класса ES6

Объектно-ориентированная модель наследования JavaScript может сбить с толку разработчиков из таких языков, как C ++, C #, Java и PHP. По этой причине ES6 ввел классы . Они в основном синтаксические сахара, но предлагают более знакомые концепции объектно-ориентированного программирования.

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

 class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } speak() { console.log(`${this.name} says "${this.noise}"`); } walk() { console.log(`${this.name} walks on ${this.legs} legs`); } } 

Объявления классов всегда выполняются в строгом режиме. Нет необходимости добавлять 'use strict' .

Метод конструктора запускается при создании объекта типа Animal . Обычно он устанавливает начальные свойства и обрабатывает другие инициализации. speak() и walk() являются методами экземпляра, которые добавляют дополнительную функциональность.

Теперь из этого класса можно создать объект с new ключевым словом:

 let rex = new Animal('Rex', 4, 'woof'); rex.speak(); // Rex says "woof" rex.noise = 'growl'; rex.speak(); // Rex says "growl" 

Добытчики и сеттеры

Сеттеры — это специальные методы, используемые только для определения значений. Точно так же, Getters — это специальные методы, используемые только для возврата значения. Например:

 class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } speak() { console.log(`${this.name} says "${this.noise}"`); } walk() { console.log(`${this.name} walks on ${this.legs} legs`); } // setter set eats(food) { this.food = food; } // getter get dinner() { return `${this.name} eats ${this.food || 'nothing'} for dinner.`; } } let rex = new Animal('Rex', 4, 'woof'); rex.eats = 'anything'; console.log( rex.dinner ); // Rex eats anything for dinner. 

Ребенок или Подклассы

Часто практично использовать один класс в качестве основы для другого. Класс Human может наследовать все свойства и методы класса Animal с помощью ключевого слова extends . Свойства и методы могут быть добавлены, удалены или изменены по мере необходимости, чтобы создание человеческого объекта стало проще и более читабельным:

 class Human extends Animal { constructor(name) { // call the Animal constructor super(name, 2, 'nothing of interest'); this.type = 'human'; } // override Animal.speak speak(to) { super.speak(); if (to) console.log(`to ${to}`); } } 

super ссылается на родительский класс, поэтому обычно это первый вызов, выполняемый в constructor . В этом примере метод Human speak() переопределяет тот, который определен в Animal .

Объектные экземпляры Human теперь могут быть созданы:

 let don = new Human('Don'); don.speak('anyone'); // Don says "nothing of interest" to anyone don.eats = 'burgers'; console.log( don.dinner ); // Don eats burgers for dinner. - let don = new Human('Don'); don.speak('anyone'); // Don says "nothing of interest" to anyone don.eats = 'burgers'; console.log( don.dinner ); // Don eats burgers for dinner. 

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

Определение метода с ключевым словом static позволяет вызывать его в классе без создания экземпляра объекта. Рассмотрим константу Math.PI : нет необходимости создавать объект Math перед доступом к свойству PI .

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

 class Human extends Animal { constructor(name) { // call the Animal constructor super(name, 2, 'nothing of interest'); this.type = 'human'; // update count of Human objects Human.count++; } // override Animal.speak speak(to) { super.speak(); if (to) console.log(`to ${to}`); } // return number of human objects static get COUNT() { return Human.count; } } // static property of the class itself - not its objects Human.count = 0; 

Статический получатель COUNT класса возвращает количество людей соответственно:

 console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 0 let don = new Human('Don'); console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 1 let kim = new Human('Kim'); console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 2 

Поля класса ES2019 (NEW)

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

 class MyClass { a = 1; b = 2; c = 3; } 

Это эквивалентно:

 class MyClass { constructor() { this.a = 1; this.b = 2; this.c = 3; } } 

Если вам все еще требуется конструктор, инициализаторы будут выполнены до его запуска.

Поля статического класса

В приведенном выше примере статические свойства были незаметно добавлены к объекту определения класса после его определения. Это не обязательно с полями класса:

 class MyClass { x = 1; y = 2; static z = 3; } console.log( MyClass.z ); // 3 

Это эквивалентно:

 class MyClass { constructor() { this.x = 1; this.y = 2; } } MyClass.z = 3; console.log( MyClass.z ); // 3 

Поля частного класса

Все свойства в классах ES6 являются открытыми по умолчанию и могут быть проверены или изменены вне класса. В приведенных выше примерах « Animal нет ничего, что могло бы помешать изменению свойства food без вызова eats eats:

 class Animal { constructor(name = 'anonymous', legs = 4, noise = 'nothing') { this.type = 'animal'; this.name = name; this.legs = legs; this.noise = noise; } set eats(food) { this.food = food; } get dinner() { return `${this.name} eats ${this.food || 'nothing'} for dinner.`; } } let rex = new Animal('Rex', 4, 'woof'); rex.eats = 'anything'; // standard setter rex.food = 'tofu'; // bypass the eats setter altogether console.log( rex.dinner ); // Rex eats tofu for dinner. 

Другие языки часто позволяют объявлять private свойства. Это невозможно в ES6, поэтому разработчики часто обходят его, используя соглашение о подчеркивании ( _propertyName ), замыкания, символы или WeakMaps . Подчеркивание дает подсказку разработчику, но ничто не мешает им получить доступ к этому свойству.

В ES2019 поля частного класса определяются с помощью префикса hash # :

 class MyClass { a = 1; // .a is public #b = 2; // .#b is private static #c = 3; // .#c is private and static incB() { this.#b++; } } let m = new MyClass(); m.incB(); // runs OK m.#b = 0; // error - private property cannot be modified outside class 

Обратите внимание, что нет никакого способа определить частные методы, методы получения или установки. Этап 3 TC39: черновик предложения предлагает использовать префикс # для имен, и он был реализован в Babel. Например:

 class MyClass { // private property #x = 0; // private method (can only be called within the class) #incX() { this.#x++; } // private setter (can only be used within the class) set #setX(x) { this.#x = x; } // private getter (can only be used within the class) get #getX() { return this.$x; } } 

Непосредственная выгода: более чистый код React!

У компонентов React часто есть методы, связанные с событиями DOM. Чтобы убедиться, что this разрешает компонент, необходимо bind каждый метод соответствующим образом. Например:

 class App extends Component { constructor() { super(); this.state = { count: 0 }; // bind all methods this.incCount = this.incCount.bind(this); } incCount() { this.setState(ps => { count: ps.count + 1 }) } render() { return ( <div> <p>{ this.state.count }</p> <button onClick={this.incCount}>add one</button> </div> ); } } 

Когда incCount определен как поле класса ES2019, его можно назначить как функцию с помощью жирной стрелки ES6 => , которая автоматически привязывается к определяющему объекту. Больше нет необходимости добавлять объявления bind :

 class App extends Component { state = { count: 0 }; incCount = () => { this.setState(ps => { count: ps.count + 1 }) }; render() { return ( <div> <p>{ this.state.count }</p> <button onClick={this.incCount}>add one</button> </div> ); } } 

Поля класса: улучшение?

Определения класса ES6 были упрощенными. Поля класса ES2019 требуют меньше кода, помогают удобочитаемости и предоставляют некоторые интересные возможности объектно-ориентированного программирования.

Использование # для обозначения приватности получило некоторую критику, в первую очередь потому, что это уродливо и похоже на взлом. Большинство языков реализуют private ключевое слово, поэтому попытка использовать этот член вне класса будет отклонена компилятором.

JavaScript интерпретируется. Рассмотрим следующий код:

 class MyClass { private secret = 123; } const myObject = new MyClass(); myObject.secret = 'one-two-three'; 

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

Хотя это неуклюже, запись # является недопустимой вне определения класса. Попытка доступа к myObject.#secret может вызвать синтаксическую ошибку.

Дискуссия будет продолжена, но, как они или нет, поля классов были приняты в нескольких движках JavaScript. Они здесь, чтобы остаться.