- Привязка контекста к функции
- Потеря «this»
- Решение 1: сделать функцию-обёртку
- Решение 2: привязать контекст с помощью bind
- Частичное применение
- Частичное применение без контекста
- Итого
- Привязка контекста (this) к функции в javascript и частичное применение функций
- Введение
- 1. jQuery.proxy
- 2. Function.prototype.bind
- Как привязать методы класса к экземпляру класса с контекстом this
- Способ появившийся ещё от пещерного человека
- Автоматическая привязка
- Прокси
- Subscribe to Блог php программиста: статьи по PHP, JavaScript, MySql
Привязка контекста к функции
При передаче методов объекта в качестве колбэков, например для setTimeout , возникает известная проблема – потеря this .
В этой главе мы посмотрим, как её можно решить.
Потеря «this»
Мы уже видели примеры потери this . Как только метод передаётся отдельно от объекта – this теряется.
Вот как это может произойти в случае с setTimeout :
При запуске этого кода мы видим, что вызов this.firstName возвращает не «Вася», а undefined !
Это произошло потому, что setTimeout получил функцию sayHi отдельно от объекта user (именно здесь функция и потеряла контекст). То есть последняя строка может быть переписана как:
Метод setTimeout в браузере имеет особенность: он устанавливает this=window для вызова функции (в Node.js this становится объектом таймера, но здесь это не имеет значения). Таким образом, для this.firstName он пытается получить window.firstName , которого не существует. В других подобных случаях this обычно просто становится undefined .
Задача довольно типичная – мы хотим передать метод объекта куда-то ещё (в этом конкретном случае – в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте?
Решение 1: сделать функцию-обёртку
Самый простой вариант решения – это обернуть вызов в анонимную функцию, создав замыкание:
Теперь код работает корректно, так как объект user достаётся из замыкания, а затем вызывается его метод sayHi .
То же самое, только короче:
Выглядит хорошо, но теперь в нашем коде появилась небольшая уязвимость.
Что произойдёт, если до момента срабатывания setTimeout (ведь задержка составляет целую секунду!) в переменную user будет записано другое значение? Тогда вызов неожиданно будет совсем не тот!
Следующее решение гарантирует, что такого не случится.
Решение 2: привязать контекст с помощью bind
В современном JavaScript у функций есть встроенный метод bind, который позволяет зафиксировать this .
Базовый синтаксис bind :
Результатом вызова func.bind(context) является особый «экзотический объект» (термин взят из спецификации), который вызывается как функция и прозрачно передаёт вызов в func , при этом устанавливая this=context .
Другими словами, вызов boundFunc подобен вызову func с фиксированным this .
Например, здесь funcUser передаёт вызов в func , фиксируя this=user :
Здесь func.bind(user) – это «связанный вариант» func , с фиксированным this=user .
Все аргументы передаются исходному методу func как есть, например:
Теперь давайте попробуем с методом объекта:
В строке (*) мы берём метод user.sayHi и привязываем его к user . Теперь sayHi – это «связанная» функция, которая может быть вызвана отдельно или передана в setTimeout (контекст всегда будет правильным).
Здесь мы можем увидеть, что bind исправляет только this , а аргументы передаются как есть:
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например _.bindAll(obj) в lodash.
Частичное применение
До сих пор мы говорили только о привязывании this . Давайте шагнём дальше.
Мы можем привязать не только this , но и аргументы. Это делается редко, но иногда может быть полезно.
Полный синтаксис bind :
Это позволяет привязать контекст this и начальные аргументы функции.
Например, у нас есть функция умножения mul(a, b) :
Давайте воспользуемся bind , чтобы создать функцию double на её основе:
Вызов mul.bind(null, 2) создаёт новую функцию double , которая передаёт вызов mul , фиксируя null как контекст, и 2 – как первый аргумент. Следующие аргументы передаются как есть.
Это называется частичное применение – мы создаём новую функцию, фиксируя некоторые из существующих параметров.
Обратите внимание, что в данном случае мы на самом деле не используем this . Но для bind это обязательный параметр, так что мы должны передать туда что-нибудь вроде null .
В следующем коде функция triple умножает значение на три:
Для чего мы обычно создаём частично применённую функцию?
Польза от этого в том, что возможно создать независимую функцию с понятным названием ( double , triple ). Мы можем использовать её и не передавать каждый раз первый аргумент, т.к. он зафиксирован с помощью bind .
В других случаях частичное применение полезно, когда у нас есть очень общая функция и для удобства мы хотим создать её более специализированный вариант.
Например, у нас есть функция send(from, to, text) . Потом внутри объекта user мы можем захотеть использовать её частный вариант: sendTo(to, text) , который отправляет текст от имени текущего пользователя.
Частичное применение без контекста
Что если мы хотим зафиксировать некоторые аргументы, но не контекст this ? Например, для метода объекта.
Встроенный bind не позволяет этого. Мы не можем просто опустить контекст и перейти к аргументам.
К счастью, легко создать вспомогательную функцию partial , которая привязывает только аргументы.
Результатом вызова partial(func[, arg1, arg2. ]) будет обёртка (*) , которая вызывает func с:
- Тем же this , который она получает (для вызова user.sayNow – это будет user )
- Затем передаёт ей . argsBound – аргументы из вызова partial ( «10:00» )
- Затем передаёт ей . args – аргументы, полученные обёрткой ( «Hello» )
Благодаря оператору расширения . реализовать это очень легко, не правда ли?
Также есть готовый вариант _.partial из библиотеки lodash.
Итого
Метод bind возвращает «привязанный вариант» функции func , фиксируя контекст this и первые аргументы arg1 , arg2 …, если они заданы.
Обычно bind применяется для фиксации this в методе объекта, чтобы передать его в качестве колбэка. Например, для setTimeout .
Когда мы привязываем аргументы, такая функция называется «частично применённой» или «частичной».
Частичное применение удобно, когда мы не хотим повторять один и тот же аргумент много раз. Например, если у нас есть функция send(from, to) и from всё время будет одинаков для нашей задачи, то мы можем создать частично применённую функцию и дальше работать с ней.
Источник
Привязка контекста (this) к функции в javascript и частичное применение функций
В предыдущем посте я описал, что this в javascript не привязывается к объекту, а зависит от контекста вызова. На практике же часто возникает необходимость в том, чтобы this внутри функции всегда ссылался на конкретный объект.
В данной статье мы рассмотрим два подхода для решения данной задачи.
1. jQuery.proxy — подход с использованием популярной библиотеки jQuery
2. Function.prototype.bind — подход, добавленный в JavaScript 1.8.5. Рассмотрим также его применение для карринга (частичного применения функции) и некоторые тонкости работы, о которых знают единицы.
Введение
Рассмотрим простой объект, содержащий свойство x и метод f, который выводит в консоль значение this.x
Как я указывал в предыдущем посте, при вызове object.f() в консоли будет выведено число 3. Предположим теперь, что нам нужно вывести данное число через 1 секунду.
Каждый раз использовать функцию обертку — неудобно. Нужен способ привязать контекст функции, так, чтобы this внутри функции object.f всегда ссылался на object
1. jQuery.proxy
Ни для кого не секрет, что jQuery — очень популярная библиотека javascript, поэтому вначале мы рассмотрим применение jQuery.proxy для привязки контекста к функции.
jQuery.proxy возвращает новую функцию, которая при вызове вызывает оригинальную функцию function в контексте context. С использованием jQuery.proxy вышеописанную задачу можно решить так:
Если нам нужно указать несколько раз одинаковый callback, то вместо дублирования
лучше вынести результат работы $.proxy в отдельную переменную
Обратим теперь внимание на то, что мы дважды указали object внутри $.proxy (первый раз метод объекта — object.f, второй — передаваемй контекст — object). Может быть есть возможность избежать дублирования? Ответ — да. Для таких случаев в $.proxy добавлена альтернативная возможность передачи параметров — первым параметром должен быть объект, а вторым — название его метода. Пример:
Обратите внимание на то, что название метода передается в виде строки.
2. Function.prototype.bind
Перейдем к рассмотрению Function.prototype.bind. Данный метод был добавлен в JavaScript 1.8.5.
Function.prototype.bind имеет 2 назначения — статическая привязка контекста к функции и частичное применение функции.
По сути bind создаёт новую функцию, которая вызывает func в контексте context. Если указаны аргументы arg1, arg2… — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове новой функции.
2.1. Привязка контекста
Использовать bind для привязки контекста очень просто, достаточно рассмотреть пример:
Таким образом пример из введения можно записать в следующем виде:
2.2. Частичное применение функций
Для упрощения рассмотрим сразу пример использования bind для частичного применения функций
Как видно из примера — суть частичного применения функций проста — создание новой функции с уменьшенным количеством аргументов, за счет «фиксации» первых аргументов с помощью функции bind.
На этом можно было бы закончить статью, но… Функции, полученные с использованием метода bind имеют некоторые особенности в поведении
2.3. Особенности bind
В комментариях к предыдущей статье было приведено 2 примера, касающихся bind (раз, два).
Я решил сделать микс из этих примеров, попутно изменив строковые значения, чтобы было проще с ними разбираться.
Пример (попробуйте угадать ответы)
Прежде чем разобрать — я перечислю основные особенности bind в соответствии со стандартом.
2.3.1. Внутренние свойств
У объектов Function, созданных посредством Function.prototype.bind, отсутствует свойство prototype или внутренние свойства [[Code]], [[FormalParameters]] и [[Scope]].
Это ограничение отличает built-in реализацию bind от вручную определенных методов (например, вариант из MDN)
2.3.2. call и apply
Поведение методов call и apply отличается от стандартного поведения для функций, а именно:
В коде видно, что thisValue не используется нигде. Таким образом подменить контекст вызова для функций полученных с помощью Function.prototype.bind с использованием call и apply — нельзя!
2.3.3. В конструкторе
В конструкторе this ссылается на новый (создаваемый) объект. Иначе говоря, контекст заданный при помощи bind, просто игнорируется. Конструктор вызывает обычный [[Call]] исходной функции.
Важно! Если в конструкторе отсутствует return this, то возвращаемое значение в общем случае неопределено и зависит от возвращаемого значения новой функции!
Источник
Как привязать методы класса к экземпляру класса с контекстом this
Есть несколько способов обеспечить доступ к this в методах класса JavaScript. В этой статье мы быстро рассмотрим наиболее распространенные способы реализации этой задачи, обсудив преимущества и недостатки каждого из них.
Проблема появляется, когда у нас есть метод класса, похожий на этот:
Затем, по какой-то причине — контекст метода printName меняется, ожидания не оправдываются, что приводит к ошибкам.
Проблема, конечно, заключается в том, что в JavaScript ещё нет семантики, с помощью которого мы могли бы легко привязать каждый метод класса к контексту самого класса. Но давайте разберёмся, какими путями мы можем привязать контекст this к методам класса.
Способ появившийся ещё от пещерного человека
В этом сценарии мы просто вручную связываем все методы к экземпляром класса в самом конструкторе.
Это наименее элегантное решение, но оно работает. К недостаткам можно отнести необходимость отслеживать, какие методы используют контекст this и должны быть сбинжены, или обеспечивать привязку каждого метода, добавляя каждый раз вызов .bind для новых методов по мере их добавления и удалять вызов .bind для методов, которые мы удаляем. К преимуществам относится ясность и отсутствие необходимости в дополнительном коде.
Автоматическая привязка
Похожий, но менее болезненный подход — использование модуля, который позаботится об этом от нашего имени. С помощью библиотеки auto-bind, которая обходит всё методы объекта и связывает их с текущим контекстом this .
Этот подход хорошо работает для классов, хотя нам, как и раньше, не обойтись без конструктора. Преимуществом этого метода является то, что нам не нужно отслеживать каждый метод по имени для их привязки. В то же время, если мы имеем дело с объектами, а не классами, нам необходимо убедиться, что autoBind вызывается для объекта после того, как каждый метод был назначен объекту, иначе некоторые методы останутся непривязанными. Любые методы, добавленные после вызова autoBind , являются несвязанными, а это означает, что в некоторых ситуациях autoBind является ещё худшим вариантом, чем ручной вызов .bind для каждого метода.
Прокси
Объект Proxy может использоваться для перехвата get-операций (геттер), в котором можно возвращать методы, привязанные к классу. Ниже у нас есть функция selfish , которая принимает объект и возвращает прокси для этого объекта. Любые методы, доступ к которым осуществляется через прокси, будут автоматически привязаны к объекту. WeakMap используется, чтобы гарантировать, что мы связываем методы только один раз, так что равенство в proxy.fn === proxy.fn сохраняется.
Преимущество этого подхода заключается в том, что методы целевого объекта будут связаны независимо от того, были ли они добавлены до или после создания прокси-объекта. Очевидным недостатком является скудная поддержка прокси даже с учетом транспилеров.
Даже если бы прокси были общедоступными, это решение было бы далеко не идеальным. Разница заключается в том, что библиотеки потенциально могут реализовывать что-то вроде функции selfish , чтобы компоненты, которые вы передаете библиотеке, следовали этой семантике без необходимости что-либо менять. Тем не менее, единственное реальное решение заключается в движении языка вперед, добавлении семантики для классов, где каждый метод привязан к this по умолчанию.
Но все эти варианты определенно лучше, чем старый способ, когда нам приходилось писать Logger.prototype.print . Приватная область видимость для классов, в которой вы можете объявлять функции, привязанные к этому классу, которые не являются методами класса (но доступны для каждого метода), и другие свойства, привязанные к классу, были бы огромным шагом вперед для развития семантики классов.
Наличие менее подробной, утомительной, подверженной ошибкам или сложной семантики привязки контекста this — это, в основном, вопрос времени. Что касается JavaScript, все мы знаем, что он развивается семимильными шагами.
Subscribe to Блог php программиста: статьи по PHP, JavaScript, MySql
Get the latest posts delivered right to your inbox
Источник