Объекты в JavaScript

Изучая JavaScript, в какой-то момент я посмотрел, что означает «объектно-ориентированный».

Это было ошибкой.

Во-первых, я познакомился со слишком большим количеством бесполезных диаграмм ООП. Наверняка вам они тоже попадались:

Во-вторых, я почитал об объектах с точки зрения программистов, работающих с Java и C++. Но их точка зрения мало применима к JavaScript. Я читал о «сокрытии информации», «инкапсуляции» и «полиморфизме», но не понимал, какое отношение они имеют к делу. (Как оказалось, никак они к делу и не относятся).

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

Я годами не чувствовал необходимости возиться с прототипами, классами, конструкторами и прочим для 98% моего кода. Так почему же от меня ожидается, что я буду использовать все это повсеместно или непременно «правильно» писать объектно-ориентированный код?

Оказалось, что объектно-ориентированный код хорош в некоторых вещах, но не во всех

С тех пор я понял, что JavaScript переиспользует свои мощные объекты для многих не объектно-ориентированных целей. И большинство из этих целей намного проще. Я использовал объекты в JavaScript примерно для 4 вещей:

  • группировка переменных
  • пространство имен
  • типизированные данные
  • собственно полнофункциональные объекты

Первые три не связаны с «настоящей» объектной ориентацией (что бы это ни значило). Но они используют имеющийся в JavaScript объектный механизм, потому что это удобно.

1. Группировка переменных

Порой вам нужно собрать связанные переменные вместе. В других языках для этого используются словари, хэши или другие структуры. Perl имеет 6 способов сделать это.

Это похоже на префиксные имена переменных, которые идут вместе:

var player_x = 0;
var player_y = 0;
var player_health = 100;

// or:
var player = {
 x: 0,
 y: 0,
 health: 100
};

В других языках специфика группировки переменных обычно обусловлена соображениями производительности. Например, структуры языка C группируют значения вместе, чтобы сделать их эффективными в байтах, но если вы хотите во время выполнения программы искать свойства по имени, вам понадобится хэш-функция/таблица.

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

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

function getCoordinates (
  target,
  { relativeTo } // Using object destructuring for optional arguments
) {
  let ret = { x: 0, y: 0, relativeTo };
  if (relativeTo) {
    x = getHorizontalOffset(target, relativeTo[0]);
    y = getVerticalOffset(target, relativeTo[1]);
  } else {
    x = getHorizontalOffset(target);
  }

  return ret; // Returning 3 related variables in the same object
}

2. Пространство имен

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

Эти вещи могли бы быть отдельными идентификаторами с префиксом math_ — в старых языках всегда так делалось. Но использование объекта выглядит немного чище. Сравните это с бесчисленными функциями в глобальной области видимости PHP.

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

(Это также известно как статические свойства/методы, что является отстойным названием. JS заразился этим от других, менее веселых языков программирования).

3. Типизированные объекты данных

Типизированные объекты данных (очень точный термин — сам придумал!) связывают данные со способами чтения и манипулирования этими данными. Например, Date, DOMRect или Array.

  • Типизированные объекты данных — это тот случай, когда this и методы становятся полезными — методы, никак не использующие this, являются просто именованными функциями.
  • Я думаю, что это тот уровень, на котором мог бы стать полезным instanceof, но ошибки проектирования JS/DOM популяризировали утиную типизацию в мире JavaScript.

Эти объекты позволяют изменять их данные различными способами, а различные способы чтения этих данных будут обновляться в соответствии с вашими изменениями:

var d = new Date();
d.toDateString(); // "Sat Apr 11 2020"
d.setMonth(7);
d.toDateString(); // "Tue Aug 11 2020"

Объекты подобного типа могут использовать (и часто используют) скрытие информации/внутренние представления, но это не обязательно. Например, Date хранит только одну временную метку UNIX в виде целого числа, и даже позволяет вам просматривать и изменять это целое число напрямую. А что касается TypedArray, то здесь название говорит само за себя.

Например, объект Color, чей конструктор принимает любую строку, которую CSS может разобрать как цвет, а затем расширяет ее в объект со свойствами R, G, B и A. (Эта часть остается в качестве упражнения для читателя, поскольку она на удивление трудна).

Сначала объект выглядит следующим образом:

{
  R: 255,
  G: 40,
  B: 177,
  A: 255
}

Выглядит не намного удобнее, чем использование hex-кодов прямо в JS, как 0xff28b1ff. Но, сделав цвет объектом, можно добавить полезные фичи:

  • гарантия, что R, G и B никогда не будут больше 255 или меньше 0.
  • возможность реализовать методы tint() и shade(), которые будут легче для восприятия, чем подгонка RGB вручную.
  • объект Color может возвращать числовой эквивалент hex-кода для .valueOf() и выводить человекочитаемый синтаксис hsla() для .toString() или массив для передачи данных при помощи .toJSON().

4. Полнофункциональные объекты

Наконец, есть полнофункциональные объекты, ведущие себя как… объекты. Типа элементов DOM, Document и так далее. Они используют все возможности this, и им это необходимо. Можно смоделировать такие вещи в функциональном программировании, но придется так усиленно избегать this, что в итоге вы изобретете that.

Но HTMLInputElement.validity.validate() очень хорош. Он использует кучу свойств в одном конкретном, видимом для пользователя объекте, а затем изменяет то, что делает элемент, чтобы отразить вычисления. А если вы вызовете метод .focus() на одном <input>, это изменит свойства других и свойство activeElement документа, которому они все принадлежат.

Для примера рассмотрим объекты jQuery и их, казалось бы, простой API:

$('.widget').addClass('loaded').fadeIn().on('click', activateWidget);

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

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

Итоги

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

Перевод статьи «I think I finally “get” JS objects».

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх