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

image

Материал, перевод которого мы сегодня публикуем, посвящён разбору паттерна проектирования «Модуль», который позволяет скрывать приватную информацию в замыканиях, давая доступ лишь к тому, что решил сделать общедоступным разработчик. Эта статья предназначена, в основном, для начинающих программистов — для тех, кто, вроде бы, знаком с такими вещами, как замыкания и IIFE, но пока не особенно уверенно ими пользуется.

IIFE


Управлять областью видимости переменных в JavaScript можно, пользуясь паттерном «Модуль». Для того чтобы создать приватную область видимости, можно воспользоваться замыканием. Как известно, функции создают собственные области видимости, содержимое которых отделено от глобальной области видимости:

(function () {
 // здесь находится приватная область видимости
})();

Перед нами — так называемая самовызывающаяся функция (IIFE, Immediately-Invoked Function Expression, немедленно вызываемое функциональное выражение). Такая функция выполняется сразу же после её объявления. Подобные функции удобно использовать для того, чтобы решить некую задачу, которую нужно решить лишь один раз, не оставляя при этом ничего лишнего в глобальной области видимости. Внутри этой функции (как, впрочем, и внутри других функций) создаётся приватная область видимости, недоступная извне. То есть, если объявить внутри этой области видимости другую функцию, то, после того, как IIFE выполнится, доступ к ней получить не удастся.

(function () {
  var myFunction = function () {
    // выполняем здесь некие действия
  };
})();

Попробуем теперь обратиться к функции myFunction из основного текста программы:

myFunction(); // Uncaught ReferenceError: myFunction is not defined

Как видите, что вполне ожидаемо, этот вызов приводит к ошибке. Это говорит нам о том, что даннаяфункция в той области видимости, из которой мы пытаемся к ней обратиться, недоступна. На самом деле, в двух вышеприведённых примерах ничего полезного не делается. Эти примеры нужны нам лишь для того, чтобы подготовиться к разбору паттерна «Модуль».

Возврат объекта из IIFE и API модуля


Как сделать так, чтобы к функции, объявленной внутри другой функции, всё же, можно было бы обратиться? Собственно говоря, то, о чём мы сейчас будем говорить, и есть паттерн «Модуль». Рассмотрим следующий пример.

// Объявим модуль
var Module = (function () {
  return {
    myMethod: function () {
      console.log('myMethod has been called.');
    }
  };
})();

// Вызовем функцию как метод объекта
Module.myMethod();

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

Объект, возвращаемый из IIFE — это обычный объект, у которого может быть множество методов и свойств. Они формируют общедоступный интерфейс или API модуля.

// Объявим модуль
var Module = (function () {
  return {
    myMethod: function () {

    },
    someOtherMethod: function () {

    }
  };
})();

// Вызовем функцию как метод объекта
Module.myMethod();
Module.someOtherMethod();

Приватные переменные и функции, хранящиеся в замыкании


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

Это могут быть временные переменные, или переменные, играющие роль хранилищ неких данных, доступ к которым мы хотим жёстко контролировать. Нас интересует такое устройство модуля, когда внешнему миру доступно лишь то, что должно быть доступно, а всё остальное оказывается скрытым. Собственно говоря, приватным станет всё то, что, в нашем примере, будет объявлено за пределами объекта, возвращаемого из IIFE.

var Module = (function () {
  var privateMethod = function () {

  };
  return {
    publicMethod: function () {

    }
  };
})();

Метод publicMethod из этого примера можно вызвать извне, а функцию privateMethod — нет, так как она находится в приватной области видимости, в замыкании. Именно подобные функции, недоступные извне, могут выполнять роль вспомогательных механизмов модулей. Они могут использоваться для управления внутренними структурами данных, для выполнения каких-то вызовов к неким сервисам, и в других ситуациях.

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

var Module = (function () {
  var privateMethod = function () {

  };
  return {
    publicMethod: function () {
      // у этого метода есть доступ к privateMethod, мы можем вызвать его здесь так:
      // privateMethod();
    }
  };
})();

Благодаря этому мы можем защищать код от несанкционированного вмешательства и защищать глобальную область видимости от загрязнения. Если этого не делать, то, с одной стороны, работа внутренних механизмов модулей может быть случайно или целенаправленно нарушена, из-за того, что внешний код обращается к функциям или переменным, к которым он обращаться не должен. С другой стороны, если не пользоваться описанным здесь подходом, в глобальную область видимости попадает много ненужного, что может, например, привести к конфликтам имён.

Вот пример объекта, возвращаемого из IIFE, который содержит общедоступные методы и может обращаться к приватным функциям:

