Выражения немедленно вызываемых функций (Immediately Invoked Function Expressions (IIFEs)) — КиберПедия 

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

Своеобразие русской архитектуры: Основной материал – дерево – быстрота постройки, но недолговечность и необходимость деления...

Выражения немедленно вызываемых функций (Immediately Invoked Function Expressions (IIFEs))

2020-10-20 98
Выражения немедленно вызываемых функций (Immediately Invoked Function Expressions (IIFEs)) 0.00 из 5.00 0 оценок
Заказать работу

В предыдущем примере ни одно из выражений с функциями не выполнялось, мы могли бы это сделать, включив в код foo() или x(), например.

Есть еще один способ выполнить выражение с функцией, на который обычно ссылаются как на immediately invoked function expression (IIFE):

(function IIFE(){    console.log("Hello!");})();// "Hello!"

Внешние (..), которые окружают выражение функции (function IIFE(){.. }), — это всего лишь нюанс грамматики JS, необходимый для предотвращения того, чтобы это выражение воспринималось как объявление обычной функции.

Последние () в конце выражения, строка })(); — это то, что и выполняет выражение с функцией, указанное сразу перед ним.

Может показаться странным, но это не так уж чужеродно, как кажется на первый взгляд. Посмотрите на сходства между foo и IIFE тут:

function foo() {.. } // `foo` выражение со ссылкой на функцию,// затем `()` выполняют ееfoo(); // Выражение с функцией `IIFE`,// затем `()` выполняют ее(function IIFE(){.. })();

Как видите, содержимое (function IIFE(){.. }) до ее вызова в () фактически такое же, как включение foo до его вызова после (). В обоих случаях ссылка на функцию выполняется с помощью () сразу после них.

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

