Loading...

Функцияга контекстти байланыштыруу

Функцияга контекстти байланыштыруу

 

Объекттин ыкмаларын кайра чалуу катары өткөрүп жатканда, мисалы үчүн 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чалуу сыяктуу .functhis

Мисалы, бул жерде 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)жаңы функцияны түзөт . Төмөнкү аргументтер ошол бойдон берилет.doublemulnull2

Бул жарым-жартылай тиркеме деп аталат - биз учурдагы параметрлердин айрымдарын бекитүү менен жаңы функцияны түзөбүз.

Бул учурда биз иш жүзүндө колдонбойбуз 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@

Эмне үчүн биз көбүнчө жарым-жартылай колдонулган функцияны түзөбүз?

Мунун пайдасы, ал достук аталышы ( doubletriple) менен көз карандысыз функцияны түзүүгө болот. Биз аны колдоно алабыз жана ар бир жолу биринчи аргументти өткөрө албайбыз, анткени менен бекитилет 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функциянын "байланышкан версиясын" кайтарат , эгерде берилген болсо.functhisarg1arg2

Көбүнчө объекттик ыкмада аны кайра чалуу катары өткөрүү үчүн bindколдонулат . thisМисалы, үчүн setTimeout.

Аргументтерди бириктиргенде, мындай функция "жарым-жартылай колдонулган" же "жарым-жартылай" деп аталат.

Жарым-жартылай колдонуу биз бир эле аргументти көп жолу кайталагыбыз келбеген учурда пайдалуу. Мисалы, эгерде бизде функция бар болсо send(from, to)жана fromбиздин милдетибиз үчүн бардык убакыт бирдей болсо, анда биз жарым-жартылай колдонулган функцияны түзүп, аны менен иштөөнү уланта алабыз.

Tasks

маанилүүлүгү: 5

Функция эмнени чыгарат?

@A@function f() {
  alert( this ); // ?
}

let user = {
  g: f.bind(null)
};

user.g();@A@
чечим
маанилүүлүгү: 5

thisКошумча байланыш менен өзгөртө алабызбы ?

Бул код эмнени чыгарат?

@A@function f() {
  alert(this.name);
}

f = f.bind( {name: "Вася"} ).bind( {name: "Петя" } );

f();@A@
чечим
маанилүүлүгү: 5

Функциянын касиетине маани жазылат. Колдонмодон кийин өзгөрөбү bind? Жообуңузду актап алыңыз.

@A@function sayHi() {
  alert( this.name );
}
sayHi.test = 5;

let bound = sayHi.bind({
  name: "Вася"
});

alert( bound.test ); // что выведет? почему?@A@
чечим
маанилүүлүгү: 5

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@
чечим
маанилүүлүгү: 5

Бул тапшырма мурункулардын биринин бир аз татаалыраак версиясы болуп саналат - "ушуну" жоготкон функцияны оңдоңуз .

Объект 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@

Сиздин өзгөртүүлөрүңүз бөлүнгөн код үзүндүсүнө гана таасир этиши керек.