Изучая 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]