Pull to refresh

Книга «Как устроен JavaScript»

Reading time7 min
Views15K
image Большинство языков программирования выросли из древней парадигмы, порожденной еще во времена Фортрана. Гуру JavaScript Дуглас Крокфорд выкорчевывает эти засохшие корни, позволяя нам задуматься над будущим программирования, перейдя на новый уровень понимания требований к Следующему Языку (The Next Language).

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

Отрывок
Как работает код без классов


И думаешь, что ты умен вне всяких классов и свободен.
Джон Леннон (John Lennon)

Одной из ключевых идей в разработке объектно-ориентированного программирования была модель обмена данными между частями программы. Имя метода и его аргументы нужно представлять в виде сообщений. Вызов метода посылает объекту сообщение. Каждый объект характеризуется собственным поведением, которое проявляется при получении конкретных сообщений. Отправитель полагает, что получатель знает, что ему делать с сообщением.

Одной из дополнительных выгод является полиморфизм. Каждый объект, распознавший конкретное сообщение, имеет право на его получение. Что происходит потом, зависит от специализации объекта. И это весьма продуктивная мысль.

К сожалению, мы стали отвлекаться на наследование — весьма эффективную схему повторного использования кода. Его важность связана с возможностью уменьшить трудозатраты при разработке программы. Наследование выстраивается на схожем замысле, за исключением некоторых нюансов. Можно сказать, что некоторый объект или класс объектов подобен какому-то другому объекту или классу объектов, но имеет некоторые важные отличия. В простой ситуации все работает замечательно. Следует напомнить, что современное ООП началось со Smalltalk — языка программирования для детей. По мере усложнения ситуации наследование становится проблематичным. Оно порождает сильное сцепление классов. Изменение одного класса может вызвать сбой в тех классах, которые от него зависят. Модули из классов получаются просто никудышными.

Кроме того, мы наблюдаем повышенное внимание к свойствам, а не к объектам. Особое внимание уделяется методам получения (get-методам) и присваивания (set-методам) значений каждому отдельно взятому свойству, а в еще менее удачных проектах свойства являются открытыми и могут быть изменены без ведома объекта. Вполне возможно ввести в обиход более удачный проект, где свойства скрыты, а методы обрабатывают транзакции, не занимаясь только лишь изменением свойств. Но такой подход применяется нечасто.

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

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

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

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

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

Конструктор


В главе 13 мы работали с фабриками — функциями, возвращающими функции. Что-то похожее теперь можем сделать с конструкторами — функциями, возвращающими объекты, которые содержат функции.

Начнем с создания counter_constructor, похожего на генератор counter. У него два метода, up и down:

function counter_constructor() {
      let counter = 0;

      function up() {
            counter += 1;
            return counter;
      }

      function down() {
            counter -= 1;
            return counter;
      }

      return Object.freeze({
            up,
            down
      });
}

Возвращаемый объект заморожен. Он не может быть испорчен или поврежден. У объекта есть состояние. Переменная counter — закрытое свойство объекта. Обратиться к ней можно только через методы. И нам не нужно использовать this.

Это весьма важное обстоятельство. Интерфейсом объекта являются исключительно методы. У него очень крепкая оболочка. Мы получаем наилучшую инкапсуляцию. Прямого доступа к данным нет. Это весьма качественная модульная конструкция.

Конструктор — это функция, возвращающая объект. Параметры и переменные конструктора становятся закрытыми свойствами объекта. В нем нет открытых свойств, состоящих из данных. Внутренние функции становятся методами объекта. Они превращают свойства в закрытые. Методы, попадающие в замороженный объект, являются открытыми.

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

Одна из блестящих идей в JavaScript — объектный литерал. Это приятный и выразительный синтаксис для кластеризации информации. Создавая методы, потребляющие и создающие объекты данных, можно сократить количество методов, повышая тем самым целостность объекта.

Получается, у нас есть два типа объектов.

  • Жесткие объекты содержат только методы. Эти объекты защищают целостность данных, содержащихся в замыкании. Они обеспечивают нас полиморфизмом и инкапсуляцией.
  • Мягкие объекты данных содержат только данные. Поведение у них отсутствует. Это просто удобная коллекция, с которой могут работать функции.

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

Если жесткий объект должен быть преобразован в строку, нужно включить метод toJSON. Иначе JSON.stringify увидит его как пустой объект, проигнорировав методы и скрытые данные (см. главу 22).

Параметры конструктора


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

Будь я предусмотрительнее, у меня был бы конструктор, получающий в качестве параметра один объект. Обычно он берется из объектного литерала, но может поступать и из других источников, например из JSON-содержимого.

Это дало бы множество преимуществ.

  • Ключевые строки придают коду задокументированный вид. Код легче читается, поскольку он сам сообщает вам, что представляет собой каждый аргумент вызывающей стороны.
  • Аргументы могут располагаться в любом порядке.
  • В будущем можно добавлять новые аргументы, не повреждая существующий код.
  • Неактуальные параметры можно игнорировать.

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

function my_little_constructor(spec) {
      let {
           name, mana_cost, colors, type, supertypes, types, subtypes, text,
           flavor, power, toughness, loyalty, timeshifted, hand, life
      } = spec;

Этот код создает и инициализирует 15 закрытых переменных, используя свойства с такими же именами из spec. Если в spec нет соответствующего свойства, происходит инициализация новой переменной, которой присваивается значение undefined. Это позволяет заполнять все пропущенное значениями по умолчанию.

Композиция


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

function my_little_constructor(spec) {
      let {компонент} = spec;
      const повторно_используемый = other_constructor(spec);
      const метод = function () {
            // могут применяться spec, компонент, повторно_используемый, метод
      };
      return Object.freeze({
            метод,
            повторно_используемый.полезный
      });
}

Ваш конструктор способен вызвать столько других конструкторов, сколько нужно для получения доступа к управлению состоянием и обеспечиваемому ими поведению. Ему даже можно передать точно такой же объект spec. Документируя spec-параметры, мы перечисляем свойства, нужные my_little_constructor, и свойства, необходимые другим конструкторам.

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

Размер


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

Разницу можно сократить, улучшив модульность. Акцент на транзакциях, а не на свойствах позволяет уменьшить количество методов, а заодно улучшить связанность.

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

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

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — JavaScript

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Tags:
Hubs:
+7
Comments4

Articles

Information

Website
piter.com
Registered
Founded
Employees
201–500 employees
Location
Россия