Классы и свойства JavaScript ES2015 часть 2

Классы, объекты и свойства

В предыдущей статье мы рассматривали новый синтаксис классов и тонкости наследования JavaScript prototype. В этом материале мы продолжим рассматривать свойства, которые могут быть установлены с помощью нового синтаксиса классов. В частности, мы рассмотрим свойства функций, аксессоров и статические свойства. Разделение функций с несколькими объектами является основной целью наследования прототипа. Поэтому понимание того, как настроить функции в классах, имеет большое значение.

Свойства акссесора были введены одновременно с ECMAScript 5 (ES5), но многие разработчики до сих пор не знают, что они существуют. Мы рассмотрим новый синтаксис классов, который позволяет проще работать с ними.

Наконец, многие языки программирования поддерживают статические члены в определениях классов. Статические члены доступны непосредственно в классе, без установки конкретного экземпляра объекта. Такие свойства используются для вспомогательных функций, связанных с классом. Несмотря на то, что в JavaScript не существует понятия статических свойств, новый синтаксис классов предлагает своеобразный аналог статического свойства через ключевое слово static.

Свойства функций

Основной целью JavaScript prototype наследования является возможность наследования свойств функций (свойств, которые указывают на какую-либо функцию) от родительского объекта к дочернему. Значение свойства должно существовать непосредственно в дочернем объекте, поскольку значения специфичны для конкретного объекта. Кроме этого прохождение по цепочке прототипов для получения значений объекта является неэффективным. Но свойства функций отличаются от свойств значений. Очень редко реализации функций специфичны для конкретного объекта. Разделяя один и тот же объект между несколькими объектами, мы снижаем потребление памяти приложением JavaScript.

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

// объект функции 'getFullName'
function getFullName() {
    return this.firstName + " " + this.lastName;
}

var person1 = {
  firstName: "Bob",
  lastName: "Smith",
  // свойство с именем 'getFullName' привязано к приведенному выше объекту функции 'getFullName'
  getFullName: getFullName
};

var person2 = {
  firstName: "Tim",
  lastName: "Johnson",
  // свойство с именем 'getFullName' привязано к приведенному выше объекту функции 'getFullName'
  getFullName: getFullName
};

// вывод "Bob Smith"
console.log(person1.getFullName());

// вывод "Tim   Johnson"
console.log(person2.getFullName());

// вывод "true" потому что оба свойства указывают на ту же функцию
console.log(person1.getFullName === person2.getFullName);

Нажмите здесь, чтобы загрузить код [this.js]

Свойство ‘getFullName‘ для обоих объектов, Person1 и Person2, указывает на объект функции ‘getFullName‘. Повторно используя одну и ту же функцию для обоих объектов, мы потребляем меньший объем памяти, разделяя функцию между разными объектами.

Эта концепция в сочетании с JavaScript prototype позволяет определять функцию в прототипе объекта. Все объекты, унаследованные от прототипа, могут использовать ту же самую функцию со значением this для каждого вызова функции, которое специфично для объекта, для которого она выполняется.

Чтобы определить свойства, которые будут унаследованы от прототипа, используется следующий синтаксис:

"use strict";

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  // определение функции
  // свойство, связанное с функцией, будет существовать
  // в прототипе объекта, а не в каждом экземпляре Person
  getFullName() {
    return this.firstName + " " +   this.lastName;
  }
}

var person = new Person("Bob", "Smith");
// применение функции в экземпляре объекта
// функция связана с прототипом
// чтобы найти функцию, обрабатывается цепочка
// прототипа, когда функция выполняется внутри
// контекста экземпляра 'person'
console.log(person.getFullName());

Нажмите здесь, чтобы загрузить код [functions.js]

Обратите внимание, что функция ‘getFullName‘ определяется без использования ключевого слова function. С помощью синтаксиса класса ES2015 ключевое слово function опускается из определения объекта функции. Эта функция не будет прямым свойством объекта Person. Вместо этого она будет в JavaScript function prototype Person, от которого наследуется объект.

Свойства получателя / установщика

Свойства получателя и установщика не являются новыми в JavaScript, хотя немногие разработчики знают, как их использовать. После добавления в ES5 дескрипторов свойств появилась возможность определять свойства объекта как стандартные свойства значений. А также использовать функции доступа для получения и установки значений свойств:

