Способы привязки контекста js

Привязка контекста к функции

При передаче методов объекта в качестве колбэков, например для 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

Источник

Оцените статью
Разные способы