diff --git a/docs/javascript/01-base/02-variables.md b/docs/javascript/01-base/02-variables.md index 20a5d5e..ad9ec2b 100644 --- a/docs/javascript/01-base/02-variables.md +++ b/docs/javascript/01-base/02-variables.md @@ -46,4 +46,50 @@ message = 'Hello'; ```js typeof null == "object" // ошибка в языке typeof function(){} == "function" // именно для функций +``` + +## Устаревшее ключевое слово "var" +Обычно var не используется в современных скриптах, но всё ещё может скрываться в старых.\ + +Существует 2 основных отличия var от let/const: +- Переменные var не имеют блочной области видимости, они ограничены, как минимум, телом функции. +- Объявления (инициализация) переменных var производится в начале исполнения функции (или скрипта для глобальных переменных). + +### Для «var» не существует блочной области видимости +Область видимости переменных `var` ограничивается либо функцией, либо, если переменная глобальная, то скриптом. Такие переменные доступны за пределами блока. + +`var` выходит за пределы блоков `if`, `for` и подобных. Это происходит потому, что на заре развития JavaScript блоки кода не имели лексического окружения. + +### «var» допускает повторное объявление +Используя var, можно переобъявлять переменную сколько угодно раз. Повторные var игнорируются. + +### «var» обрабатываются в начале запуска функции +Это поведение называется «hoisting» (всплытие, поднятие), потому что все объявления переменных var «всплывают» в самый верх функции. +Объявления переменных var обрабатываются в начале выполнения функции (или запуска скрипта, если переменная является глобальной). + +Другими словами, переменные var считаются объявленными с самого начала исполнения функции вне зависимости от того, в каком месте функции реально находятся их объявления (при условии, что они не находятся во вложенной функции). + +***Объявления переменных «всплывают», но присваивания значений – нет.*** + +### IIFE +В прошлом, поскольку существовал только `var`, а он не имел блочной области видимости, программисты придумали способ её эмулировать. Этот способ получил название «Immediately-invoked function expressions» (сокращенно IIFE). +Здесь создаётся и немедленно вызывается Function Expression. Так что код выполняется сразу же и у него есть свои локальные переменные. +```js +// Способы создания IIFE +(function() { + var message = "Привет"; + alert(message); // Привет +})(); // Круглые скобки вокруг функции + +(function() { + alert("Круглые скобки вокруг всего выражения"); +}()); + +!function() { + alert("Выражение начинается с логического оператора НЕ"); +}(); + ++function() { + alert("Выражение начинается с унарного плюса"); +}(); ``` \ No newline at end of file diff --git a/docs/javascript/02-objects/08-getter-setter.md b/docs/javascript/02-objects/08-getter-setter.md new file mode 100644 index 0000000..45ec397 --- /dev/null +++ b/docs/javascript/02-objects/08-getter-setter.md @@ -0,0 +1,77 @@ +--- +sidebar_position: 8 +--- + +# Свойства - геттеры и сеттеры + +## Геттеры и сеттеры +Свойства-аксессоры представлены методами: «геттер» – для чтения и «сеттер» – для записи. При литеральном объявлении объекта они обозначаются get и set +```js +let obj = { + get propName() { + // геттер, срабатывает при чтении obj.propName + }, + + set propName(value) { + // сеттер, срабатывает при записи obj.propName = value + } +}; +``` + +## Дескрипторы свойств доступа +Дескрипторы свойств-аксессоров отличаются от «обычных» свойств-данных. + +Свойства-аксессоры не имеют `value` и `writable`, но взамен предлагают функции `get` и `set`. + +То есть, дескриптор аксессора может иметь: +- **get** – функция без аргументов, которая сработает при чтении свойства, +- **set** – функция, принимающая один аргумент, вызываемая при присвоении свойства, +- **enumerable** – то же самое, что и для свойств-данных, +- **configurable** – то же самое, что и для свойств-данных. + +Например, для создания аксессора `fullName` при помощи `defineProperty` мы можем передать дескриптор с использованием `get` и `set` +```js +let user = { + name: "John", + surname: "Smith" +}; + +Object.defineProperty(user, 'fullName', { + get() { + return `${this.name} ${this.surname}`; + }, + set(value) { + [this.name, this.surname] = value.split(" "); + } +}); +alert(user.fullName); // John Smith +for(let key in user) alert(key); // name, surname +``` + +## Умные геттеры/сеттеры +Геттеры/сеттеры можно использовать как обёртки над «реальными» значениями свойств, чтобы получить больше контроля над операциями с ними. + +Например, если мы хотим запретить устанавливать короткое имя для `user`, мы можем использовать сеттер `name` для проверки, а само значение хранить в отдельном свойстве `_name`: +```js +let user = { + get name() { + return this._name; + }, + + set name(value) { + if (value.length < 4) { + alert("Имя слишком короткое, должно быть более 4 символов"); + return; + } + this._name = value; + } +}; + +user.name = "Pete"; +alert(user.name); // Pete + +user.name = ""; // Имя слишком короткое... +``` + +## Использование для совместимости +У аксессоров есть интересная область применения – они позволяют в любой момент взять «обычное» свойство и изменить его поведение, поменяв на геттер и сеттер. diff --git a/docs/javascript/03-data-types/11-json.md b/docs/javascript/03-data-types/11-json.md new file mode 100644 index 0000000..1ebb3b6 --- /dev/null +++ b/docs/javascript/03-data-types/11-json.md @@ -0,0 +1,62 @@ +--- +sidebar_position: 11 +--- + +# Формат JSON + +- JSON – это формат данных, который имеет собственный независимый стандарт и библиотеки для большинства языков программирования. +- JSON поддерживает простые объекты, массивы, строки, числа, логические значения и null. +- JavaScript предоставляет методы JSON.stringify для сериализации в JSON и JSON.parse для чтения из JSON. +- Оба метода поддерживают функции преобразования для интеллектуального чтения/записи. +- Если объект имеет метод toJSON, то он вызывается через JSON.stringify. + +JavaScript предоставляет методы: + +- JSON.stringify для преобразования объектов в JSON. +- JSON.parse для преобразования JSON обратно в объект. + +JSON поддерживает следующие типы данных: + +- Объекты \{ ... } +- Массивы [ ... ] +- Примитивы: + - строки, + - числа, + - логические значения true/false, + - null. + +## JSON.stringify +JSON.stringify пропускает некоторые специфические свойства объектов JavaScript +- Свойства-функции (методы). +- Символьные ключи и значения. +- Свойства, содержащие undefined. + +💥 ***Важное ограничение: не должно быть циклических ссылок.*** + +## Исключаем и преобразуем: replacer +- **value** Значение для кодирования. +- **replacer** Массив свойств для кодирования или функция соответствия function(key, value). +- **space** Дополнительное пространство (отступы), используемое для форматирования. + +В большинстве случаев JSON.stringify используется только с первым аргументом. Но если нам нужно настроить процесс замены, например, отфильтровать циклические ссылки, то можно использовать второй аргумент JSON.stringify. + +```js +let json = JSON.stringify(value[, replacer, space]) +``` + +## Форматирование: space +Третий аргумент в JSON.stringify(value, replacer, space) – это количество пробелов, используемых для удобного форматирования. + +## Пользовательский «toJSON» +Как и toString для преобразования строк, объект может предоставлять метод toJSON для преобразования в JSON. JSON.stringify автоматически вызывает его, если он есть. + +## JSON.parse +Чтобы декодировать JSON-строку, нам нужен другой метод с именем JSON.parse. +```js +let value = JSON.parse(str[, reviver]); +``` +- **str** JSON для преобразования в объект. +- **reviver** Необязательная функция, которая будет вызываться для каждой пары (ключ, значение) и может преобразовывать значение. + +Кроме того, JSON не поддерживает комментарии. + diff --git a/docs/javascript/04-functions/04-arrow-func.md b/docs/javascript/04-functions/04-arrow-func.md index cd1280a..b7a89e7 100644 --- a/docs/javascript/04-functions/04-arrow-func.md +++ b/docs/javascript/04-functions/04-arrow-func.md @@ -53,7 +53,7 @@ function defer(f, ms) { } ``` -#### Стрелочные функции: +## Особенности стрелочных функций: - Не имеют this. - Не имеют arguments. - Не могут быть вызваны с new. diff --git a/docs/javascript/04-functions/05-args.md b/docs/javascript/04-functions/05-args.md new file mode 100644 index 0000000..6c67b8b --- /dev/null +++ b/docs/javascript/04-functions/05-args.md @@ -0,0 +1,62 @@ +--- +sidebar_position: 5 +--- + +# Остаточные параметры и оператор расширения + +Когда мы видим "..." в коде, это могут быть как остаточные параметры, так и оператор расширения. + +Как отличить их друг от друга: + +- Если ... располагается в конце списка параметров функции, то это «остаточные параметры». Он собирает остальные неуказанные аргументы и делает из них массив. +- Если ... встретился в вызове функции или где-либо ещё, то это «оператор расширения». Он извлекает элементы из массива. + +Полезно запомнить: +- Остаточные параметры используются, чтобы создавать новые функции с неопределённым числом аргументов. +- С помощью оператора расширения можно вставить массив в функцию, которая по умолчанию работает с обычным списком аргументов. +- +Вместе эти конструкции помогают легко преобразовывать наборы значений в массивы и обратно. + +К аргументам функции можно обращаться и по-старому — через псевдомассив arguments. + +## Остаточные параметры (...) +Остаточные параметры могут быть обозначены через три точки `....` +```js +function sumAll(...args) { // args — имя массива + let sum = 0; + for (let arg of args) sum += arg; + return sum; +} +alert( sumAll(1) ); // 1 +alert( sumAll(1, 2) ); // 3 +alert( sumAll(1, 2, 3) ); // 6 +``` +💥 ***Остаточные параметры должны располагаться в конце*** +```js +function f(arg1, ...rest, arg2) { // arg2 после ...rest ?! + // Ошибка +} +``` +## Переменная "arguments" +Все аргументы функции находятся в псевдомассиве `arguments` под своими порядковыми номерами. +Хотя arguments похож на массив, и его тоже можно перебирать, это всё же не массив. Методы массивов не поддерживаются. +```js +function showName() { + alert( arguments.length ); + alert( arguments[0] ); + alert( arguments[1] ); + // for (let arg of arguments) alert(arg); // Объект arguments можно перебирать +} +showName("Юлий", "Цезарь"); // Вывод: 2, Юлий, Цезарь +showName("Илья");// Вывод: 1, Илья, undefined (второго аргумента нет) +``` +💥 ***Стрелочные функции не имеют "arguments"*** +Если мы обратимся к arguments из стрелочной функции, то получим аргументы внешней «нормальной» функции. + +## Оператор расширения +Когда `...arr` используется при вызове функции, он «расширяет» перебираемый объект `arr` в список аргументов. +```js +let arr = [3, 5, 1]; +alert( Math.max(...arr) ); // 5 (оператор "раскрывает" массив в список аргументов) +``` + diff --git a/docs/javascript/04-functions/06-closure.md b/docs/javascript/04-functions/06-closure.md new file mode 100644 index 0000000..6fef236 --- /dev/null +++ b/docs/javascript/04-functions/06-closure.md @@ -0,0 +1,113 @@ +--- +sidebar_position: 6 +--- + +# Область видимости переменных, замыкание +Источник: [https://learn.javascript.ru/closure](https://learn.javascript.ru/closure) + +## Блоки кода +Если переменная объявлена внутри блока кода `{...}`, то она видна только внутри этого блока. +С помощью блоков `{...}` мы можем изолировать часть кода, выполняющую свою собственную задачу, с переменными, принадлежащими только ей +Для `if`, `for`, `while` и т.д. переменные, объявленные в блоке кода `{...}`, также видны только внутри + +## Вложенные функции +Функция называется «вложенной», когда она создаётся внутри другой функции. +Она может получить доступ к внешним переменным. +Вложенная функция может быть возвращена: либо в качестве свойства нового объекта (если внешняя функция создаёт объект с методами), либо сама по себе. И затем может быть использована в любом месте. Не важно где, она всё так же будет иметь доступ к тем же внешним переменным. + +## Лексическое окружение + +#### Переменные +В JavaScript у каждой выполняемой функции, блока кода `{...}` и скрипта есть связанный с ними внутренний (скрытый) объект, называемый ***лексическим окружением** `LexicalEnvironment`. + +Объект лексического окружения состоит из двух частей: +1. **Environment Record** – объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this). +2. Ссылка на **внешнее лексическое окружение** – то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок). + +**«Переменная» – это просто свойство специального внутреннего объекта: `Environment Record`.** \ +**«Получить или изменить переменную», означает, «получить или изменить свойство этого объекта».** + +- Переменная – это свойство специального внутреннего объекта, связанного с текущим выполняющимся блоком/функцией/скриптом. +- Работа с переменными – это на самом деле работа со свойствами этого объекта. + +#### Function Declaration +**Разница заключается в том, что Function Declaration мгновенно инициализируется полностью.** + +Когда создается лексическое окружение, Function Declaration сразу же становится функцией, готовой к использованию (в отличие от `let`, который до момента объявления не может быть использован). + +Поэтому мы можем вызвать функцию, объявленную как Function Declaration, до самого её объявления. + +Такое поведение касается только Function Declaration, а не Function Expression, в которых мы присваиваем функцию переменной. + +#### Внутреннее и внешнее лексическое окружение +Когда запускается функция, в начале ее вызова автоматически создается новое лексическое окружение для хранения локальных переменных и параметров вызова. + +![](images/06-closure-1.png) + +В процессе вызова функции у нас есть два лексических окружения: внутреннее (для вызываемой функции) и внешнее (глобальное): + +- Внутреннее лексическое окружение соответствует текущему выполнению say.\ + В нём находится одна переменная name, параметр функции. Мы вызываем say("John"), так что значение переменной name равно "John". +- Внешнее лексическое окружение – это глобальное лексическое окружение.\ + В нём находятся переменная phrase и сама функция. + +У внутреннего лексического окружения есть ссылка на внешнее `outer`. + +**Когда код хочет получить доступ к переменной – сначала происходит поиск во внутреннем лексическом окружении, затем во внешнем, затем в следующем и так далее, до глобального.** + +Если переменная не была найдена, это будет ошибкой в строгом режиме (`use strict`). Без строгого режима, для обратной совместимости, присваивание несуществующей переменной создаёт новую глобальную переменную с таким же именем. + +#### Возврат функции +```js +function makeCounter() { + let count = 0; + + return function() { + return count++; + }; +} + +let counter = makeCounter(); +``` +В начале каждого вызова makeCounter() создается новый объект лексического окружения, в котором хранятся переменные для конкретного запуска makeCounter. + +Таким образом, мы имеем два вложенных лексических окружения, как в примере выше: +![](images/06-closure-2.png) +Отличие заключается в том, что во время выполнения makeCounter() создается крошечная вложенная функция, состоящая всего из одной строки: return count++. Мы ее еще не запускаем, а только создаем. + +Все функции помнят лексическое окружение, в котором они были созданы. Технически здесь нет никакой магии: все функции имеют скрытое свойство [[Environment]], которое хранит ссылку на лексическое окружение, в котором была создана функция: +![](images/06-closure-3.png) + +Таким образом, counter.[[Environment]] имеет ссылку на \{count: 0} лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на [[Environment]] устанавливается один раз и навсегда при создании функции. + +Впоследствии, при вызове counter(), для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из counter.[[Environment]]: +![](images/06-closure-4.png) +Теперь, когда код внутри counter() ищет переменную count, он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова makeCounter(), где находит count и изменяет ее. + +***Переменная обновляется в том лексическом окружении, в котором она существует.*** +![](images/06-closure-5.png) +Если мы вызовем counter() несколько раз, то в одном и том же месте переменная count будет увеличена до 2, 3 и т.д. + +💥 **Замыкания** + +**Замыкание** – это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями. + +То есть они автоматически запоминают, где были созданы, с помощью скрытого свойства `[[Environment]]`, и все они могут получить доступ к внешним переменным. + +Когда на собеседовании фронтенд-разработчику задают вопрос: «что такое замыкание?», – правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве `[[Environment]]` и о том, как работает лексическое окружение. + +Есть только одно исключение, когда функция создаётся с использованием `new Function`, в её `[[Environment]]` записывается ссылка не на внешнее лексическое окружение, в котором она была создана, а на глобальное. Поэтому такая функция имеет доступ только к глобальным переменным. + +## Сборка мусора +Обычно лексическое окружение удаляется из памяти вместе со всеми переменными после завершения вызова функции. Это связано с тем, что на него нет ссылок. Как и любой объект JavaScript, оно хранится в памяти только до тех пор, пока к нему можно обратиться. + +Однако если существует вложенная функция, которая все еще доступна после завершения функции, то она имеет свойство `[[Environment]]`, ссылающееся на лексическое окружение. + +В этом случае лексическое окружение остается доступным даже после завершения работы функции. + +Объект лексического окружения исчезает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до тех пор, пока на него ссылается хотя бы одна вложенная функция. + +#### Оптимизация на практике +Но на практике движки JavaScript пытаются это оптимизировать. Они анализируют использование переменных и, если легко по коду понять, что внешняя переменная не используется – она удаляется. + +***Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.*** \ No newline at end of file diff --git a/docs/javascript/04-functions/07-global-object.md b/docs/javascript/04-functions/07-global-object.md new file mode 100644 index 0000000..c0597dc --- /dev/null +++ b/docs/javascript/04-functions/07-global-object.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 7 +--- + +# Глобальный объект +Глобальный объект предоставляет переменные и функции, доступные в любом месте программы. По умолчанию это те, что встроены в язык или среду исполнения. + +В браузере он называется `window`, в Node.js — `global`, в другой среде исполнения может называться иначе. + +Недавно `globalThis` был добавлен в язык как стандартизированное имя для глобального объекта, которое должно поддерживаться в любом окружении. + +В браузере глобальные функции и переменные, объявленные с помощью `var` (не `let/const`!), становятся свойствами глобального объекта: + +- Глобальный объект хранит переменные, которые должны быть доступны в любом месте программы. +- Это включает в себя как встроенные объекты, например, Array, так и характерные для окружения свойства, например, window.innerHeight – высота окна браузера. +- Глобальный объект имеет универсальное имя – globalThis. +- …Но чаще на него ссылаются по-старому, используя имя, характерное для данного окружения, такое как window (браузер) и global (Node.js). +- Следует хранить значения в глобальном объекте, только если они действительно глобальны для нашего проекта. И стараться свести их количество к минимуму. +- В браузерах, если только мы не используем модули, глобальные функции и переменные, объявленные с помощью var, становятся свойствами глобального объекта. +- Для того, чтобы код был проще и в будущем его легче было поддерживать, следует обращаться к свойствам глобального объекта напрямую, как window.x. \ No newline at end of file diff --git a/docs/javascript/04-functions/08-setTimeout-setInterval.md b/docs/javascript/04-functions/08-setTimeout-setInterval.md new file mode 100644 index 0000000..d113c56 --- /dev/null +++ b/docs/javascript/04-functions/08-setTimeout-setInterval.md @@ -0,0 +1,69 @@ +--- +sidebar_position: 8 +--- + +# Планирование: setTimeout и setInterval +- Методы `setInterval(func, delay, ...args)` и `setTimeout(func, delay, ...args)` позволяют выполнять func регулярно или только один раз после задержки `delay`, заданной в мс. +- Для отмены выполнения необходимо вызвать `clearInterval/clearTimeout` со значением, которое возвращают методы `setInterval/setTimeout`. +- Вложенный вызов `setTimeout` является более гибкой альтернативой `setInterval`. Также он позволяет более точно задать интервал между выполнениями. +- Планирование с нулевой задержкой `setTimeout(func,0)` или, что то же самое, `setTimeout(func)` используется для вызовов, которые должны быть исполнены как можно скорее, после завершения исполнения текущего кода. +- Браузер ограничивает 4-мя мс минимальную задержку между пятью и более вложенными вызовами `setTimeout`, а также для `setInterval`, начиная с 5-го вызова. + +Методы планирования не гарантируют точную задержку. Например, таймер в браузере может замедляться по многим причинам: +- Перегружен процессор. +- Вкладка браузера в фоновом режиме. +- Работа ноутбука от аккумулятора. + +Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС. + +## setTimeout +setTimeout позволяет вызвать функцию один раз через определённый интервал времени. + +```js +let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...); +``` +💥 Передавайте функцию, но не запускайте её +Разработчики иногда ошибаются, добавляя скобки () после функции. Это не работает, потому что `setTimeout` ожидает ссылку на функцию. + +Вызов `setTimeout` возвращает «идентификатор таймера» `timerId`, который можно использовать для отмены дальнейшего выполнения. +```js +let timerId = setTimeout(...); +clearTimeout(timerId); +``` +## setInterval +`setInterval` позволяет вызывать функцию регулярно, повторяя вызов через определённый интервал времени. +Метод `setInterval` имеет такой же синтаксис как `setTimeout` + +```js +let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...); +``` +Чтобы остановить дальнейшее выполнение функции, необходимо вызвать clearInterval(timerId). + +## Вложенный setTimeout +Есть два способа запускать что-то регулярно. Один из них `setInterval`. Другим является вложенный `setTimeout`. + +Вложенный `setTimeout` – более гибкий метод, чем `setInterval`. С его помощью последующий вызов может быть задан по-разному в зависимости от результатов предыдущего. + +Например, необходимо написать сервис, который отправляет запрос для получения данных на сервер каждые 5 секунд, но если сервер перегружен, то необходимо увеличить интервал запросов до 10, 20, 40 секунд… Вот псевдокод: +```js +let delay = 5000; + +let timerId = setTimeout(function request() { + ...отправить запрос... + + if (ошибка запроса из-за перегрузки сервера) { + // увеличить интервал для следующего запроса + delay *= 2; + } + + timerId = setTimeout(request, delay); + +}, delay); +``` +***Вложенный `setTimeout` позволяет задать задержку между выполнениями более точно, чем `setInterval`.*** + +## setTimeout с нулевой задержкой +Особый вариант использования: `setTimeout(func, 0)` или просто `setTimeout(func)`. +Это планирует вызов `func` настолько быстро, насколько это возможно. Но планировщик будет вызывать функцию только после завершения выполнения текущего кода. + +Так вызов функции будет запланирован сразу после выполнения текущего кода. \ No newline at end of file diff --git a/docs/javascript/04-functions/09-decorators-call-apply.md b/docs/javascript/04-functions/09-decorators-call-apply.md new file mode 100644 index 0000000..0ea37fa --- /dev/null +++ b/docs/javascript/04-functions/09-decorators-call-apply.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 9 +--- + +# Декораторы и переадресация вызова, call/apply +Источник [https://learn.javascript.ru/call-apply-decorators](https://learn.javascript.ru/call-apply-decorators) + +Декоратор – это обёртка вокруг функции, которая изменяет поведение последней. Основная работа по-прежнему выполняется функцией. + +Обычно безопасно заменить функцию или метод декорированным, за исключением одной мелочи. Если исходная функция предоставляет свойства, такие как `func.calledCount` или типа того, то декорированная функция их не предоставит. Потому что это обёртка. Так что нужно быть осторожным в их использовании. Некоторые декораторы предоставляют свои собственные свойства. + +Декораторы можно рассматривать как «дополнительные возможности» или «аспекты», которые можно добавить в функцию. Мы можем добавить один или несколько декораторов. И всё это без изменения кода оригинальной функции! + +Для реализации `cachingDecorator` мы используем методы: +- `func.call(context, arg1, arg2…)` – вызывает `func` с данным контекстом и аргументами. +- `func.apply(context, args)` – вызывает `func`, передавая `context` как `this` и псевдомассив `args` как список аргументов. + +В основном переадресация вызова выполняется с помощью apply: +```js +let wrapper = function(original, arguments) { + return original.apply(this, arguments); +}; +``` +Весьма распространено заимствовать методы массива и применять их к arguments. В качестве альтернативы можно использовать объект с остаточными параметрами ...args, который является реальным массивом. + diff --git a/docs/javascript/04-functions/10-apply-context.md b/docs/javascript/04-functions/10-apply-context.md new file mode 100644 index 0000000..f26238f --- /dev/null +++ b/docs/javascript/04-functions/10-apply-context.md @@ -0,0 +1,64 @@ +--- +sidebar_position: 10 +--- + +# Привязка контекста к функции +Источник: [https://learn.javascript.ru/bind](https://learn.javascript.ru/bind) + +При передаче методов объекта в качестве колбэков, например для setTimeout, возникает известная проблема – потеря this. + +Метод `bind` возвращает «привязанный вариант» функции `func`, фиксируя контекст `this` и первые аргументы `arg1`, `arg2…`, если они заданы. + +Обычно `bind` применяется для фиксации `this` в методе объекта, чтобы передать его в качестве колбэка. Например, для `setTimeout`. + +Когда мы привязываем аргументы, такая функция называется «частично применённой» или «частичной». + +Частичное применение удобно, когда мы не хотим повторять один и тот же аргумент много раз. Например, если у нас есть функция send(from, to) и from всё время будет одинаков для нашей задачи, то мы можем создать частично применённую функцию и дальше работать с ней. + +## Потеря «this» +Мы уже видели примеры потери `this`. Как только метод передаётся отдельно от объекта – `this` теряется. + +```js +let user = { + firstName: "Вася", + sayHi() { + alert(`Привет, ${this.firstName}!`); + } +}; + +setTimeout(user.sayHi, 1000); // Привет, undefined! +``` +Это произошло потому, что setTimeout получил функцию sayHi отдельно от объекта user (именно здесь функция и потеряла контекст). + +> Метод setTimeout в браузере имеет особенность: он устанавливает this=window для вызова функции (в Node.js this становится объектом таймера, но здесь это не имеет значения). Таким образом, для this.firstName он пытается получить window.firstName, которого не существует. В других подобных случаях this обычно просто становится undefined. + +## Решение 1: сделать функцию-обёртку +Самый простой вариант решения – это обернуть вызов в анонимную функцию, создав замыкание: +```js +let user = { + firstName: "Вася", + sayHi() { + alert(`Привет, ${this.firstName}!`); + } +}; + +setTimeout(function() { + user.sayHi(); // Привет, Вася! +}, 1000); +``` + +## Решение 2: привязать контекст с помощью bind +Результатом вызова `func.bind(context)` является особый «экзотический объект» (термин взят из спецификации), который вызывается как функция и прозрачно передаёт вызов в `func`, при этом устанавливая `this=context`. + +Другими словами, вызов boundFunc подобен вызову `func` с фиксированным `this`. +```js +let user = { + firstName: "Вася", + sayHi() { + alert(`Привет, ${this.firstName}!`); + } +}; +let sayHi = user.sayHi.bind(user); // (*) +sayHi(); // Привет, Вася! +setTimeout(sayHi, 1000); // Привет, Вася! +``` diff --git a/docs/javascript/04-functions/images/06-closure-1.png b/docs/javascript/04-functions/images/06-closure-1.png new file mode 100644 index 0000000..8193d78 Binary files /dev/null and b/docs/javascript/04-functions/images/06-closure-1.png differ diff --git a/docs/javascript/04-functions/images/06-closure-2.png b/docs/javascript/04-functions/images/06-closure-2.png new file mode 100644 index 0000000..085b599 Binary files /dev/null and b/docs/javascript/04-functions/images/06-closure-2.png differ diff --git a/docs/javascript/04-functions/images/06-closure-3.png b/docs/javascript/04-functions/images/06-closure-3.png new file mode 100644 index 0000000..375ee8a Binary files /dev/null and b/docs/javascript/04-functions/images/06-closure-3.png differ diff --git a/docs/javascript/04-functions/images/06-closure-4.png b/docs/javascript/04-functions/images/06-closure-4.png new file mode 100644 index 0000000..64bb5d6 Binary files /dev/null and b/docs/javascript/04-functions/images/06-closure-4.png differ diff --git a/docs/javascript/04-functions/images/06-closure-5.png b/docs/javascript/04-functions/images/06-closure-5.png new file mode 100644 index 0000000..7a876b9 Binary files /dev/null and b/docs/javascript/04-functions/images/06-closure-5.png differ