"use strict";
class Person {

constructor(firstName, lastName) {
  // к внутренним свойства могут добавляться префиксы через нижнее подчеркивание
  this._firstName = firstName;
  this._lastName = lastName;
}

// имя свойства используется во время определения функции аксессора
get firstName() {
  // возвращаем значение внутреннего свойства,
  // также здесь может быть выполнена другая логика
  // внутренние свойства – это конвенция, а не
  // часть возвращаемого языком JavaScript this._firstName;
}

// функция аксессора определяется в прототипе, в то время как
// значения хранятся во внутренних свойствах в экземпляре объекта
set firstName(value) {
  // устанавливаем значения внутренних свойств,
  // здесь также может выполняться валидация или другая логика
  this._firstName = value;
  }
}

var person = new Person("Bob", "Smith");

// свойства устанавливаются как обычные свойства значений, аксессоры вызываются безоговорочно
person.firstName = "Tommy";

// получаем операции, которые работают похожим образом
console.log(person.firstName);

Нажмите здесь, чтобы загрузить код [accessors.js]

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

JavaScript предоставляет конвенцию для определения статических свойств объектов. Классы ES2015 продолжают эту конвенцию через ключевое слово static:

"use strict";

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  //   определение статической функции
  // недоступно в экземпляре объекта
  // функция определяется в самом объекте класса (функции),
  // не прототипе; поэтому она не наследуется
  // и не может быть замещена (переназначена)
  // дочерним объектом
  static getFormattedName(person) {
    return person.lastName + ", " + person.firstName;
  }
}

var person = new Person("Bob", "Smith"); // связываем статическую функцию непосредственно с объектом класса (функции)
console.log(Person.getFormattedName(person));

Статическая функция определяется непосредственно в самом классе, и недоступна через использование объекта this. Статические функции используются в качестве вспомогательных функций, связанных с классом.

Нажмите здесь, чтобы загрузить код [static.js]

Закрытые свойства

ES2015 до сих пор формально не поддерживает использование public, protected, и private модификаторов доступа для свойств объекта. В JavaScript все свойства являются public. Некоторые препроцессоры, такие как TypeScript, добавили поддержку транспиляции для public и private модификаторов доступа. С помощью использования выражения и private классов в JavaScript можно реализовать закрытые свойства:

"use strict";

var Person = {
  // функция-оболочка необходима, чтобы создать новую закрытую переменную для каждого
  // экземпляра класса
  // исходный код определения класса может быть определен в функции-оболочке
  // потому что закрытие зависимо от лексической структуры кода
  create: function(...cargs) {
    let _name = undefined;
    return new class {
      constructor(name) {
        _name = name;
      }
      get name() {
        return _name;
      }
      set name(value) {
        _name = value;
      }
    }(...cargs);
  }
};

// вызов функции-оболочки для создания объекта
var person =   Person.create("Bob Smith");
console.log(person.name);

Нажмите здесь, чтобы загрузить код [private.js]

Хотя эта конструкция работает, она имеет недостатки. private используются в JavaScript повсеместно, но это может снизить производительность приложений и привести к потреблению дополнительных ресурсов памяти. Создавать объемные закрытия, чтобы скрыть некоторые свойства, не рекомендуется. Кроме этого новый объект определения класса создается при каждом вызове функции-оболочки. Определение класса должно выполняться в функции-оболочке. Создание определения класса снова приведет к потреблению дополнительных ресурсов. Это ключевой момент, который вы должны помнить при использовании JavaScript object prototype.

JavaScript не C ++, не Java и не C #. Он не поддерживает классическое наследование и в настоящее время не предназначен для того, чтобы «имитировать» характерные черты классического наследования, такие как модификаторы доступа. Добавление через подчеркивания или другие символы (Angular.js использует $) префиксов private (лучше назвать их внутренними) свойств является общепринятой практикой, и приводит к достижению той же цели, не вызывая снижения производительности.

Заключение

Классы ES2015 улучшили синтаксис для определения свойств наследования объектов, свойств получателя / установщика. Они также четче выделяют различия между тем, какие свойства определяются в экземпляре объекта, какие наследуются, а какие рассматриваются, как статические. Классы ES2015 не меняют характер JavaScript prototype, но делают наследование более доступным для JavaScript разработчиков.

Перевод статьи «JavaScript ES2015 Classes and Properties (Part 2 of 2)» был подготовлен дружной командой проекта Сайтостроение от А до Я.