ÐÑи пеÑедаÑе меÑодов обÑекÑа в каÑеÑÑве колбÑков, напÑÐ¸Ð¼ÐµÑ Ð´Ð»Ñ setTimeout, Ð²Ð¾Ð·Ð½Ð¸ÐºÐ°ÐµÑ Ð¸Ð·Ð²ÐµÑÑÐ½Ð°Ñ Ð¿Ñоблема â поÑеÑÑ this.
Ð ÑÑой главе Ð¼Ñ Ð¿Ð¾ÑмоÑÑим, как ÐµÑ Ð¼Ð¾Ð¶Ð½Ð¾ ÑеÑиÑÑ.
ÐоÑеÑÑ Â«this»
ÐÑ Ñже видели пÑимеÑÑ Ð¿Ð¾ÑеÑи this. Ðак ÑолÑко меÑод пеÑедаÑÑÑÑ Ð¾ÑделÑно Ð¾Ñ Ð¾Ð±ÑекÑа â this ÑеÑÑеÑÑÑ.
ÐÐ¾Ñ ÐºÐ°Ðº ÑÑо Ð¼Ð¾Ð¶ÐµÑ Ð¿ÑоизойÑи в ÑлÑÑае Ñ setTimeout:
let user = {
firstName: "ÐаÑÑ",
sayHi() {
alert(`ÐÑивеÑ, ${this.firstName}!`);
}
};
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(`ÐÑивеÑ, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // ÐÑивеÑ, ÐаÑÑ!
}, 1000);
ТепеÑÑ ÐºÐ¾Ð´ ÑабоÑÐ°ÐµÑ ÐºÐ¾ÑÑекÑно, Ñак как обÑÐµÐºÑ user доÑÑаÑÑÑÑ Ð¸Ð· замÑканиÑ, а заÑем вÑзÑваеÑÑÑ ÐµÐ³Ð¾ меÑод sayHi.
То же Ñамое, ÑолÑко коÑоÑе:
setTimeout(() => user.sayHi(), 1000); // ÐÑивеÑ, ÐаÑÑ!
ÐÑглÑÐ´Ð¸Ñ Ñ Ð¾ÑоÑо, но ÑепеÑÑ Ð² наÑем коде поÑвилаÑÑ Ð½ÐµÐ±Ð¾Ð»ÑÑÐ°Ñ ÑÑзвимоÑÑÑ.
ЧÑо пÑоизойдÑÑ, еÑли до моменÑа ÑÑабаÑÑÐ²Ð°Ð½Ð¸Ñ setTimeout (Ð²ÐµÐ´Ñ Ð·Ð°Ð´ÐµÑжка ÑоÑÑавлÑÐµÑ ÑелÑÑ ÑекÑндÑ!) в пеÑеменнÑÑ user бÑÐ´ÐµÑ Ð·Ð°Ð¿Ð¸Ñано дÑÑгое знаÑение? Тогда вÑзов неожиданно бÑÐ´ÐµÑ ÑовÑем не ÑоÑ!
let user = {
firstName: "ÐаÑÑ",
sayHi() {
alert(`ÐÑивеÑ, ${this.firstName}!`);
}
};
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(`ÐÑивеÑ, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user); // (*)
sayHi(); // ÐÑивеÑ, ÐаÑÑ!
setTimeout(sayHi, 1000); // ÐÑивеÑ, ÐаÑÑ!
Ð ÑÑÑоке (*) Ð¼Ñ Ð±ÐµÑÑм меÑод user.sayHi и пÑивÑзÑваем его к user. ТепеÑÑ sayHi â ÑÑо «ÑвÑзаннаÑ» ÑÑнкÑиÑ, коÑоÑÐ°Ñ Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð²Ñзвана оÑделÑно или пеÑедана в setTimeout (конÑекÑÑ Ð²Ñегда бÑÐ´ÐµÑ Ð¿ÑавилÑнÑм).
ÐдеÑÑ Ð¼Ñ Ð¼Ð¾Ð¶ÐµÐ¼ ÑвидеÑÑ, ÑÑо bind иÑпÑавлÑÐµÑ ÑолÑко this, а аÑгÑменÑÑ Ð¿ÐµÑедаÑÑÑÑ ÐºÐ°Ðº еÑÑÑ:
let user = {
firstName: "ÐаÑÑ",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
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) {
return 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(`[${time}] ${this.firstName}: ${phrase}!`);
}
};
// добавлÑем ÑаÑÑиÑно пÑименÑннÑй меÑод Ñ ÑикÑиÑованнÑм вÑеменем
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 вÑÑ Ð²ÑÐµÐ¼Ñ Ð±ÑÐ´ÐµÑ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð² Ð´Ð»Ñ Ð½Ð°Ñей задаÑи, Ñо Ð¼Ñ Ð¼Ð¾Ð¶ÐµÐ¼ ÑоздаÑÑ ÑаÑÑиÑно пÑименÑннÑÑ ÑÑнкÑÐ¸Ñ Ð¸ далÑÑе ÑабоÑаÑÑ Ñ Ð½ÐµÐ¹.
ÐомменÑаÑии
<code>, Ð´Ð»Ñ Ð½ÐµÑколÑÐºÐ¸Ñ ÑÑÑок кода — Ñег<pre>, еÑли болÑÑе 10 ÑÑÑок — ÑÑÑÐ»ÐºÑ Ð½Ð° пеÑоÑниÑÑ (plnkr, JSBin, codepenâ¦)