var a = 42; (function IIFE(){    var a = 10;    console.log(a); // 10})(); console.log(a);    // 42

Функции IIFE также могут возвращать значения:

var x = (function IIFE(){    return 42;})(); x;  // 42

Значение 42 возвращается из выполненной IIFE функции, а затем присваивается в x.

Замыкание

Замыкание является одной из самых важных и зачастую наименее понятных концепций в JavaScript. Я не буду вдаваться в подробности сейчас, а вместо этого направляю вас в книгу Область видимости и замыкания этой серии. Но я хотел бы сказать несколько слов о замыканиях, чтобы вы понимали общую концепцию. Это будет важной техникой в вашем наборе навыков в JS.

Вы можете думать о замыкании как о способе "запомнить" и продолжить работу в области видимости функции (с ее переменными) даже когда функция уже закончила свою работу.

Проиллюстрируем:

function makeAdder(x) {    // параметр `x` - внутренняя переменная     // внутренняя функция `add()` использует `x`, поэтому    // у нее есть "замыкание" на нее    function add(y) {            return y + x;    };     return add;}

Ссылка на внутреннюю функцию add(..), которая возвращается с каждым вызовом внешней makeAdder(..), умеет запоминать какое значение x было передано в makeAdder(..). Теперь давайте используем makeAdder(..):

// `plusOne` получает ссылку на внутреннюю функцию `add(..)`// с замыканием на параметре `x`// внешней `makeAdder(..)`var plusOne = makeAdder(1); // `plusTen` получает ссылку на внутреннюю функцию `add(..)`// с замыканием на параметре `x`// внешней `makeAdder(..)`var plusTen = makeAdder(10); plusOne(3);        // 4 <-- 1 + 3plusOne(41);       // 42 <-- 1 + 41 plusTen(13);       // 23 <-- 10 + 13

Теперь подробней о том, как работает этот код:

  1. Когда мы вызываем makeAdder(1), мы получаем обратно ссылку на ее внутреннюю add(..), которая запоминает x как 1. Мы назвали эту ссылку на функцию plusOne(..).
  2. Когда мы вызываем makeAdder(10), мы получаем обратно ссылку на ее внутреннюю add(..), которая запоминает x как 10. Мы назвали эту ссылку на функцию plusTen(..).
  3. Когда мы вызываем plusOne(3), она прибавляет 3 (свою внутреннюю y) к 1 (которая запомнена в x), и мы получаем в качестве результата 4.
  4. Когда мы вызываем plusTen(13), она прибавляет 13 (свою внутреннюю y) к 10 (которая запомнена в x), и мы получаем в качестве результата 23.

Не волнуйтесь, если всё это кажется странным и сбивающим по началу с толку — это нормально! Понадобится много практики, чтобы всё это полностью понять.

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

Модули

Самое распространенное использование замыкания в JavaScript — это модульный шаблон. Модули позволяют определять частные детали реализации (переменные, функции), которые скрыты от внешнего мира, а также публичное API, которое доступно снаружи.

Представим:

function User(){    var username, password;     function doLogin(user,pw) {            username = user;            password = pw;             // сделать остальную часть работы по логину    }     var publicAPI = {            login: doLogin    };     return publicAPI;} // создать экземпляр модуля `User`var fred = User(); fred.login("fred", "12Battery34!");

Функция User() служит как внешняя область видимости, которая хранит переменные username и password, а также внутреннюю функцию doLogin(). Всё это частные внутренние детали этого модуля User, которые недоступны из внешнего мира.

Предупреждение: Мы не вызываем тут new User() намеренно, несмотря на тот факт, что это будет более естественно для большинства читателей. User() — просто функция, а не класс, поэтому она вызывается обычным образом. Использование new было бы неуместной тратой ресурсов.

При выполнении User() создается экземпляр модуля User: новая область видимости и также совершенно новая копия каждой из внутренних переменных/функций. Мы присваиваем этот экземпляр в fred. Если мы запустим User() снова, то получим новый экземпляр, никак не связанный с fred.

У внутренней функции doLogin() есть замыкание на username и password, что значит, что она сохранит свой доступ к ним даже после того, как функция User() завершит свое выполнение.

publicAPI — это объект с одним свойством/методом, login, который является ссылкой на внутреннюю функцию doLogin(). Когда мы возвращаем publicAPI из User(), он становится экземпляром, который мы назвали fred.

На данный момент внешняя функция User() закончила выполнение. Как правило, вы думаете, что внутренние переменные, такие как username и password, при этом исчезают. Но они никуда не деваются, потому что есть замыкание в функции login(), хранящее их.

Вот поэтому мы можем вызвать fred.login(..), что подобно вызову внутренней doLogin(..), и у нее все еще будет доступ ко внутренним переменным username и password.

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

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

Идентификатор this

Еще одна очень часто неверно понимаемая концепция в JavaScript — это идентификатор this. Опять таки, есть пара глав по нему в книге this и прототипы объектов этой серии, поэтому здесь мы только кратко его рассмотрим.

При том что может часто казаться, что ключевое слово this связано с "объектно-ориентированным шаблонами", в JS this — это другой механизм.

Если у функции есть внутри ссылка this, эта ссылка this обычно указывает на объект. Но на какой объект она указывает зависит от того, как эта функция была вызвана.

Важно понимать, что this не ссылается на саму функцию, хотя это самое распространенное неверное представление.

Вот краткая иллюстрация:

function foo() {    console.log(this.bar);} var bar = "global"; var obj1 = {    bar: "obj1",    foo: foo}; var obj2 = {    bar: "obj2"}; //-------- foo();                        // "global"obj1.foo();                   // "obj1"foo.call(obj2); // "obj2"new foo();                    // undefined

Есть четыре правила того, как устанавливается this, и они показаны в этих четырех последних строках кода.

  1. foo() присваивает в this ссылку на глобальный объект в нестрогом режиме. В строгом режиме, this будет undefined, и вы получите ошибку при доступе к свойству bar, поэтому "global" — это значение для this.bar.
  2. obj1.foo() устанавливает this в объект obj1.
  3. foo.call(obj2) устанавливает this в объект obj2.
  4. new foo() устанавливает this в абсолютно новый пустой объект.

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

Примечание: Более детальная информация о ключевом слове this есть в главах 1 и 2 книги this и прототипы объектов этой серии.

Прототипы

Механизм прототипов в JavaScript довольно сложен. Здесь мы только немного взглянем на него. Вам потребуется потратить много времени, изучая главы 4-6 книги this и прототипы объектов этой серии, чтобы получить детальную информацию.

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

Связывание ссылки на внутренний прототип объекта к его резервному варианту происходит в момент создания объекта. Простейшим способом проиллюстрировать это является вызов встроенной функции Object.create(..).

Пример:

var foo = {    a: 42}; // создаем `bar` и связываем его с `foo`var bar = Object.create(foo); bar.b = "hello world"; bar.b;      // "hello world"bar.a;      // 42 <-- делегируется в `foo`

Следующая картинка поможет визуально показать объекты foo и bar и их связь:

Свойства a на самом деле не существует в объекте bar, но поскольку bar прототипно связан с foo, JavaScript автоматически прибегает к поиску a в объекте foo, где оно и находится.

Такая связь может показаться странной возможностью языка. Самым распространенным способом ее использования (я бы поспорил об его правильности) является эмулирование "классового наследование".

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

Примечание: Более детальная информация о прототипах и делегировании поведения есть в главах 4-6 книги this & прототипы объектов этой серии.

Старый и новый

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

Так что же вам делать со всеми этими новыми вещами? Нужно ли ждать годы или десятилетия, чтобы все старые браузеры канули в лету?

Хотя именно так думают многие люди, это совсем не здравый подход к JS.

Есть две основные техники, которыми можно пользоваться, чтобы "привнести" более новые возможности JavaScript в старые браузеры: полифиллинг (polyfilling) и транспиляция (transpiling).

Полифиллинг (polyfilling)

Слово "polyfill", введенный Реми Шарпом термин (https://remysharp.com/2010/10/08/what-is-a-polyfill), означает определение новой функции с помощью кода, реализующего эквивалентное поведению, но с возможностью запуска в более старых окружениях JS.

Например, ES6 определяет функцию, называемую Number.isNaN(..), для обеспечения точной, безошибочной проверки на значения NaN, отменяя устаревшую первоначальную функцию isNaN(..). Но очень легко заполифиллить новую функцию, с целью пользования ею в вашем коде независимо от того, поддерживает браузер ES6 или нет.

Пример:

if (!Number.isNaN) {    Number.isNaN = function isNaN(x) {            return x!== x;    };}

Оператор if защищает против применения полифильного определения в браузерах, поддерживающих ES6 синтаксис. Если же функция не существует, то мы определяем Number.isNaN(..).

Примечание: Проверка, которую мы тут выполняем, использует преимущество причудливости значения NaN, которое заключается в том, что оно является единственным не равным самому себе значением во всем языке. Поэтому значение NaN — единственное, делающее условие x!== x истинным.

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

А лучше используйте уже проверенный набор полифиллов, которому вы можете доверять, вроде тех, что предоставляются ES5-Shim (https://github.com/es-shims/es5-shim) и ES6-Shim (https://github.com/es-shims/es6-shim).

Транспиляция (Transpiling)

Не существует возможности полифиллить новый синтаксис, который был добавлен в язык. Новый синтаксис вызовет ошибку в старом движке JS, как нераспознанный/невалидный.

Поэтому лучшим выбором будет использовать утилиту, которая конвертирует ваш более новый код в эквивалент более старого. Этот процесс обычно называют "транспиляцией", как объединение терминов трансформация и компиляция (transforming + compiling).

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

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

Есть несколько важных причин, по которым вам следует транспилить код:

  • Новый синтаксис, добавленный в язык, разрабатывается, с целью сделать ваш код более читаемым и поддерживаемым. Старые эквиваленты часто намного более запутаны. Следует писать с помощью более нового и ясного синтаксиса, не только для себя, но и для всех остальных членов команды разработки.
  • Если вы транспилите только для старых браузеров, но используете новый синтаксис в новейших браузерах, вы получаете преимущество оптимизации производительности браузера с помощью нового синтаксиса. Это также позволяет разработчикам браузеров делать код более приближенным к жизни для проверки их реализаций и оптимизаций.
  • Использование нового синтаксиса как можно раньше позволяет ему быть протестированным более тесно в реальном мире, что обеспечивает более ранние отзывы в комитет JavaScript (TC39). Если проблемы обнаружены достаточно рано, то их можно изменить/устранить до того, как эти ошибки дизайна языка станут постоянными.

Вот небольшой пример транспиляции. ES6 добавляет возможность, называемую "значения параметров по умолчанию". Это выглядит примерно так:

function foo(a = 2) {    console.log(a);} foo();      // 2foo(42);  // 42

Просто, правда? Еще и полезно! Но это как раз новый синтаксис, который будет считаться невалидным в до-ES6 движках. Так что же транспилятор сделает с этим кодом, чтобы заставить его работать в более старых движках?

function foo() {    var a = arguments[0]!== (void 0)? arguments[0]: 2;    console.log(a);}

Как видите, он проверяет, равно ли значение arguments[0] значению void 0 (т.е. undefined); если да, то предоставляет значение по умолчанию 2, иначе он присваивает то, что было передано.

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

Возможно, просто глядя на версию ES6, вы не задумывались, что undefined — единственное значение, которое не может быть явно задано значением по умолчанию для параметра, но транспилированный код показывает это гораздо яснее.

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

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

Есть довольно много отличных транспиляторов на выбор. Вот несколько из них на момент написания этого текста:

  • Babel (https://babeljs.io) (бывший 6to5): Транспилирует из ES6+ в ES5
  • Traceur (https://github.com/google/traceur-compiler): Транспилирует из ES6, ES7 и далее в ES5

Не-JavaScript

На данный момент, мы рассмотрели только вещи, касающиеся самого языка JS. Реальность такова, что большая часть JS написана для запуска и взаимодействия с такими средами как браузеры. Добрая часть вещей, которые вы пишете в своем коде, строго говоря, не контролируется напрямую JavaScript. Возможно это звучит несколько странно.

Самый распространенный не-JavaScript JavaScript, с которым вы столкнетесь — это DOM API. Например:

var el = document.getElementById("foo");

Переменная document существует как глобальная переменная, когда ваш код выполняется в браузере. Она ни обеспечивается движком JS, ни особенно контролируется спецификацией JavaScript. Она принимает форму чего-то ужасно похожего на обычный JS объект, но не является им на самом деле. Это специальный объект, часто называемый "хост-объектом."

Более того, метод getElementById(..) в document выглядит как обычная функция JS, но это всего лишь кое-как открытый интерфейс к встроенному методу, предоставлeнному DOM из вашего браузера. В некоторых браузерах (нового поколения) этот слой может быть на JS, но традиционно DOM и его поведение реализовано на чем-то вроде C/C++.

Еще один пример с вводом/выводом (I/O).

Всеобщее любимое всплывающее окно alert(..) в пользовательском окне браузера. alert(..) предоставляется вашей JS программе браузером, а не самим движком JS. Вызов, который вы делаете, отправляет сообщение во внутренности браузера, и уже браузер обрабатывают отрисовку и отображение окна с сообщением.

То же происходит и с console.log(..): ваш браузер предоставляет подобные механизмы и подключает их к средствам разработчика.

Эта книга, да и вся эта серия, фокусируется на языке JavaScript. Поэтому вы не увидите какого-либо подробного раскрытия деталей об этих не-JavaScript механизмах JavaScript. Как бы то ни было, вам не нужно забывать о них, поскольку они будут в каждой написанной вами JS программе!

Обзор

Первым шагом в изучении духа программирования на JavaScript является получение первичного представления о его внутренних структурах, таких как значения, типы, замыкания функций, this и прототипы.

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

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

 

Глава 3: Введение в "Вы не знаете JS"

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

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

Серия Вы не знаете JS (YDKJS) представляет разительный контраст с типичными подходами к изучению языка и непохожа почти на все другие книги о JS, которые вы прочтете. Она требует от вас выйти из зоны комфорта и задать серьезные вопросы "почему?" для всех до единой функциональных возможностей, с которыми вы столкнетесь. Вы готовы заняться этой задачей?

Я использую эту последнюю главу, чтобы кратко подвести итог того, что ждать от остальных книг серии, и как наиболее эффективно приняться за постройку основы обучения JS, держа в своих руках YDKJS.


Поделиться с друзьями:

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

Своеобразие русской архитектуры: Основной материал – дерево – быстрота постройки, но недолговечность и необходимость деления...

Биохимия спиртового брожения: Основу технологии получения пива составляет спиртовое брожение, - при котором сахар превращается...

Эмиссия газов от очистных сооружений канализации: В последние годы внимание мирового сообщества сосредоточено на экологических проблемах...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.044 с.