var Module = (function () {
  var myModule = {};
  var privateMethod = function () {

  };
  myModule.publicMethod = function () {

  };
  myModule.anotherPublicMethod = function () {

  };
  return myModule; // возвращает объект с общедоступными методами
})();

// использование модуля
Module.publicMethod();

Именование приватных и общедоступных функций


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

var Module = (function () {
  var _privateMethod = function () {

  };
  var publicMethod = function () {

  };
  return {
    publicMethod: publicMethod,
  }
})();

Итоги


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

Уважаемые читатели! Пользуетесь ли вы паттерном «Модуль» в своих JS-проектах?

Комментарии (16)


  1. worldxaker
    15.08.2018 12:35
    -1

    ну что за некрофилия??? хватит уже писать переводы устаревших статей. все давно используют ES6 Modules


    1. wert_lex
      15.08.2018 12:40
      +1

      все давно используют ES6 Modules

      Пока на ноде es6 модули не допилили до production-ready. Так что CommonJS и AMD надо как минимум понимать.


      1. worldxaker
        15.08.2018 13:57

        да, но в этой статье даже они не обсуждаются


    1. PYXRU
      15.08.2018 15:10

      Написано:

      Эта статья предназначена, в основном, для начинающих программистов — для тех, кто, вроде бы, знаком с такими вещами, как замыкания и IIFE, но пока не особенно уверенно ими пользуется.

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


      1. zamsky
        15.08.2018 15:55

        Для новичков это реально вредная статья, потому что после появления классов IIFE это отжившая техника.


        1. PYXRU
          15.08.2018 16:00

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


          1. zamsky
            15.08.2018 16:11

            Потому что она дана в старом синтаксисе и на кривом примере.


            1. PYXRU
              15.08.2018 16:26

              Когда это простите синтаксис данный устарел? let, const это не замена var, они другое подразумевают, общего у них что это способы объявление переменных. Или когда IIFE, стали старым синтаксисом.


              1. zamsky
                15.08.2018 16:42

                Ох, ну расскажите тогда, при каком codestyle у вас будут одновременно var, let и const одновременно.
                Или в каком контексте вы планируете в 2018 году использовать IIFE.


                1. PYXRU
                  15.08.2018 16:46

                  Причем тут 2018 года? Тут статья о возможностях языка, а не о том полезно это юзать или нет. Практически уверен, что какие нибудь веб студии или разработка сайтов под заказ, можно найти примеры использования в одном файле let, const, var.Но синтаксис и возможности это разные вещи, не кто не говорит, что возможности языка всегда делают хорошие вещи, но это не делает их бесполезными. Везде надо стараться использовать наиболее оптимальный инструмент для работы, но чтобы выбирать надо знать как можно больше возможностей.


                  1. zamsky
                    15.08.2018 16:51

                    Да причем тут студии? Вы лично используете одновременно let, const, var? Есть ли у вас codestyle, линтеры, вот это всё?

                    Возможности языка надо изучать на современных Best Practices, а не на этом помете мамонтов.


                    1. PYXRU
                      15.08.2018 17:03

                      Эти штуки есть в подавляющем числе IT команд, которые специализируются на разработке программ, сайтов, которые делаются всегда(или будут апгрейдится). А не веб сайты на заказ, где это нафиг никому не надо. Полностью не согласен с этим высказыванием, возможности языка надо просто изучать, потому что их всегда очень много, и надо постоянно поддерживать форму чтобы не забыть. Best Practices на то и Best Practices, они пишутся не того чтобы показать возможности языка, а для того чтобы обойти подводные камни


                      1. zamsky
                        15.08.2018 17:14

                        Вам в чиновники надо, ни на один вопрос прямо ответить не можете.

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


                        1. PYXRU
                          15.08.2018 17:24

                          Я вроде на все вопросы ответил. Я не считаю, что линтеры, style guide, babel и т.д и т.п нужны всем проектам. Например, я пишу себе прогу для телевизора на electron, ну и зачем мне style guide и linter, мне важен результат, должно работать. Все эти штуки придуманы для того, чтобы интеграция новых членов в команду стоила как можно меньше и разработка была продуктивнее.

                          Best Practices нужны для того, чтобы писать красиво, понятно и однозначно.

                          Это предназначение Style Guide

                          Обычно подводные камни называют «особенности» языка.
                          Вот как пример статья habr.com/post/159313


  1. wert_lex
    15.08.2018 12:38
    +1

    Ой. Я просто оставлю это здесь: https://eloquentjavascript.net/2nd_edition/10_modules.html
    Тут всё, что нужно знать про CommonJS и AMD модули. А тут еще и ES6 зацепили: https://eloquentjavascript.net/10_modules.html


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


  1. megahertz
    15.08.2018 18:16

    На хабре уже была отличная статья habr.com/company/yandex/blog/192874