Объекттин ыкмаларын кайра чалуу катары өткөрүп жатканда, мисалы үчүн setTimeout
, белгилүү көйгөй бар - жоготуу this
.
Бул бөлүмдө биз аны кантип чечсе болорун көрөбүз.
"this" жоготуу
Биз буга чейин жоготуу мисалдарын көрдүк this
. Метод объектиден өзүнчө өтөөр замат this
ал жоголот.
Бул кандайча болушу мүмкүн setTimeout
:
@A@let user = {
firstName: "Вася",
sayHi() {
alert(`Привет, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Привет, undefined!@A@
Бул кодду иштеткенде, биз чалуу this.firstName
"Вася" эмес, кайтып келерин көрөбүз, бирок undefined
!
setTimeout
Бул функцияны sayHi
объекттен өзүнчө алгандыктан болду user
(бул жерде функция контекстти жоготкон). Башкача айтканда, акыркы сап төмөнкүдөй кайра жазылышы мүмкүн:
@A@let f = user.sayHi;
setTimeout(f, 1000); // контекст user потеряли@A@
setTimeout
Браузердеги ыкманын өзгөчө өзгөчөлүгү бар: ал this=window
функция чакырыгын орнотот (Node.jsде this
ал таймер объектисине айланат, бирок бул жерде маанилүү эмес). Ошентип, ал жок this.firstName
алууга аракет кылат . window.firstName
Башка ушул сыяктуу учурларда, this
ал, адатта, жөн гана болуп калат undefined
.
Тапшырма абдан мүнөздүү - биз объект ыкмасын башка жерге өткөргүбүз келет (бул конкреттүү учурда, пландоочуга), ал жерде ал чакырылат. Мен аны туура контекстте аташ үчүн кантип жасамакмын?
Чечим 1: орогуч функциясын жасаңыз
Эң жөнөкөй чечим - жабууну түзүү менен чалууну анонимдүү функцияга ороп коюу:
@A@let user = {
firstName: "Вася",
sayHi() {
alert(`Привет, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // Привет, Вася!
}, 1000);@A@
Азыр код туура иштейт, анткени объект user
жабылуудан алынып, андан кийин анын sayHi
.
Ошол эле, кыскараак:
setTimeout(() => user.sayHi(), 1000); // Привет, Вася!
Жакшы көрүнөт, бирок азыр биздин кодубузда кичинекей кемчилик бар.
Эгерде операция учуруна чейин setTimeout
(анткени кечигүү бүтүндөй бир секунд!) user
өзгөрмөгө башка маани жазылса эмне болот? Ошондо чакырык күтүлбөгөн жерден таптакыр башкача болот!
@A@let user = {
firstName: "Вася",
sayHi() {
alert(`Привет, ${this.firstName}!`);
}
};
setTimeout(() => user.sayHi(), 1000);
// ...в течение 1 секунды
user = { sayHi() { alert("Другой пользователь в 'setTimeout'!"); } };
// Другой пользователь в 'setTimeout'!@A@
Төмөнкү чечим мунун болбошуна кепилдик берет.
Чечим 2: контекстти байланыш менен байланыштырыңыз
Заманбап JavaScript-те, функциялар сизге this
.
Негизги синтаксис bind
:
@A@// полный синтаксис будет представлен немного позже
let boundFunc = func.bind(context);@A@
Чакыруунун натыйжасы func.bind(context)
өзгөчө "экзотикалык объект" (термин спецификациядан алынган) болуп саналат, ал функция катары чакырылат жана func
, орнотуу учурунда чалууну ачык өткөрөт this=context
.
Башкача айтканда, чалуу белгиленген boundFunc
чалуу сыяктуу .func
this
Мисалы, бул жерде funcUser
чалуу func
, оңдоп өтөт this=user
:
@A@let user = {
firstName: "Вася"
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(user);
funcUser(); // Вася@A@
Бул жерде func.bind(user)
"байланышкан вариант" болуп саналат func
, туруктуу this=user
.
Бардык аргументтер оригиналдуу методго өткөрүлүп берилет func
, мисалы:
@A@let user = {
firstName: "Вася"
};
function func(phrase) {
alert(phrase + ', ' + this.firstName);
}
// привязка this к user
let funcUser = func.bind(user);
funcUser("Привет"); // Привет, Вася (аргумент "Привет" передан, при этом this = user)@A@
Эми объект ыкмасы менен аракет кылалы:
@A@let user = {
firstName: "Вася",
sayHi() {
alert(`Привет, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user); // (*)
sayHi(); // Привет, Вася!
setTimeout(sayHi, 1000); // Привет, Вася!@A@
Сапта (*)
биз ыкманы алып user.sayHi
, аны менен байланыштырабыз user
. Эми sayHi
бул өзүнчө чакыра турган же ага өткөрүлүп берилүүчү "кошулган" функция setTimeout
(контекст ар дайым туура болот).
bind
Бул жерде биз бир гана оңдоолор this
жана аргументтер төмөнкүдөй өткөрүлүп жатканын көрө алабыз :
@A@let user = {
firstName: "Вася",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Привет"); // Привет, Вася (аргумент "Привет" передан в функцию "say")
say("Пока"); // Пока, Вася (аргумент "Пока" передан в функцию "say")@A@
bindAll
Эгерде объектте көптөгөн ыкмалар болсо жана биз аларды активдүү өткөрүүнү пландаштырсак, анда биз алардын бардыгы үчүн контекстти циклге байлай алабыз:
@A@for (let key in user) {
if (typeof user[key] == 'function') {
user[key] = user[key].bind(user);
}
}@A@
Кээ бир JS китепканалары lodash ичиндеги _.bindAll(obj) сыяктуу ыңгайлуу жапырт контекстти бириктирүү үчүн орнотулган функцияларды камсыз кылат .
Жарым-жартылай колдонуу
Буга чейин биз байланыштыруу жөнүндө гана сүйлөштүк this
. Келгиле, андан ары кадам жасайлы.
this
Биз гана эмес , аргументтерди да байлай алабыз . Бул сейрек жасалат, бирок кээде пайдалуу болушу мүмкүн.
Толук синтаксис bind
:
@A@let bound = func.bind(context, [arg1], [arg2], ...);@A@
this
Бул функциянын контекстти жана баштапкы аргументтерин байланыштырууга мүмкүндүк берет .
Мисалы, бизде көбөйтүү функциясы бар mul(a, b)
:
@A@function mul(a, b) {
return a * b;
}@A@
Анын негизинде bind
функция түзүү үчүн колдонолу :double
@A@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@A
Чакыруу контекст катары жана - биринчи аргумент катары бекитилген чалуудан өткөн mul.bind(null, 2)
жаңы функцияны түзөт . Төмөнкү аргументтер ошол бойдон берилет.double
mul
null
2
Бул жарым-жартылай тиркеме деп аталат - биз учурдагы параметрлердин айрымдарын бекитүү менен жаңы функцияны түзөбүз.
Бул учурда биз иш жүзүндө колдонбойбуз this
. Бирок bind
бул талап кылынган параметр, андыктан биз null
.
Төмөнкү коддо функция triple
маанини үчкө көбөйтөт:
@A@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@A@
Эмне үчүн биз көбүнчө жарым-жартылай колдонулган функцияны түзөбүз?
Мунун пайдасы, ал достук аталышы ( double
, triple
) менен көз карандысыз функцияны түзүүгө болот. Биз аны колдоно алабыз жана ар бир жолу биринчи аргументти өткөрө албайбыз, анткени менен бекитилет bind
.
Башка учурларда, жарым-жартылай колдонуу бизде өтө жалпы функция болгондо жана ыңгайлуулук үчүн анын адистештирилген версиясын түзгүбүз келгенде пайдалуу.
Мисалы, бизде send(from, to, text)
функция бар Андан кийин объекттин ичинде user
биз анын жеке вариантын колдонгубуз келиши мүмкүн: sendTo(to, text)
, ал текстти учурдагы колдонуучунун атынан жөнөтөт.
Контекстсиз жарым-жартылай колдонуу
Эгер биз контекстти эмес, кээ бир аргументтерди жазгыбыз келсечи this
? Мисалы, объект ыкмасы үчүн.
Камтылган bind
бул жол бербейт. Биз жөн эле контекстти калтырып, аргументтерге өтө албайбыз.
partial
Бактыга жараша, аргументтерди гана байланыштырган жардамчы функцияны түзүү оңой .
Бул сыяктуу:
@A@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!@A@
Чалуунун натыйжасы төмөнкү чакырыктар менен коштолгон partial(func[, arg1, arg2...])
пакет болот :(*)
func
- Ал алган нерсе
this
(чалуу үчүнuser.sayNow
- бул болотuser
) - Андан кийин ага
...argsBound
чалуудагы аргументтерди өткөрүп беретpartial
("10:00"
) - Андан кийин аны өткөрөт
...args
- орогуч тарабынан алынган аргументтер ("Hello"
)
Спрэд операторуна рахмат, ...
муну ишке ашыруу абдан оңой, туурабы?
лодаш китапханадан тайяр _.партиал хем бар.
Бардыгы
Метод контекстти жана биринчи аргументтерди бекитип , bind
функциянын "байланышкан версиясын" кайтарат , эгерде берилген болсо.func
this
arg1
arg2
Көбүнчө объекттик ыкмада аны кайра чалуу катары өткөрүү үчүн bind
колдонулат . this
Мисалы, үчүн setTimeout
.
Аргументтерди бириктиргенде, мындай функция "жарым-жартылай колдонулган" же "жарым-жартылай" деп аталат.
Жарым-жартылай колдонуу биз бир эле аргументти көп жолу кайталагыбыз келбеген учурда пайдалуу. Мисалы, эгерде бизде функция бар болсо send(from, to)
жана from
биздин милдетибиз үчүн бардык убакыт бирдей болсо, анда биз жарым-жартылай колдонулган функцияны түзүп, аны менен иштөөнү уланта алабыз.
Tasks
Функция эмнени чыгарат?
@A@function f() {
alert( this ); // ?
}
let user = {
g: f.bind(null)
};
user.g();@A@
this
Кошумча байланыш менен өзгөртө алабызбы ?
Бул код эмнени чыгарат?
@A@function f() {
alert(this.name);
}
f = f.bind( {name: "Вася"} ).bind( {name: "Петя" } );
f();@A@
Функциянын касиетине маани жазылат. Колдонмодон кийин өзгөрөбү bind
? Жообуңузду актап алыңыз.
@A@function sayHi() {
alert( this.name );
}
sayHi.test = 5;
let bound = sayHi.bind({
name: "Вася"
});
alert( bound.test ); // что выведет? почему?@A@
askPassword()
Төмөндөгү коддогу чалуу паролду текшерип, user.loginOk/loginFail
жоопко жараша чалышы керек.
Бирок, аны чакыруу катага алып келет. Неге?
Баардыгы иштей тургандай кылып бөлүп көрсөтүлгөн сызыкты оңдоңуз (башка сызыктарды өзгөртүүнүн кереги жок).
@A@function askPassword(ok, fail) {
let password = prompt("Password?", '');
if (password == "rockstar") ok();
else fail();
}
let user = {
name: 'Вася',
loginOk() {
alert(`${this.name} logged in`);
},
loginFail() {
alert(`${this.name} failed to log in`);
},
};
askPassword(user.loginOk, user.loginFail);@A@
Бул тапшырма мурункулардын биринин бир аз татаалыраак версиясы болуп саналат - "ушуну" жоготкон функцияны оңдоңуз .
Объект user
өзгөртүлдү. Эми эки функциянын ордуна loginOk/loginFail
бир гана - user.login(true/false)
.
askPassword
Төмөндөгү коддогу функцияны чакырууга эмнени өткөрүп берүү керек, ал функцияны user.login(true)
катары чакырып ok
, функциясын user.login(false)
аткарышы үчүн fail
?
@A@function askPassword(ok, fail) {
let password = prompt("Password?", '');
if (password == "rockstar") ok();
else fail();
}
let user = {
name: 'John',
login(result) {
alert( this.name + (result ? ' logged in' : ' failed to log in') );
}
};
askPassword(?, ?); // ?@A@
Сиздин өзгөртүүлөрүңүз бөлүнгөн код үзүндүсүнө гана таасир этиши керек.