Замыкания
Содержание:
- Параметры по умолчанию
- Функции через =>
- Чего НЕ может JavaScript в браузере?
- «Поднятие» переменных и функций
- Свойство «length»
- Усиливаем пример с определением имени. Тесты
- Объявление и вызов функции
- Параметры по умолчанию
- Outer variables
- Проблема «несуществующего свойства»
- Языки «над» JavaScript
- Переменные
- new Function
- Function Expressions
- addEventListener
- Arrow Functions
- В интернете кто-то не прав
- Потеря «this»
- Вложенные функции
- Вызов функций
- Итого
Параметры по умолчанию
Можно указывать параметры по умолчанию через равенство , например:
Параметр по умолчанию используется при отсутствующем аргументе или равном , например:
При передаче любого значения, кроме , включая пустую строку, ноль или , параметр считается переданным, и значение по умолчанию не используется.
Параметры по умолчанию могут быть не только значениями, но и выражениями.
Например:
Заметим, что значение выражения будет вычислено, и соответствующие функции вызваны – лишь в том случае, если это необходимо, то есть когда функция вызвана без параметра.
В частности, выражение по умолчанию не вычисляется при объявлении функции. В примере выше функция будет вызвана именно в последней строке, так как не передан параметр.
Функции через =>
Появился новый синтаксис для задания функций через «стрелку» .
Его простейший вариант выглядит так:
Эти две записи – примерно аналогичны:
Как видно, – это уже готовая функция. Слева от находится аргумент, а справа – выражение, которое нужно вернуть.
Если аргументов несколько, то нужно обернуть их в скобки, вот так:
Если нужно задать функцию без аргументов, то также используются скобки, в этом случае – пустые:
Когда тело функции достаточно большое, то можно его обернуть в фигурные скобки :
Заметим, что как только тело функции оборачивается в , то её результат уже не возвращается автоматически. Такая функция должна делать явный , как в примере выше, если конечно хочет что-либо возвратить.
Функции-стрелки очень удобны в качестве коллбеков, например:
Такая запись – коротка и понятна. Далее мы познакомимся с дополнительными преимуществами использования функций-стрелок для этой цели.
Чего НЕ может JavaScript в браузере?
Возможности JavaScript в браузере ограничены ради безопасности пользователя. Цель заключается в предотвращении доступа недобросовестной веб-страницы к личной информации или нанесения ущерба данным пользователя.
Примеры таких ограничений включают в себя:
-
JavaScript на веб-странице не может читать/записывать произвольные файлы на жёстком диске, копировать их или запускать программы. Он не имеет прямого доступа к системным функциям ОС.
Современные браузеры позволяют ему работать с файлами, но с ограниченным доступом, и предоставляют его, только если пользователь выполняет определённые действия, такие как «перетаскивание» файла в окно браузера или его выбор с помощью тега .
Существуют способы взаимодействия с камерой/микрофоном и другими устройствами, но они требуют явного разрешения пользователя. Таким образом, страница с поддержкой JavaScript не может незаметно включить веб-камеру, наблюдать за происходящим и отправлять информацию в ФСБ.
-
Различные окна/вкладки не знают друг о друге. Иногда одно окно, используя JavaScript, открывает другое окно. Но даже в этом случае JavaScript с одной страницы не имеет доступа к другой, если они пришли с разных сайтов (с другого домена, протокола или порта).
Это называется «Политика одинакового источника» (Same Origin Policy). Чтобы обойти это ограничение, обе страницы должны согласиться с этим и содержать JavaScript-код, который специальным образом обменивается данными.
Это ограничение необходимо, опять же, для безопасности пользователя. Страница , которую открыл пользователь, не должна иметь доступ к другой вкладке браузера с URL и воровать информацию оттуда.
-
JavaScript может легко взаимодействовать с сервером, с которого пришла текущая страница. Но его способность получать данные с других сайтов/доменов ограничена. Хотя это возможно в принципе, для чего требуется явное согласие (выраженное в заголовках HTTP) с удалённой стороной. Опять же, это ограничение безопасности.
Подобные ограничения не действуют, если JavaScript используется вне браузера, например — на сервере. Современные браузеры предоставляют плагины/расширения, с помощью которых можно запрашивать дополнительные разрешения.
«Поднятие» переменных и функций
Интерпретатор JavaScript всегда перемещает («поднимает») объявления функций и переменных в начало области видимости. Если переменная определена внутри функции, она поднимается к верхней части функции, а если переменная определена глобально — к верхней части глобального контекста. Рассмотрим, что это значит на примере:
Объявление переменной поднимается вверх без присваивания, перекрывая одноименную глобальную переменную, поэтому в первом случае значение переменной – .
В случае с функциями, поднимается вся функция целиком, если речь идет о функциях-объявлениях (declaration); функция-выражение (expression) не поднимается:
В этом этом примере поднимается только функция . Идентификатор «anomFunc» (переменная, которой присвоена функция) также поднимается, но сама анонимная функция при этом остаётся на месте (как и в случае с пременной).
Свойство «length»
Ещё одно встроенное свойство «length» содержит количество параметров функции в её объявлении. Например:
Как мы видим, троеточие, обозначающее «остаточные параметры», здесь как бы «не считается»
Свойство иногда используется для интроспекций в функциях, которые работают с другими функциями.
Например, в коде ниже функция принимает в качестве параметров вопрос и произвольное количество функций-обработчиков ответа .
Когда пользователь отвечает на вопрос, функция вызывает обработчики. Мы можем передать два типа обработчиков:
- Функцию без аргументов, которая будет вызываться только в случае положительного ответа.
- Функцию с аргументами, которая будет вызываться в обоих случаях и возвращать ответ.
Чтобы вызвать обработчик правильно, будем проверять свойство .
Идея состоит в том, чтобы иметь простой синтаксис обработчика без аргументов для положительных ответов (наиболее распространённый случай), но также и возможность передавать универсальные обработчики:
Это частный случай так называемого – обработка аргументов в зависимости от их типа или, как в нашем случае – от значения . Эта идея имеет применение в библиотеках JavaScript.
Усиливаем пример с определением имени. Тесты
const myFunc = function() { }; // 1 const myFuncA = function myFuncA2() { }; // 2const myFuncB = () => { }; // 3const myFuncC = new Function(); // 4const property = Symbol('symbolProperty');const myObject = { methodA: function() { }, // 5 methodB: function MyMethodB() {}, // 6 methodC: () => { }, // 7 methodD(){ }, // 8 (){ } // 9};function myFuncD() { }; // 10(function() { })(); // 11(function myFuncF(){ })(); // 12
Теперь, используя описанные способы выше, возьмём у каждой функции свойство name, и начнем с функций, заданных как :
myFunc.name // "myFunc"myFuncA.name // "myFuncA"myFuncB.name // "myFuncB"myFuncC.name // "myFuncC"
Функции, объявленные как Object Method:
myObject.methodA.name // "methodA"myObject.methodB.name // "MyMethodB"myObject.methodC.name // "methodC"myObject.methodD.name // "methodD"myObject.name // ""
Функции, объявленные как IIFE (Immediately-invoked function expression):
(function myFuncD() { }).name // "myFuncD"(function() { }).name // ""(function myFuncF(){ }).name // "myFuncF"(new Function()).name // "anonymous"
Вопросов возникает ещё больше. А может функция в опросе из twitter все таки именованная? Может я ввел вас в заблуждение?
Объявление и вызов функции
Операции с функцией в JavaScript можно разделить на 2 шага:
- объявление (создание) функции;
- вызов (выполнение) этой функции.
1. Объявление функции. Написание функции посредством Function Declaration начинается с написания ключевого слова . После этого указывается имя функции, круглые скобки в которых при необходимости перечисляются через запятую параметры и код функции, заключённый в фигурные скобки.
function имя (параметры) { // код функции }
Например:
// объявление функции someName function someName() { alert('Вы вызвали функцию someName!'); } // function - ключевое слово, которое означает что мы хотим создать функцию // someName - имя функции // () - круглые скобки внутри которых при необходимости мы можем поместить параметры через запятую // { ... } - код или тело функции
При составлении имени функции необходимо руководствоваться теме же правилами, что и при создании имени переменной. Т.е. оно может содержать буквы, цифры (0-9), знаки «$» и «_». В качестве букв рекомендуется использовать только буквы английского алфавита (a-z, A-Z). Имя функции, также как и имя переменной не может начинаться с цифры.
Параметров у функции может быть сколько угодно много или не быть вообще. Круглые скобки в любом случае указываются. Если параметров несколько, то их между собой необходимо разделить посредством запятой. Они позволяют более удобно (по имени) получить переданные аргументы функции при её вызове.
Код функции — это набор инструкций, заключенный в фигурные скобки, которые необходимо выполнить при её вызове. Код функции называют ещё её телом.
2. Вызов функции. Объявленная функция сама по себе не выполняется. Для того чтобы функцию запустить, её необходимо вызвать. Вызов функции осуществляется посредством указания её имени и двух круглых скобок. Внутри скобок при необходимости ей можно передать аргументы (дополнительные данные) отделяя их друг от друга с помощью запятой.
// выполнение функции, приведённой в предыдущем примере (без передачи ей аргументов) someName();
Параметры по умолчанию
Если параметр не указан, то его значением становится .
Например, вышеупомянутая функция может быть вызвана с одним аргументом:
Это не приведёт к ошибке. Такой вызов выведет . В вызове не указан параметр , поэтому предполагается, что .
Если мы хотим задать параметру значение по умолчанию, мы должны указать его после :
Теперь, если параметр не указан, его значением будет
В данном случае это строка, но на её месте могло бы быть и более сложное выражение, которое бы вычислялось и присваивалось при отсутствии параметра. Например:
Вычисление параметров по умолчанию
В JavaScript параметры по умолчанию вычисляются каждый раз, когда функция вызывается без соответствующего параметра.
В примере выше будет вызываться каждый раз, когда вызывается без параметра .
Использование параметров по умолчанию в ранних версиях JavaScript
Ранние версии JavaScript не поддерживали параметры по умолчанию. Поэтому существуют альтернативные способы, которые могут встречаться в старых скриптах.
Например, явная проверка на :
…Или с помощью оператора :
Outer variables
A function can access an outer variable as well, for example:
The function has full access to the outer variable. It can modify it as well.
For instance:
The outer variable is only used if there’s no local one.
If a same-named variable is declared inside the function then it shadows the outer one. For instance, in the code below the function uses the local . The outer one is ignored:
Global variables
Variables declared outside of any function, such as the outer in the code above, are called global.
Global variables are visible from any function (unless shadowed by locals).
It’s a good practice to minimize the use of global variables. Modern code has few or no globals. Most variables reside in their functions. Sometimes though, they can be useful to store project-level data.
Проблема «несуществующего свойства»
Если вы только начали читать учебник и изучать JavaScript, то, возможно, эта проблема вам пока незнакома, но она достаточно распространена.
Например, рассмотрим объекты для пользователей . У большинства пользователей есть адрес с улицей , но некоторые адрес не указали.
В этом случае при попытке получить свойство будет ошибка:
Это нормальный результат, так работает JavaScript, но во многих реальных ситуациях удобнее было бы получать не ошибку, а просто («нет улицы»).
Или ещё пример. В веб-разработке нам бывает нужно получить данные об HTML-элементе, который иногда может отсутствовать на странице:
До появления в языке для решения подобных проблем использовался оператор .
Например:
Использование логического И со всей цепочкой свойств гарантирует, что все они существуют (а если нет – вычисление прекращается), но это довольно длинная и громоздкая конструкция.
Языки «над» JavaScript
Синтаксис JavaScript подходит не под все нужды. Разные люди хотят иметь разные возможности.
Это естественно, потому что проекты разные и требования к ним тоже разные.
Так, в последнее время появилось много новых языков, которые транспилируются (конвертируются) в JavaScript, прежде чем запустятся в браузере.
Современные инструменты делают транспиляцию очень быстрой и прозрачной, фактически позволяя разработчикам писать код на другом языке, автоматически преобразуя его в JavaScript «под капотом».
Примеры таких языков:
- CoffeeScript добавляет «синтаксический сахар» для JavaScript. Он вводит более короткий синтаксис, который позволяет писать чистый и лаконичный код. Обычно такое нравится Ruby-программистам.
- TypeScript концентрируется на добавлении «строгой типизации» для упрощения разработки и поддержки больших и сложных систем. Разработан Microsoft.
- Flow тоже добавляет типизацию, но иначе. Разработан Facebook.
- Dart стоит особняком, потому что имеет собственный движок, работающий вне браузера (например, в мобильных приложениях). Первоначально был предложен Google, как замена JavaScript, но на данный момент необходима его транспиляция для запуска так же, как для вышеперечисленных языков.
- Brython транспилирует Python в JavaScript, что позволяет писать приложения на чистом Python без JavaScript.
Есть и другие. Но даже если мы используем один из этих языков, мы должны знать JavaScript, чтобы действительно понимать, что мы делаем.
Переменные
Можно объявить при помощи:
- (константа, т.е. изменению не подлежит)
- (устаревший способ, подробности позже)
Имя переменной может включать:
- Буквы и цифры, однако цифра не может быть первым символом.
- Символы и используются наряду с буквами.
- Иероглифы и символы нелатинского алфавита также допустимы, но обычно не используются.
Переменные типизируются динамически. В них могут храниться любые значения:
Всего существует 8 типов данных:
- для целых и вещественных чисел,
- для работы с целыми числами произвольной длины,
- для строк,
- для логических значений истинности или ложности: ,
- – тип с единственным значением , т.е. «пустое значение» или «значение не существует»,
- – тип с единственным значением , т.е. «значение не задано»,
- и – сложные структуры данных и уникальные идентификаторы; их мы ещё не изучили.
Оператор возвращает тип значения переменной, с двумя исключениями:
Подробности: Переменные, Типы данных.
new Function
Существует ещё один способ создания функции, который используется очень редко, но упомянем и его для полноты картины.
Он позволяет создавать функцию полностью «на лету» из строки, вот так:
То есть, функция создаётся вызовом :
- Параметры функции через запятую в виде строки.
- Код функции в виде строки.
Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения.
Пример использования – динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами.
Function Expressions
A JavaScript function can also be defined using an expression.
A function expression can be stored in a variable:
Example
var x = function (a, b) {return a * b};
After a function expression has been stored in a variable, the variable can
be used as a function:
Example
var x = function (a, b) {return a * b};
var z = x(4, 3);
The function above is actually an anonymous function (a function without a
name).
Functions stored in variables do not need function names. They are always
invoked (called) using the variable name.
The function above ends with a semicolon because it is a part of an executable statement.
addEventListener
Фундаментальный недостаток описанных выше способов назначения обработчика –- невозможность повесить несколько обработчиков на одно событие.
Например, одна часть кода хочет при клике на кнопку делать её подсвеченной, а другая – выдавать сообщение.
Мы хотим назначить два обработчика для этого. Но новое DOM-свойство перезапишет предыдущее:
Разработчики стандартов достаточно давно это поняли и предложили альтернативный способ назначения обработчиков при помощи специальных методов и . Они свободны от указанного недостатка.
Синтаксис добавления обработчика:
- Имя события, например .
- Ссылка на функцию-обработчик.
- Дополнительный объект со свойствами:
- : если , тогда обработчик будет автоматически удалён после выполнения.
- : фаза, на которой должен сработать обработчик, подробнее об этом будет рассказано в главе Всплытие и погружение. Так исторически сложилось, что может быть , это то же самое, что .
- : если , то указывает, что обработчик никогда не вызовет , подробнее об этом будет рассказано в главе Действия браузера по умолчанию.
Для удаления обработчика следует использовать :
Удаление требует именно ту же функцию
Для удаления нужно передать именно ту функцию-обработчик которая была назначена.
Вот так не сработает:
Обработчик не будет удалён, т.к
в передана не та же функция, а другая, с одинаковым кодом, но это не важно
Вот так правильно:
Обратим внимание – если функцию обработчик не сохранить где-либо, мы не сможем её удалить. Нет метода, который позволяет получить из элемента обработчики событий, назначенные через
Метод позволяет добавлять несколько обработчиков на одно событие одного элемента, например:
Как видно из примера выше, можно одновременно назначать обработчики и через DOM-свойство и через . Однако, во избежание путаницы, рекомендуется выбрать один способ.
Обработчики некоторых событий можно назначать только через
Существуют события, которые нельзя назначить через DOM-свойство, но можно через .
Например, таково событие , которое срабатывает, когда завершена загрузка и построение DOM документа.
Так что более универсален. Хотя заметим, что таких событий меньшинство, это скорее исключение, чем правило.
Arrow Functions
Arrow functions allows a short syntax for writing function expressions.
You don’t need the keyword, the keyword, and the
curly brackets.
// ES5
var x = function(x, y) {
return x * y;
}
// ES6
const x = (x, y) => x * y;
Arrow functions do not have their own .
They are not well suited for defining object methods.
Arrow functions are not hoisted. They must be defined before they are used.
Using
is safer than using , because a function expression is
always constant value.
You can only omit the keyword and the curly brackets if the function is a single statement.
Because of this, it might be a good habit to always keep them:
В интернете кто-то не прав
Все началось с простого вопроса в канале Telegram для изучающих JavaScript, в котором, помимо всего прочего, появился вопрос касательно обработчиков событий в браузере. Вопрос был в том, как они «навешиваются» и «снимаются» с DOM-элемента. Среди ответов от вполне опытного разработчика был следующий:
Далее ответ был подкреплен блоком кода похожим на этот:
element.addEventListener('click', function() { /* .. */ })element.removeEventListener('click', function() { /* .. */ })
На что в качестве возражения с моей стороны был приведен следующий блок кода, показывающий, что анонимная функция может быть удалена как обработчик:
const handleClick = () => { /* */ }element.addEventListener('click', handleClick)element.removeEventListener('click', handleClick)
Потеря «this»
Мы уже видели примеры потери . Как только метод передаётся отдельно от объекта – теряется.
Вот как это может произойти в случае с :
При запуске этого кода мы видим, что вызов возвращает не «Вася», а !
Это произошло потому, что получил функцию отдельно от объекта (именно здесь функция и потеряла контекст). То есть последняя строка может быть переписана как:
Метод в браузере имеет особенность: он устанавливает для вызова функции (в Node.js становится объектом таймера, но здесь это не имеет значения). Таким образом, для он пытается получить , которого не существует. В других подобных случаях обычно просто становится .
Задача довольно типичная – мы хотим передать метод объекта куда-то ещё (в этом конкретном случае – в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте?
Вложенные функции
Функция называется «вложенной», когда она создаётся внутри другой функции.
Это очень легко сделать в JavaScript.
Мы можем использовать это для упорядочивания нашего кода, например, как здесь:
Здесь вложенная функция создана для удобства. Она может получить доступ к внешним переменным и, значит, вывести полное имя. В JavaScript вложенные функции используются очень часто.
Что ещё интереснее, вложенная функция может быть возвращена: либо в качестве свойства нового объекта (если внешняя функция создаёт объект с методами), либо сама по себе. И затем может быть использована в любом месте
Не важно где, она всё так же будет иметь доступ к тем же внешним переменным
Например, здесь, вложенная функция присваивается новому объекту в конструкторе:
А здесь мы просто создаём и возвращаем функцию «счётчик»:
Давайте продолжим с примером . Он создаёт функцию «counter», которая возвращает следующее число при каждом вызове. Несмотря на простоту, немного модифицированные варианты этого кода применяются на практике, например, в генераторе псевдослучайных чисел и во многих других случаях.
Как же это работает изнутри?
Когда внутренняя функция начинает выполняться, начинается поиск переменной изнутри-наружу. Для примера выше порядок будет такой:
- Локальные переменные вложенной функции…
- Переменные внешней функции…
- И так далее, пока не будут достигнуты глобальные переменные.
В этом примере будет найден на шаге . Когда внешняя переменная модифицируется, она изменится там, где была найдена. Значит, найдёт внешнюю переменную и увеличит её значение в лексическом окружении, которому она принадлежит. Как если бы у нас было .
Теперь рассмотрим два вопроса:
- Можем ли мы каким-нибудь образом сбросить счётчик из кода, который не принадлежит ? Например, после вызова в коде выше.
- Если мы вызываем несколько раз – нам возвращается много функций . Они независимы или разделяют одну и ту же переменную ?
Попробуйте ответить на эти вопросы перед тем, как продолжить чтение.
…
Готовы?
Хорошо, давайте ответим на вопросы.
- Такой возможности нет: – локальная переменная функции, мы не можем получить к ней доступ извне.
- Для каждого вызова создаётся новое лексическое окружение функции, со своим собственным . Так что, получившиеся функции – независимы.
Вот демо:
Надеюсь, ситуация с внешними переменными теперь ясна. Для большинства ситуаций такого понимания вполне достаточно, но в спецификации есть ряд деталей, которые мы, для простоты, опустили. Далее мы разберём происходящее ещё более подробно.
Вызов функций
Программный код, образующий тело функции, выполняется не в момент определения функции, а в момент ее вызова. Вызов функций выполняется с помощью выражения вызова. Выражение вызова состоит из выражения обращения к функции, которое возвращает объект функции, и следующими за ним круглыми скобками со списком из нуля или более выражений-аргументов, разделенных запятыми, внутри.
Если выражение обращения к функции является выражением обращения к свойству — если функция является свойством объекта или элементом массива (т.е. методом) — тогда выражение вызова является выражением вызова метода. В следующем фрагменте демонстрируется несколько примеров выражений вызова обычных функций:
При вызове функции вычисляются все выражения-аргументы (указанные между скобками), и полученные значения используются в качестве аргументов функции. Эти значения присваиваются параметрам, имена которых перечислены в определении функции. В теле функции выражения обращений к параметрам возвращают значения соответствующих аргументов.
При вызове обычной функции возвращаемое функцией значение становится значением выражения вызова. Если возврат из функции происходит по достижении ее конца интерпретатором, возвращается значение undefined. Если возврат из функции происходит в результате выполнения инструкции return, возвращается значение выражения, следующего за инструкцией return, или undefined, если инструкция return не имеет выражения.
Метод — это не что иное, как функция, которая хранится в виде свойства объекта. Если имеется функция func и объект obj, то можно определить метод объекта obj с именем method, как показано ниже:
Чаще всего при вызове методов используется форма обращения к свойствам с помощью оператора точки, однако точно так же можно использовать форму обращения к свойствам с помощью квадратных скобок. Например, оба следующих выражения являются выражениями вызова методов:
Аргументы и возвращаемое значение при вызове метода обрабатываются точно так же, как при вызове обычной функции
Однако вызов метода имеет одно важное отличие: контекст вызова. Выражение обращения к свойству состоит из двух частей: объекта (в данном случае obj) и имени свойства (method)
В подобных выражениях вызова методов объект obj становится контекстом вызова, и тело функции получает возможность ссылаться на этот объект с помощью ключевого слова this. Например:
Методы и ключевое слово this занимают центральное место в парадигме объектно-ориентированного программирования. Любая функция, используемая как метод, фактически получает неявный аргумент — объект, относительно которого она была вызвана. Как правило, методы выполняют некоторые действия с объектом, и синтаксис вызова метода наглядно отражает тот факт, что функция оперирует объектом.
Обратите внимание: this — это именно ключевое слово, а не имя переменной или свойства. Синтаксис JavaScript не допускает возможность присваивания значений элементу this
Итого
Синтаксис опциональной цепочки имеет три формы:
- – возвращает , если существует , и в противном случае.
- – возвращает , если существует , и в противном случае.
- – вызывает , если существует , в противном случае возвращает .
Как мы видим, все они просты и понятны в использовании. проверяет левую часть выражения на равенство , и продолжает дальнейшее вычисление, только если это не так.
Цепочка позволяет без возникновения ошибок обратиться к вложенным свойствам.
Тем не менее, нужно разумно использовать — только там, где это уместно, если допустимо, что левая часть не существует. Чтобы таким образом не скрывать возможные ошибки программирования.