Привязка контекста к функции
При передаче методов объекта в качестве колбэков, например для setTimeout , возникает известная проблема – потеря this .
В этой главе мы посмотрим, как её можно решить.
Потеря «this»
Мы уже видели примеры потери this . Как только метод передаётся отдельно от объекта – this теряется.
Вот как это может произойти в случае с setTimeout :
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(user.sayHi, 1000); // Привет, undefined!
При запуске этого кода мы видим, что вызов this.firstName возвращает не «Вася», а undefined !
Это произошло потому, что setTimeout получил функцию sayHi отдельно от объекта user (именно здесь функция и потеряла контекст). То есть последняя строка может быть переписана как:
let f = user.sayHi; setTimeout(f, 1000); // контекст user потеряли
Метод setTimeout в браузере имеет особенность: он устанавливает this=window для вызова функции (в Node.js this становится объектом таймера, но здесь это не имеет значения). Таким образом, для this.firstName он пытается получить window.firstName , которого не существует. В других подобных случаях this обычно просто становится undefined .
Задача довольно типичная – мы хотим передать метод объекта куда-то ещё (в этом конкретном случае – в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте?
Решение 1: сделать функцию-обёртку
Самый простой вариант решения – это обернуть вызов в анонимную функцию, создав замыкание:
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(function() < user.sayHi(); // Привет, Вася! >, 1000);
Теперь код работает корректно, так как объект user достаётся из замыкания, а затем вызывается его метод sayHi .
То же самое, только короче:
setTimeout(() => user.sayHi(), 1000); // Привет, Вася!
Выглядит хорошо, но теперь в нашем коде появилась небольшая уязвимость.
Что произойдёт, если до момента срабатывания setTimeout (ведь задержка составляет целую секунду!) в переменную user будет записано другое значение? Тогда вызов неожиданно будет совсем не тот!
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(() => user.sayHi(), 1000); // . в течение 1 секунды user = < sayHi() < alert("Другой пользователь в 'setTimeout'!"); >>; // Другой пользователь в 'setTimeout'!
Следующее решение гарантирует, что такого не случится.
Решение 2: привязать контекст с помощью bind
В современном JavaScript у функций есть встроенный метод bind, который позволяет зафиксировать this .
Базовый синтаксис bind :
// полный синтаксис будет представлен немного позже let boundFunc = func.bind(context);
Результатом вызова func.bind(context) является особый «экзотический объект» (термин взят из спецификации), который вызывается как функция и прозрачно передаёт вызов в func , при этом устанавливая this=context .
Другими словами, вызов boundFunc подобен вызову func с фиксированным this .
Например, здесь funcUser передаёт вызов в func , фиксируя this=user :
let user = < firstName: "Вася" >; function func() < alert(this.firstName); >let funcUser = func.bind(user); funcUser(); // Вася
Здесь func.bind(user) – это «связанный вариант» func , с фиксированным this=user .
Все аргументы передаются исходному методу func как есть, например:
let user = < firstName: "Вася" >; function func(phrase) < alert(phrase + ', ' + this.firstName); >// привязка this к user let funcUser = func.bind(user); funcUser("Привет"); // Привет, Вася (аргумент "Привет" передан, при этом this = user)
Теперь давайте попробуем с методом объекта:
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; let sayHi = user.sayHi.bind(user); // (*) sayHi(); // Привет, Вася! setTimeout(sayHi, 1000); // Привет, Вася!
В строке (*) мы берём метод user.sayHi и привязываем его к user . Теперь sayHi – это «связанная» функция, которая может быть вызвана отдельно или передана в setTimeout (контекст всегда будет правильным).
Здесь мы можем увидеть, что bind исправляет только this , а аргументы передаются как есть:
let user = < firstName: "Вася", say(phrase) < alert(`$, $!`); > >; let say = user.say.bind(user); say("Привет"); // Привет, Вася (аргумент "Привет" передан в функцию "say") say("Пока"); // Пока, Вася (аргумент "Пока" передан в функцию "say")
Удобный метод: bindAll
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
for (let key in user) < if (typeof user[key] == 'function') < user[key] = user[key].bind(user); >>
Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например _.bindAll(obj) в lodash.
Частичное применение
До сих пор мы говорили только о привязывании this . Давайте шагнём дальше.
Мы можем привязать не только this , но и аргументы. Это делается редко, но иногда может быть полезно.
Полный синтаксис bind :
let bound = func.bind(context, [arg1], [arg2], . );
Это позволяет привязать контекст this и начальные аргументы функции.
Например, у нас есть функция умножения mul(a, b) :
function mul(a, b)
Давайте воспользуемся bind , чтобы создать функцию double на её основе:
function mul(a, b) < return a * b; >let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10
Вызов mul.bind(null, 2) создаёт новую функцию double , которая передаёт вызов mul , фиксируя null как контекст, и 2 – как первый аргумент. Следующие аргументы передаются как есть.
Это называется частичное применение – мы создаём новую функцию, фиксируя некоторые из существующих параметров.
Обратите внимание, что в данном случае мы на самом деле не используем this . Но для bind это обязательный параметр, так что мы должны передать туда что-нибудь вроде null .
В следующем коде функция triple умножает значение на три:
function mul(a, b) < return a * b; >let triple = mul.bind(null, 3); alert( triple(3) ); // = mul(3, 3) = 9 alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15
Для чего мы обычно создаём частично применённую функцию?
Польза от этого в том, что возможно создать независимую функцию с понятным названием ( double , triple ). Мы можем использовать её и не передавать каждый раз первый аргумент, т.к. он зафиксирован с помощью bind .
В других случаях частичное применение полезно, когда у нас есть очень общая функция и для удобства мы хотим создать её более специализированный вариант.
Например, у нас есть функция send(from, to, text) . Потом внутри объекта user мы можем захотеть использовать её частный вариант: sendTo(to, text) , который отправляет текст от имени текущего пользователя.
Частичное применение без контекста
Что если мы хотим зафиксировать некоторые аргументы, но не контекст this ? Например, для метода объекта.
Встроенный bind не позволяет этого. Мы не можем просто опустить контекст и перейти к аргументам.
К счастью, легко создать вспомогательную функцию partial , которая привязывает только аргументы.
function partial(func, . argsBound) < return function(. args) < // (*) return func.call(this, . argsBound, . args); >> // использование: let user = < firstName: "John", say(time, phrase) < alert(`[$] $: $!`); > >; // добавляем частично применённый метод с фиксированным временем user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Что-то вроде этого: // [10:00] John: Hello!
Результатом вызова 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 всё время будет одинаков для нашей задачи, то мы можем создать частично применённую функцию и дальше работать с ней.
Как передать функцию в функцию js
Функция в функцию передаётся как обычный параметр, так же, как это работает с любыми другими значениями. Например:
const sayHello = () => console.log('Hello!'); >; const execFunc = (func) => func(); >; execFunc(sayHello); // => Hello!
Частая ошибка, когда новички передают не саму функцию, а вызов функции. Будьте внимательны, при таком подходе, сначала вычислиться то, что мы передаём, затем это значение попадёт в функцию:
const sayHello = () => console.log('Hello!'); >; const execFunc = (func) => func(); >; execFunc(sayHello()); // => TypeError: func is not a function
Ошибка возникает, потому что в функцию execFunc() передаётся не функция, а вызов функции sayHello() .
this
Поведение ключевого слова this в JavaScript несколько отличается по сравнению с остальными языками. Имеются также различия при использовании this в строгом и нестрогом режиме.
В большинстве случаев значение this определяется тем, каким образом вызвана функция. Значение this не может быть установлено путём присваивания во время исполнения кода и может иметь разное значение при каждом вызове функции. В ES5 представлен метод bind() , который используется для привязки значения ключевого слова this независимо от того, как вызвана функция . Также в ES2015 представлены стрелочные функции , которые не создают собственные привязки к this (они сохраняют значение this лексического окружения, в котором были созданы).
Интерактивный пример
Синтаксис
this
Значение
Свойство контекста выполнения кода (global, function или eval), которое в нестрогом режиме всегда является ссылкой на объект, а в строгом режиме может иметь любое значение.
Global контекст
В глобальном контексте выполнения (за пределами каких-либо функций) this ссылается на глобальный объект вне зависимости от режима (строгий или нестрогий).
// В браузерах, объект window также является объектом global: console.log(this === window); // true a = 37; console.log(window.a); // 37 this.b = "MDN"; console.log(window.b); // "MDN" console.log(b); // "MDN"
Примечание: вы всегда можете легко получить объект global, используя глобальное свойство globalThis , независимо от текущего контекста, в котором выполняется ваш код.
Function контекст
В пределах функции значение this зависит от того, каким образом вызвана функция.
Простой вызов
Поскольку следующий код не в строгом режиме , и значение this не устанавливается вызовом, по умолчанию будет использоваться объект global, которым в браузере является window .
function f1() return this; > // В браузере: f1() === window; // window - глобальный объект в браузере // В Node: f1() === global; // global - глобальный объект в Node
В строгом режиме, если значение this не установлено в контексте выполнения, оно остаётся undefined , как показано в следующем примере:
function f2() "use strict"; // см. strict mode return this; > f2() === undefined; // true
Примечание: Во втором примере this должно иметь значение undefined , потому что функция f2 была вызвана напрямую, а не как метод или свойство объекта (например, window.f2() ). Реализация этой особенности не поддерживалась в некоторых браузерах, когда они впервые начали поддерживать строгий режим . В результате они некорректно возвращали объект window .
Для того, чтобы при вызове функции установить this в определённое значение, используйте call() или apply() , как в следующих примерах.
Пример 1
// В качестве первого аргумента методов call или apply может быть передан объект, // на который будет указывать this. var obj = a: "Custom" >; // Это свойство принадлежит глобальному объекту var a = "Global"; function whatsThis() return this.a; //значение this зависит от контекста вызова функции > whatsThis(); // 'Global' whatsThis.call(obj); // 'Custom' whatsThis.apply(obj); // 'Custom'
Пример 2
function add(c, d) return this.a + this.b + c + d; > var o = a: 1, b: 3 >; // Первый параметр - это объект для использования в качестве // 'this', последующие параметры передаются как // аргументы функции call add.call(o, 5, 7); // 16 // Первый параметр - это объект для использования в качестве // 'this', второй - массив, чьи члены используются // в качестве аргументов функции call add.apply(o, [10, 20]); // 34
Обратите внимание, что в нестрогом режиме, если значение, переданное в call или apply как this , не является объектом, будет сделана попытка преобразовать его в объект с помощью внутренней операции ToObject . Таким образом, если переданное значение является примитивом, таким как 7 или ‘foo’ , оно будет преобразовано в Object с использованием связанного конструктора, так что примитивное число 7 будет преобразовано в объект так, как будто с помощью new Number(7) , а строка ‘foo’ — как будто с помощью new String(‘foo’) , например
function bar() console.log(Object.prototype.toString.call(this)); > bar.call(7); // [object Number] bar.call("foo"); // [object String]
Метод bind
ECMAScript 5 представил Function.prototype.bind() . Вызов f.bind(someObject) создаёт новую функцию с тем же телом и областью действия, что и f , но там, где в исходной функции используется this , в новой функции оно постоянно будет связано с первым аргументом bind , независимо от того, как функция используется.
function f() return this.a; > var g = f.bind( a: "azerty" >); console.log(g()); // azerty var h = g.bind( a: "yoo" >); // bind only works once! console.log(h()); // azerty var o = a: 37, f: f, g: g, h: h >; console.log(o.a, o.f(), o.g(), o.h()); // 37,37, azerty, azerty
Стрелочные функции
В стрелочных функциях , this привязан к окружению, в котором была создана функция. В глобальной области видимости this будет указывать на глобальный объект.
var globalObject = this; var foo = () => this; console.log(foo() === globalObject); // true
Примечание: Note: если аргумент this передаётся в call, bind или apply при вызове стрелочной функции, он будет проигнорирован. Вы всё ещё можете добавить аргументы к вызову, но первый аргумент (thisArg) должен быть установлен в null.
Неважно, как стрелочная функция foo() будет вызвана, её значение this будет указывать на глобальный объект. this будет сохранять своё значение, даже если функция foo() будет вызвана как метод объекта (что в обычных функциях связывает this с объектом вызова) или с использованием методов call , apply или bind :
// Вызов функции как метода объекта var obj = foo: foo >; console.log(obj.foo() === globalObject); // true // Попытка установить this с помощью call console.log(foo.call(obj) === globalObject); // true // Попытка установить this с помощью bind foo = foo.bind(obj); console.log(foo() === globalObject); // true
Несмотря ни на что, this стрелочной функции foo() имеет то же значение, что и при создании функции (глобальный объект в примере выше). То же самое касается стрелочных функций, созданных внутри других функций: их this будет привязан к окружению.
// Создаём объект obj, содержащий метод bar, который возвращает функцию, // которая возвращает свой this. Возвращаемая функция создана // как стрелочная функция, таким образом её this постоянно замкнут // на this функции, в которой она создана. Значение bar может быть установлено // в вызове, который, в свою очередь, устанавливает значение возвращаемой функции. var obj = bar: function () var x = () => this; return x; >, >; // Вызываем bar как метод объекта obj, устанавливая его this на obj // Присваиваем ссылку возвращаемой функции переменной fn var fn = obj.bar(); // Вызываем fn без установки this, что в обычных функциях указывало бы // на глобальный объект или undefined в строгом режиме. console.log(fn() === obj); // true // Но будьте осторожны, если вы ссылаетесь на метод obj, не вызывая его var fn2 = obj.bar; // Вызов this стрелочной функции изнутри метода bar вернёт теперь window, // потому что он следует за this из fn2. console.log(fn2()() == window); // true
В примере выше, функция (назовём её анонимной функцией A), присвоенная методу obj.bar , возвращает другую функцию (назовём её анонимной функцией B) которая создана как стрелочная функция. В результате, this функции B при вызове замкнут на this, принадлежащий obj.bar (функции A). this функции B всегда будет иметь то значение, которое он получил при создании. В примере выше, this функции B указывает на this функции A,которым является obj, таким образом this будет равен obj даже тогда, когда будет вызван методом, который в нормальных условиях устанавливал бы значение this равным undefined или глобальному объекту (или любым другим методом, как в предыдущем примере в глобальном контексте выполнения).
В методе объекта
Когда функция вызывается как метод объекта, используемое в этой функции ключевое слово this принимает значение объекта, по отношению к которому вызван метод.
В следующем примере, когда вызвано свойство o.f() , внутри функции this привязано к объекту o.
var o = prop: 37, f: function () return this.prop; >, >; console.log(o.f()); // logs 37
Необходимо отметить, что на поведение this совсем не влияет то, как или где была определена функция. В предыдущем примере мы определили функцию внутри свойства f во время определения объекта o . Однако, мы могли бы также просто определить сначала функцию, а затем закрепить её за свойством o.f . В этом случае поведение this не изменится:
var o = prop: 37 >; function independent() return this.prop; > o.f = independent; console.log(o.f()); // logs 37
Эти примеры показывают, что имеет значение только то, что функция была вызвана из свойства f объекта o .
Аналогично, привязывание this обуславливается наличием ближайшей ссылки на объект или свойство. В следующем примере, когда мы вызываем функцию, мы обращаемся к ней как к методу g объекта o.b . На этот раз во время выполнения, this , что находится внутри функции, будет ссылаться на o.b . Тот факт, что объект является членом объекта o , не имеет значения; важна только ближайшая ссылка.
.b = g: independent, prop: 42 >; console.log(o.b.g()); // logs 42
this в цепочке object’s prototype
Это же представление справедливо и для методов, определённых где-либо в цепочке object’s prototype. Если метод находится в цепочке прототипов, то this ссылается на объект, на котором был вызван метод, т.е. так, словно метод является методом самого объекта, а не прототипа.
var o = f: function () return this.a + this.b; >, >; var p = Object.create(o); p.a = 1; p.b = 4; console.log(p.f()); // 5
В этом примере объект, который присвоен переменной p , не имеет собственного свойства f , а наследует это свойство от своего прототипа. Однако, совершенно неважно, что поиск свойства f в конце концов обнаружит его на объекте o . Поскольку поиск начался с p.f , то и свойство this внутри функции f будет ссылаться на объект p . Таким образом, если f вызывается как метод p , то и this относится к p . Это полезная особенность прототипного наследования JS.
this с геттерами/сеттерами
Все те же утверждения справедливы, если функция вызывается из геттера или сеттера. Для функции, которая используется как геттер или сеттер this привязан к объекту, свойство которого необходимо извлечь через геттер/сеттер.
function modulus() return Math.sqrt(this.re * this.re + this.im * this.im); > var o = re: 1, im: -1, get phase() return Math.atan2(this.im, this.re); >, >; Object.defineProperty(o, "modulus", get: modulus, enumerable: true, configurable: true, >); console.log(o.phase, o.modulus); // logs -0.78 1.4142
В конструкторе
Когда функция используется как конструктор (с ключевым словом new ), this связано с создаваемым новым объектом.
Примечание: по умолчанию конструктор возвращает объект, на который ссылается this , но он может вернуть и другой объект (если возвращаемое значение не является объектом, тогда будет возвращён объект с this ).
/* * Конструктор работает таким образом: * * function MyConstructor() < * // фактический код, составляющий тело функции. * // создание свойств с |this| по * // желанию, определяя их значения; например, * this.fum = "nom"; * // и т.д. * * // Если функция возвращает выражение, * // возвращающее объект, этот объект будет * // результатом выражения |new|. В обратном случае, * // результат выражения - объект, * // в данный момент привязанный к |this| * // (т.е. наиболее часто встречающийся случай). * >*/ function C() this.a = 37; > var o = new C(); console.log(o.a); // logs 37 function C2() this.a = 37; return a: 38 >; > o = new C2(); console.log(o.a); // logs 38
В последнем примере ( C2 ), из-за того, что конструктор вернул объект, новый объект, к которому было привязано this , был просто отброшен. (Это фактически делает выражение » this.a = 37; » «мёртвым» кодом. Он не является буквально нерабочим, так как он выполняется, но он может быть изъят без каких-либо внешних эффектов.)
call и apply
Когда в теле функции используется ключевое слово this , его значение может быть привязано к конкретному объекту в вызове при помощи методов call или apply , которые наследуются всеми функциями от Function.prototype .
function add(c, d) return this.a + this.b + c + d; > var o = a: 1, b: 3 >; // Первый параметр - это объект, который следует использовать как // 'this', последующие параметры передаются // как аргументы при вызове функции add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 // Первый параметр - объект, который следует использовать как // 'this', второй параметр - массив, // элементы которого используются как аргументы при вызове функции add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
Необходимо отметить, что если методам call и apply передаётся значение с this , которое не является при этом объектом, будет предпринята попытка конвертировать значение в объект, используя внутреннюю операцию ToObject . Если переданное значение является примитивным типом, например 7 или ‘foo’ , оно будет преобразовано в объект с использованием родственного конструктора, так примитив 7 преобразовывается в объект через new Number(7), а строка ‘foo’ в объект через new String(‘foo’), и т.д.
function bar() console.log(Object.prototype.toString.call(this)); > bar.call(7); // [object Number]
Как обработчик событий DOM
Когда функция используется как обработчик событий, this присваивается элементу с которого начинается событие (некоторые браузеры не следуют этому соглашению для обработчиков, добавленных динамически с помощью всех методов, кроме addEventListener ).
// Когда вызывается как обработчик, связанный элемент становится синим function bluify(e) // Всегда true console.log(this === e.currentTarget); // true, когда currentTarget и target один объект console.log(this === e.target); this.style.backgroundColor = "#A5D9F3"; > // Получить список каждого элемента в документе var elements = document.getElementsByTagName("*"); // Добавить bluify как обработчика кликов, чтобы при // нажатии на элемент он становился синим for (var i = 0; i elements.length; i++) elements[i].addEventListener("click", bluify, false); >
В инлайновом обработчике событий
Когда код вызван из инлайнового обработчика, this указывает на DOM-элемент, в котором расположен код события:
button onclick="alert(this.tagName.toLowerCase());">Показать this/button>
Код выше выведет ‘ button ‘. Следует отметить, что this будет указывать на DOM-элемент только во внешних (не вложенных) функциях:
button onclick="alert((function() ()));"> Показать вложенный this /button>
В этом случае this вложенной функции не будет установлен, так что будет возвращён global/window объект.
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-this-keyword |
Совместимость
BCD tables only load in the browser
Смотрите также
- Строгий режим
- All this, статья о this в разном контексте
- Краткое объяснение ключевого слова ‘this’ в JavaScript
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2023 by individual mozilla.org contributors. Content available under a Creative Commons license.
Привязка контекста (this) к функции в javascript и частичное применение функций
В предыдущем посте я описал, что this в javascript не привязывается к объекту, а зависит от контекста вызова. На практике же часто возникает необходимость в том, чтобы this внутри функции всегда ссылался на конкретный объект.
В данной статье мы рассмотрим два подхода для решения данной задачи.
1. jQuery.proxy — подход с использованием популярной библиотеки jQuery
2. Function.prototype.bind — подход, добавленный в JavaScript 1.8.5. Рассмотрим также его применение для карринга (частичного применения функции) и некоторые тонкости работы, о которых знают единицы.
Введение
Рассмотрим простой объект, содержащий свойство x и метод f, который выводит в консоль значение this.x
var object = < x: 3, f: function() < console.log(this.x); >>
Как я указывал в предыдущем посте, при вызове object.f() в консоли будет выведено число 3. Предположим теперь, что нам нужно вывести данное число через 1 секунду.
setTimeout(object.f, 1000); // выведет undefined //простой способ это обойти — сделать вызов через обёртку: setTimeout(function() < object.f(); >, 1000); // выведет 3
Каждый раз использовать функцию обертку — неудобно. Нужен способ привязать контекст функции, так, чтобы this внутри функции object.f всегда ссылался на object
1. jQuery.proxy
jQuery.proxy(function, context); jQuery.proxy(context, name);
Ни для кого не секрет, что jQuery — очень популярная библиотека javascript, поэтому вначале мы рассмотрим применение jQuery.proxy для привязки контекста к функции.
jQuery.proxy возвращает новую функцию, которая при вызове вызывает оригинальную функцию function в контексте context. С использованием jQuery.proxy вышеописанную задачу можно решить так:
setTimeout($.proxy(object.f, object), 1000); // выведет 3
Если нам нужно указать несколько раз одинаковый callback, то вместо дублирования
setTimeout($.proxy(object.f, object), 1000); setTimeout($.proxy(object.f, object), 2000); setTimeout($.proxy(object.f, object), 3000);
лучше вынести результат работы $.proxy в отдельную переменную
var fn = $.proxy(object.f, object); setTimeout(fn, 1000); setTimeout(fn, 2000); setTimeout(fn, 3000);
Обратим теперь внимание на то, что мы дважды указали object внутри $.proxy (первый раз метод объекта — object.f, второй — передаваемй контекст — object). Может быть есть возможность избежать дублирования? Ответ — да. Для таких случаев в $.proxy добавлена альтернативная возможность передачи параметров — первым параметром должен быть объект, а вторым — название его метода. Пример:
var fn = $.proxy(object, "f"); setTimeout(fn, 1000);
Обратите внимание на то, что название метода передается в виде строки.
2. Function.prototype.bind
func.bind(context[, arg1[, arg2[, . ]]])
Перейдем к рассмотрению Function.prototype.bind. Данный метод был добавлен в JavaScript 1.8.5.
Совместимость с браузерами
Firefox (Gecko): 4.0 (2)
Chrome: 7
Internet Explorer: 9
Opera: 11.60
Safari: 5.1.4
Эмуляция Function.prototype.bind из Mozilla Developer Network
Function.prototype.bind = function (oThis) < if (typeof this !== "function") < // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); >var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () <>, fBound = function () < return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); >; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; >;
Function.prototype.bind имеет 2 назначения — статическая привязка контекста к функции и частичное применение функции.
По сути bind создаёт новую функцию, которая вызывает func в контексте context. Если указаны аргументы arg1, arg2… — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове новой функции.
2.1. Привязка контекста
Использовать bind для привязки контекста очень просто, достаточно рассмотреть пример:
function f() < console.log(this.x); >var bound = f.bind(); // bound - новая функция - "обертка", у которой this ссылается на объект bound();// Выведет 3
Таким образом пример из введения можно записать в следующем виде:
var object = < x: 3, f: function() < console.log(this.x); >> setTimeout(object.f.bind(object), 1000); // выведет 3
2.2. Частичное применение функций
Для упрощения рассмотрим сразу пример использования bind для частичного применения функций
function f(x, y, z) < console.log(x + y + z); >var bound = f.bind(null, 3, 5); // напомню что первый параметр - это контекст для функции, поскольку мы не используем this в функции f, то контекст не имеет значения - поэтому в данном случае передан null bound(7); // распечатает 15 (3 + 5 + 7) bound(17); // распечатает 25 (3 + 5 + 17)
Как видно из примера — суть частичного применения функций проста — создание новой функции с уменьшенным количеством аргументов, за счет «фиксации» первых аргументов с помощью функции bind.
На этом можно было бы закончить статью, но… Функции, полученные с использованием метода bind имеют некоторые особенности в поведении
2.3. Особенности bind
В комментариях к предыдущей статье было приведено 2 примера, касающихся bind (раз, два).
Я решил сделать микс из этих примеров, попутно изменив строковые значения, чтобы было проще с ними разбираться.
Пример (попробуйте угадать ответы)
function ClassA() < console.log(this.x, arguments) >ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind(, "bindArg"); ClassB.prototype = ; new ClassA("callArg"); new ClassB("callArg"); ClassB("callArg"); ClassB.call(, 'callArg');
Ответы
fromProtoA [«callArg»]
fromProtoA [«bindArg», «callArg»]
fromBind [«bindArg», «callArg»]
fromBind [«bindArg», «callArg»]
Прежде чем разобрать — я перечислю основные особенности bind в соответствии со стандартом.
2.3.1. Внутренние свойств
У объектов Function, созданных посредством Function.prototype.bind, отсутствует свойство prototype или внутренние свойства [[Code]], [[FormalParameters]] и [[Scope]].
Это ограничение отличает built-in реализацию bind от вручную определенных методов (например, вариант из MDN)
2.3.2. call и apply
Поведение методов call и apply отличается от стандартного поведения для функций, а именно:
boundFn.[[Call]] = function (thisValue, extraArgs): var boundArgs = boundFn.[[BoundArgs]], boundThis = boundFn.[[BoundThis]], targetFn = boundFn.[[TargetFunction]], args = boundArgs.concat(extraArgs); return targetFn.[[Call]](boundThis, args);
В коде видно, что thisValue не используется нигде. Таким образом подменить контекст вызова для функций полученных с помощью Function.prototype.bind с использованием call и apply — нельзя!
2.3.3. В конструкторе
В конструкторе this ссылается на новый (создаваемый) объект. Иначе говоря, контекст заданный при помощи bind, просто игнорируется. Конструктор вызывает обычный [[Call]] исходной функции.
Важно! Если в конструкторе отсутствует return this, то возвращаемое значение в общем случае неопределено и зависит от возвращаемого значения новой функции!
Разбор примера
function ClassA() < console.log(this.x, arguments) >ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind(, "bindArg"); // исходя из 2.3.1, эта строчка не делает ровным счетом ничего в built-in реализациях Function.prototype.bind // Но в ручной реализации bind (например, как в MDN) эта строчка сыграет роль ClassB.prototype = ; // Тут все просто - никакой bind мы еще не использовали // Результат: fromProtoA ["callArg"] new ClassA("callArg"); // Исходя из 2.3.3 - this ссылается на новый объект. поскольку в bind был задан параметр bindArg, то в выводе аргументов он займет первое место // Результат: fromProtoA ["bindArg", "callArg"] // При ручной реализации bind результат будет другой: fromBind ["bindArg", "callArg"]. new ClassB("callArg"); // Обычный вызов bind функции, поэтому в качестве контекста будет , первым параметром bindArg (заданный через bind), вторым - "callArg" // Результат: fromBind ["bindArg", "callArg"] ClassB("callArg"); // Из пункта 2.3.2. следует, что при вызове метода call на функции, полученной с использованием bind передаваемый контекст игнорируется. // Результат: fromBind ["bindArg", "callArg"] ClassB.call(, 'callArg');
Заключение
В данном посте я постарался описать основные методы привязывания контекста к функциям, а также описал некоторые особенности в работе Function.prototype.bind, при этом я старался оставить только важные детали (с моей точки зрения).
Если вы заметили ошибки/неточности или хотите что-то уточнить/добавить — напишите в ЛС, поправлю
- javascript
- bound functions
- jQuery.proxy
- Function.prototype.bind
