25 август 2025

Dekorativlar va ekspeditorlik, call/apply

. JavaScript funktsiyalar bilan ishlashda ajoyib moslashuvchanlikni beradi. Ularni aylanib o’tish, obyektlar sifatida ishlatish mumkin va endi biz ular o’rtasida chaqiruvlarni oldinga yo’naltirish va ularni qanday bezashni ko’rib chiqamiz.

Shaffof keshlash

Aytaylik, bizda protsessor og’ir bo’lgan slow(x) funktsiyasi mavjud, ammo natijalari barqaror. Boshqacha qilib aytganda, xuddi shu x uchun u har doim bir xil natijani beradi.

Agar funktsiya tez-tez chaqirilsa, biz qayta hisoblash uchun qo’shimcha vaqt sarflamaslik uchun har xil x natijalarini keshlashni (eslashni) xohlashimiz mumkin.

Ammo bu funktsiyani slow() ga qo’shish o’rniga biz o’ramni yaratamiz. Ko’rib turganimizdek, buni amalga oshirishning foydalari juda ko’p.

Mana kod va tushuntirishlar quyidagicha:

function slow(x) {
  // bu erda og'ir CPU talab qiladigan ish bo'lishi mumkin
  alert(`Called with ${x}`);
  return x;
}

function cachingDecorator(func) {
  let cache = new Map();

  return function(x) {
    if (cache.has(x)) { // agar natija xaritada bo'lsa
      return cache.get(x); // qaytaradi
    }

    let result = func(x); // aks holda funktsiyani chaqiradi

    cache.set(x, result); // va natijani keshlaydi (eslab qolish)
    return result;
  };
}

slow = cachingDecorator(slow);

alert( slow(1) ); // slow(1) keshlangan
alert( "Again: " + slow(1) ); // xuddi shu

alert( slow(2) ); // slow(2) keshlangan
alert( "Again: " + slow(2) ); // oldingi satr bilan bir xil

Yuqoridagi kodda cachingDecorator dekorativ: boshqa funktsiyani bajaradigan va uning xatti-harakatlarini o’zgartiradigan maxsus funktsiya.

G’oya shundan iboratki, biz har qanday funktsiya uchun cachingDecorator ni chaqira olamiz va u keshlash o’ramani qaytaradi. Bu juda yaxshi, chunki bizda bunday funktsiyani ishlatishi mumkin bo’lgan juda ko’p funktsiyalar mavjud va biz ularga faqat cachingDecorator dasturini qo’llashimiz kerak.

Keshlashni asosiy funktsiya kodidan ajratib, biz ham asosiy kodni soddalashtiramiz.

Endi uning qanday ishlashi haqida batafsil ma’lumotga ega bo’laylik.

cachingDecorator(func) ning natijasi “wrapper”: function(x), func(x) chaqiruvini keshlash mantig’iga “o’rab” oladi:

Ko’rib turganimizdek, o’rash func(x) natijasini “boricha” qaytaradi. Tashqi koddan o’ralgan slow funktsiyasi hanuzgacha xuddi shunday ishlaydi. Uning xatti-harakatlariga keshlash xususiyati qo’shildi.

Xulosa qilib aytganda, slow ning kodini o’zgartirish o’rniga alohida cachingDecorator dan foydalanishning bir qancha afzalliklari bor:

  • cachingDecorator qayta ishlatilishi mumkin. Biz uni boshqa funktsiyaga qo’llashimiz mumkin.
  • Keshlash mantig’i alohida, u slow ning murakkabligini oshirmadi (agar mavjud bo’lsa).
  • Agar kerak bo’lsa, biz bir nechta dekorativlarni birlashtira olamiz (boshqa dekorativlar ergashadilar).

Kontekst uchun “func.call” dan foydalanish

Yuqorida aytib o’tilgan keshlash dekorativi obyekt usullari bilan ishlashga mos kelmaydi.

Masalan, quyidagi ishchi kodida worker.slow() dekorativ keyin ishlashni to’xtatadi:

// biz worker.slow keshlash qilamiz
let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    // aslida, bu yerda CPU uchun juda og'ir vazifa bo'lishi mumkin
    alert("Called with " + x);
    return x * this.someMethod(); // (*)
  }
};

// oldingi kod bilan bir xil
function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func(x); // (**)
    cache.set(x, result);
    return result;
  };
}

alert( worker.slow(1) ); // original usul ishlaydi

worker.slow = cachingDecorator(worker.slow); // endi uni keshlashni amalga oshiring

alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined

Xato this.someMethod ga kirishga harakat qiladigan (*) satrida paydo bo’ladi va ishlamayapti. Nega tushunyapsizmi?

Sababi shundaki, o’ram asl funktsiyani (**) satrida func(x) deb chaqiradi. Va shunga o’xshash chaqirilganda funktsiya this = undefined bo’ladi.

Agar biz bajarishga harakat qilsak, shunga o’xshash alomatni kuzatardik:

let func = worker.slow;
func(2);

Shunday qilib, o’rash chaqiruvni asl usulga o’tkazadi, ammo this kontekstisiz. Shuning uchun xato.

Keling, buni tuzataylik.

Maxsus o’rnatilgan funktsiya usuli func.call(kontekst, … args), bu funktsiyani this ni aniq belgilab qo’yishga imkon beradi.

Sintaksis:

func.call(context, arg1, arg2, ...)

Birinchi argumentni this, keyingisini esa argument sifatida taqdim etadigan func ishlaydi.

Oddiy qilib aytganda, ushbu ikkita chaqiruvlar deyarli bir xil:

func(1, 2, 3);
func.call(obj, 1, 2, 3)

Ularning ikkalasi ham 1, 2 va 3 argumentlari bilan func ni chaqirishadi. Faqatgina farq shundaki, func.call this ni obj ga o’rnatadi.

Masalan, quyidagi kodda biz sayHi ni turli xil obyektlar tarkibida chaqiramiz: sayHi.call(user) sayHi ni this=user ni ta’minlaydi va keyingi satrda this=admin o’rnatiladi:

function sayHi() {
  alert(this.name);
}

let user = { name: "John" };
let admin = { name: "Admin" };

// turli xil obyektlarni "this" sifatida o'tkazish uchun chaqiruvdan foydalaning
sayHi.call( user ); // this = John
sayHi.call( admin ); // this = Admin

Va bu yerda biz call quyidagi kontekst va iboralar bilan say ga chaqiruv qilish uchun ishlatamiz:

function say(phrase) {
  alert(this.name + ': ' + phrase);
}

let user = { name: "John" };

// user "this" "Salom" birinchi argumentga aylanadi
say.call( user, "Salom" ); // John: Salom

Bizning holatimizda kontekstni asl funktsiyaga o’tkazish uchun biz call dan foydalanishingiz mumkin:

let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    alert("Called with " + x);
    return x * this.someMethod(); // (*)
  }
};

function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func.call(this, x); // "this" hozir to'g'ri uzatildi
    cache.set(x, result);
    return result;
  };
}

worker.slow = cachingDecorator(worker.slow); // endi uni keshlashni amalga oshiring

alert( worker.slow(2) ); // ishlaydi
alert( worker.slow(2) ); // ishlaydi, asl nusxasini chaqirmaydi (keshlangan)

Endi hamma narsa yaxshi.

Barchasini aniq qilish uchun keling, this qanday o’tishini chuqurroq ko’rib chiqaylik:

  1. Dekorativdan so’ng worker.slow endi function (x) { ... }.
  2. Shunday qilib, worker.slow(2) bajarilganda, o’ram argument sifatida 2 va this = worker bo’ladi (bu nuqta oldidagi obyekt).
  3. O’rama ichida, natija hali keshlanmagan deb faraz qilsak, func.call(this, x) joriy this (= worker) va joriy argumentni (= 2) asl usulga o’tkazadi.

“func.apply” bilan ko’p argumentlarga o’tish

Endi cachingDecorator ni yanada universal qilaylik. Hozirgacha u faqat bitta argumentli funktsiyalar bilan ishlaydi.

Endi multi-argumentli worker.slow usulini qanday keshlash mumkin?

let worker = {
  slow(min, max) {
    return min + max; // qo'rqinchli CPU ga qabul qilinadi
  }
};

// bir xil argumentli chaqiruvlarni eslab qolishi kerak
worker.slow = cachingDecorator(worker.slow);

Bu yerda ikkita vazifani hal qilishimiz kerak.

Birinchidan, min va max ikkala argumentni cache xaritasidagi kalit uchun qanday ishlatish kerakligi. Ilgari, bitta x argumenti uchun biz natijani saqlash uchun cache.set(x, result) va uni olish uchun cache.get(x) Ammo endi argumentlarning kombinatsiyasi (min, max) uchun natijani eslashimiz kerak. Mahalliy Map yagona kalitni faqat kalit sifatida qabul qiladi.

Ko’plab yechimlar mavjud:

  1. Ko’p qirrali va ko’p kalitlarga imkon beradigan yangi (yoki uchinchi tomonlardan foydalangan holda) xaritaga o’xshash ma’lumotlar tuzilishini amalga oshiring.
  2. Ichki xaritalardan foydalaning: cache.set(min) juftlikni saqlaydigan Map bo’ladi (max, natija). Shunday qilib biz result ni cache.get(min).get(max) sifatida olishimiz mumkin.
  3. Ikkita qiymatni biriga qo’shib qo’ying. Bizning alohida holatimizda biz Map kalit sifatida min, max matnidan foydalanishimiz mumkin. Moslashuvchanlik uchun biz dekorativ hash funktsiyasini taqdim etishimiz mumkin, bu ko’pchilikdan bitta qiymatni qanday yaratishni biladi.

Ko’pgina amaliy dasturlar uchun 3-variant yetarli darajada yaxshi, shuning uchun biz unga rioya qilamiz.

Yechish kerak bo’lgan ikkinchi vazifa – ko’plab funktsiyalarni func ga qanday o’tkazish. Hozirda function(x) bitta argumentni qabul qiladi va func.call(this, x) uni o’tkazadi.

Bu yerda biz boshqa o’rnatilgan usuldan foydalanishingiz mumkin func.apply.

Sintaksis:

func.apply(context, args)

U argumentlar ro’yxati sifatida func sozlamasini this=context va massivga-o’xshash obyekt args dan foydalanadi.

Masalan, ushbu ikkita chaqiruv deyarli bir xil:

func(1, 2, 3);
func.apply(context, [1, 2, 3])

Ikkalasi ham 1,2,3 argumentlarini beradigan func ishlaydi. Ammo apply shuningdek this=context ni o’rnatadi.

Masalan, bu yerda say argumentlar ro’yxati sifatida this = user va messageData bilan chaqiriladi:

function say(time, phrase) {
  alert(`[${time}] ${this.name}: ${phrase}`);
}

let user = { name: "John" };

let messageData = ['10:00', 'Salom']; // vaqt va iboraga aylanadi

// foydalanuvchi shunday bo'ladi, messageData argumentlar ro'yxati (vaqt, ibora) sifatida qabul qilinadi
say.apply(user, messageData); // [10:00] John: Salom (this=user)

call va apply o’rtasidagi yagona sintaksis farqi shundaki, call argumentlar ro’yxatini kutadi, apply esa ular massivga-o’xshash obyektni oladi.

Biz qatorni (yoki istalgan takrorlanadigan) argumentlar ro’yxati sifatida o’tkaza oladigan "rest-parametrlari-spread-operator" maqolasi topilmadi bobidan ... tarqatish operatorini allaqachon bilamiz. Shunday qilib, biz uni call bilan ishlatsak, deyarli apply bilan bir xil natijalarga erishishimiz mumkin.

Ushbu ikkita chaqiruv deyarli teng:

let args = [1, 2, 3];

func.call(context, ...args); // massivga tarqatish operatori bilan ro'yxat sifatida o'tkazing
func.apply(context, args);   // murojaat qilish bilan bir xil

Agar batafsilroq ko’rib chiqadigan bo’lsak, call va apply ning bunday ishlatilishi o’rtasida ozgina farq bor.

  • ... tarqatish operatori chaqiriladigan ro’yxat sifatida iterable args larni o’tkazishga imkon beradi.
  • apply faqat massivga-o’xshash args larni qabul qiladi.

Demak, bu chaqiruvlar bir-birini to’ldiradi. Qayerda takrorlanadiganni kutsak, call ishlaydi, qayerda biz massivga-o’xshaganni kutsak, apply ishlaydi.

Va agar args takrorlanadigan va massivga-o’xshash bo’lsa, masalan, haqiqiy massiv, biz texnik jihatdan ulardan har qandayidan foydalanishimiz mumkin, ammo apply tezroq bo’ladi, chunki bu bitta operatsiya. JavaScript-ning aksariyat interpretatorlari uni call + spread juftligidan ko’ra yaxshiroq optimallashtiradi.

apply ning eng muhim usullaridan biri bu chaqiruvni boshqa funktsiyaga o’tkazishdir, masalan:

let wrapper = function() {
  return anotherFunction.apply(this, arguments);
};

Bu chaqiruvni yo’naltirish deb nomlanadi. wrapper har bir narsani oladi: this kontekstini va argumentlarni anotherFunction ga qaytaradi va natijasini qaytaradi.

Tashqi kod bunday wrapper chaqirganda, uni asl funktsiya chaqiruvidan ajratib bo’lmaydi.

Keling, barchasini yanada kuchliroq cachingDecorator da bajaraylik:

let worker = {
  slow(min, max) {
    alert(`Called with ${min},${max}`);
    return min + max;
  }
};

function cachingDecorator(func, hash) {
  let cache = new Map();
  return function() {
    let key = hash(arguments); // (*)
    if (cache.has(key)) {
      return cache.get(key);
    }

    let result = func.call(this, ...arguments); // (**)

    cache.set(key, result);
    return result;
  };
}

function hash(args) {
  return args[0] + ',' + args[1];
}

worker.slow = cachingDecorator(worker.slow, hash);

alert( worker.slow(3, 5) ); // ishlaydi
alert( "Again " + worker.slow(3, 5) ); // bir xil (keshlangan)

Endi o’rash har qanday sonli argumentlar bilan ishlaydi.

Ikki o’zgarish mavjud:

  • (*) satrida argumentlardan bitta kalit yaratish uchun hash chaqiriladi. Bu yerda biz “qo’shilish” funktsiyasidan foydalanamiz, bu (3, 5) argumentlarini "3,5" kalitiga aylantiradi. Keyinchalik murakkab holatlarda boshqa xeshlash funktsiyalari talab qilinishi mumkin.
  • So’ngra (**) dastlabki funktsiyaga (qancha bo’lishidan qat’iy nazar) kontekstni va barcha argumentlarni o’tkazish uchun func.apply dan foydalanadi.

…And for objects that are both iterable and array-like, such as a real array, we can use any of them, but apply will probably be faster, because most JavaScript engines internally optimize it better.

Passing all arguments along with the context to another function is called call forwarding.

That’s the simplest form of it:

let wrapper = function() {
  return func.apply(this, arguments);
};

When an external code calls such wrapper, it is indistinguishable from the call of the original function func.

Qarz olish usuli

Endi xeshlash funktsiyasida yana bir kichik yaxshilanishni amalga oshiramiz:

function hash(args) {
  return args[0] + ',' + args[1];
}

Hozirga kelib, u faqat ikkita argument asosida ishlaydi. Agar u biron bir sonni args ni yopishtirsa yaxshi bo’lar edi.

Tabiiy yechim arr.join usulidan foydalanish bo’ladi:

function hash(args) {
  return args.join();
}

…Afsuski, bu ishlamaydi. Biz hash(argumentlar) va argumentlar obyekti deb nomlanayotganimiz uchun ham takrorlanadigan, ham massivga-o’xshash, ammo haqiqiy massiv emas.

Shunday qilib, join ni chaqirish muvaffaqiyatsiz tugadi, biz quyida ko’rib turganimizdek:

function hash() {
  alert( arguments.join() ); // Error: arguments.join is not a function
}

hash(1, 2);

Shunga qaramay, massiv qo’shilishidan foydalanishning oson usuli mavjud:

function hash() {
  alert( [].join.call(arguments) ); // 1,2
}

hash(1, 2);

Bu hiyla usulni qarzga olish deb nomlanadi.

Biz oddiy massivda qo’shilish usulini olamiz (qarzga olamiz) [].join. Va uni argumentlar kontekstida ishlatish uchun [].join.call dan foydalanamiz.

Nima uchun u ishlaydi?

Bu chunki, arr.join(glue) mahalliy usulining ichki algoritmi juda oddiy.

Spetsifikatsiyadan deyarli “boricha” olingan:

  1. Birinchi argument glue bo’lsin, agar argument bo’lmasa, keyin "," vergul bo’lsin.
  2. result bo’sh matn bo’lsin.
  3. this[0] ni result ga qo’shib qo’ying.
  4. glue va this[1] qo’shing.
  5. glue va this[2] qo’shing.
  6. …Buni this.length elementlari yopishtirilguncha bajaring.
  7. result ni qaytaring.

Shunday qilib, texnik jihatdan bu this ni oladi va this[0], this[1] … va boshqalarni birlashtiradi. Bu ataylab har qanday massivga-o’xshash tarzda yozilgan (bu tasodif emas, ko’plab usullar ushbu amaliyotga amal qiladi). Shuning uchun u this = arguments bilan ham ishlaydi.

Xulosa

Decorator – bu uning xatti-harakatlarini o’zgartiradigan funktsiya atrofidagi o’rash. Asosiy ish hali ham funktsiya tomonidan amalga oshiriladi.

Odatda, funktsiyani yoki usulni bezatilgan bilan almashtirish xavfsizdir, faqat bitta kichik narsa bundan mustasno. Agar asl funktsiya funktsiyalariga ega bo’lsa, masalan, func.calledCount yoki boshqa narsalar bo’lsa, bezatilgan narsalar ularni ta’minlamaydi. Chunki bu o’ram. Shunday qilib, ulardan biri foydalansa, ehtiyot bo’lish kerak. Ba’zi dekorativlar o’zlarining xususiyatlarini taqdim etadilar.

Dekorativlarni funktsiyaga qo’shilishi mumkin bo’lgan “xususiyatlar” yoki “jihatlar” sifatida qarash mumkin. Biz ularni bir yoki ko’p qo’shishimiz mumkin. Va bularning barchasi uning kodini o’zgartirmasdan!

cachingDecorator ni amalga oshirish uchun biz quyidagi usullarni o’rganib chiqdik:

Umumiy chaqiruvni yo’naltirish odatda apply bilan amalga oshiriladi:

let wrapper = function() {
  return original.apply(this, arguments);
};

Shuningdek, biz obyektdan usul olib, uni boshqa obyekt kontekstida “chaqirish” paytida usulni qarzga olish misolini ko’rdik. Massiv usullarini qo’llash va ularni argumentlarga qo’llash odatiy holdir. Shu bilan bir massivda, haqiqiy massiv bo’lgan qoldiq parametrlari obyektidan foydalanish.

U yerda yovvoyi tabiatda ko’plab dekorativlar mavjud. Ushbu bobning vazifalarini hal qilish orqali ularni qanchalik yaxshi egallaganingizni tekshiring.

Vazifalar

Barcha chaqiruvlarni calls xususiyatida ishlashga saqlaydigan o’ramni qaytaradigan dekorativ spy(func) yarating.

Har bir chaqiruv argumentlar massivi sifatida saqlanadi.

Masalan:

function work(a, b) {
  alert( a + b ); // ixtiyoriy funktsiya yoki usuldak ishlaydi
}

work = spy(work);

work(1, 2); // 3
work(4, 5); // 9

for (let args of work.calls) {
  alert( 'call:' + args.join() ); // "call:1,2", "call:4,5"
}

P.S. Ushbu dekorativ ba’zan birlik sinovi uchun foydalidir. Kengaytirilgan shakli – Sinon.JS kutubxonasida sinon.spy.

Sinovlar bilan sandbox-ni oching.

Bu yerda biz barcha argumentlarni logda saqlash uchun calls.push(args) va chaqiruvni yo’naltirish uchun f.apply(this, args) dan foydalanishimiz mumkin.

function spy(func) {
  function wrapper(...args) {
    // using ...args instead of arguments to store "real" array in wrapper.calls
    wrapper.calls.push(args);
    return func.apply(this, args);
  }

  wrapper.calls = [];

  return wrapper;
}

Yechimni sandbox-dagi sinovlar bilan oching.

f ning har bir chaqiruvini ms millisoniyalarga kechiktiradigan dekorativ delay(f, ms) yarating.

Masalan:

function f(x) {
  alert(x);
}

// o'rama yarating
let f1000 = delay(f, 1000);
let f1500 = delay(f, 1500);

f1000("test"); // 1000ms dan keyin "test" ni ko'rsatadi
f1500("test"); // 1500ms dan keyin "test" ni ko'rsatadi

Boshqacha qilib aytganda, delay(f, ms) f ning ms bilan kechiktirilganligini qaytaradi.

Yuqoridagi kodda f bitta argumentning funktsiyasidir, ammo sizning yechimingiz barcha argumentlarni va kontekstni this dan o’tkazishi kerak.

Sinovlar bilan sandbox-ni oching.

Yechim:

function delay(f, ms) {
  return function () {
    setTimeout(() => f.apply(this, arguments), ms);
  };
}

let f1000 = delay(alert, 1000);

f1000("test"); // 1000ms dan keyin "test" ni ko'rsatadi

Iltimos, bu yerda o’q funktsiyasidan qanday foydalanilganiga e’tibor bering. Ma’lumki, o’q funktsiyalari o’zlarining this va argumentlari ga ega emas, shuning uchun f.apply(this, arguments) this va argumentlarni o’ramdan oladi.

Agar biz odatdagi funktsiyani o’tkazsak, setTimeout uni argumentlarsiz chaqiradi va this=window (biz brauzerda ekanligimizni taxmin qilamiz).

Biz hali ham this dan oraliq o’zgaruvchanni ishlatib o’tishimiz mumkin, ammo bu biroz noqulayroq:

function delay(f, ms) {
  return function (...args) {
    let savedThis = this; // buni oraliq o'zgaruvchanga saqlang
    setTimeout(function () {
      f.apply(savedThis, args); // bu yerda ishlating
    }, ms);
  };
}

Yechimni sandbox-dagi sinovlar bilan oching.

debounce(f, ms) dekorativ natijasi ms milisoniyasiga maksimal bir marta chaqiruvni f ga yetkazadigan o’rash bo’lishi kerak.

Boshqacha qilib aytganda, biz “debounced” funktsiyani chaqirganimizda, u eng yaqin ms millisoniyalarda qolgan barcha kelajaklarga e’tibor berilmasligini kafolatlaydi.

Masalan:

Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual f will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call.

f(1); // darhol ishlaydi f(2); // e’tiborsiz qoldirildi

setTimeout( () => f(3), 100); // e’tiborsiz qoldirildi (atigi 100 ms o’tdi) setTimeout( () => f(4), 1100); // ishlaydi setTimeout( () => f(5), 1500); // e’tiborsiz qoldirildi (oxirgi ishdan 1000 ms dan kam)

Amalda "debounce" bu qisqa vaqt ichida hech qanday yangi narsa qilinmasligini bilsak, biror narsani oladigan / yangilaydigan funktsiyalar uchun foydalidir, shuning uchun resurslarni sarf qilmaslik yaxshiroqdir.

Sinovlar bilan sandbox-ni oching.

function debounce(func, ms) {
  let timeout;
  return function () {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, arguments), ms);
  };
}

debounce ga chaqiruv o’ramni qaytaradi. Ikki variant bo’lishi mumkin:

  • isCooldown = false – bajarilishga tayyor.
  • isCooldown = true – vaqt tugashini kutmoqda.

Birinchi chaqiruvda isCooldown false, shuning uchun chaqiruv davom etadi va holat true ga o’zgaradi.

isCooldown true bo’lsa, boshqa barcha chaqiruvlar e’tiborga olinmaydi.

Keyin setTimeout berilgan kechikishdan keyin uni false ga qaytaradi.

Yechimni sandbox-dagi sinovlar bilan oching.

muhimlik: 5

“Tormozlash” dekorativini yarating throttle(f, ms) – bu o’rashni qaytarib beradi va chaqiruvni maksimal ravishda ms milisoniyalarda bir marta f ga yetkazadi. “Sovutish” davriga to’g’ri keladigan ushbu chaqiruvlar e’tiborga olinmaydi.

debounce bilan farq – agar sovuq vaqt davomida e’tiborsiz qilingan chaqiruv oxirgi bo’lsa, u kechikish oxirida amalga oshiriladi.

Keling, ushbu talabni yaxshiroq tushunish va qayerdan kelib chiqqanligini bilish uchun real dasturni tekshirib ko’raylik.

Masalan, biz sichqoncha harakatlarini kuzatishni xohlaymiz.

Brauzerda biz sichqonchaning har qanday mikro harakatida ishlaydigan funktsiyani o’rnatamiz va u harakatlanayotganda ko’rsatgich o’rnini topamiz. Sichqonchani faol ishlatish paytida bu funktsiya odatda juda tez-tez ishlaydi va sekundiga 100 marta (har 10 msda) bo’lishi mumkin.

Kuzatuv funktsiyasi veb-sahifadagi ba’zi ma’lumotlarni yangilashi kerak.

update() funktsiyasini yangilash har bir mikro harakatlarda buni amalga oshirish uchun juda og’ir. Bundan tashqari, uni 100 ms bir martadan ko’proq qilishning ma’nosi yo’q.

Shunday qilib biz asl update() o’rniga har bir sichqoncha harakatida ishlaydigan funktsiya sifatida throttle(update, 100) ni tayinlaymiz. Dekorator tez-tez chaqiriladi, lekin update() maksimal 100ms uchun bir marta chaqiriladi.

Vizual ravishda shunday bo’ladi:

  1. Sichqonchaning birinchi harakati uchun bezatilgan variant update ga chaqiruv qiladi. Muhimi, foydalanuvchi bizning harakatlarimizga bo’lgan munosabatni darhol ko’radi.
  2. Sichqoncha harakatlanayotganda, 100ms ga qadar hech narsa bo’lmaydi. Bezaklangan variant chaqiruvlarni e’tiborsiz qoldiradi.
  3. 100ms oxirida yana bitta update oxirgi koordinatalar bilan sodir bo’ladi.
  4. Keyin, nihoyat, sichqon bir joyda to’xtaydi. Bezaklangan variant 100ms tugashini kutadi va keyin so’nggi koordinatalar bilan update ni ishga tushiradi. Shunday qilib, ehtimol, eng muhimi, so’nggi sichqonchaning koordinatalari qayta ishlanadi.

Kod misoli:

function f(a) {
  console.log(a);
}

// f1000 chaqiruvlarni maksimal 1000 ms uchun bir martadan f ga yuboradi
let f1000 = throttle(f, 1000);

f1000(1); // ko'rsatadi 1
f1000(2); // (tejamkorlik, 1000ms hali chiqmagan)
f1000(3); // (tejamkorlik, 1000ms hali chiqmagan)

// 1000 ms vaqt tugashi bilan...
// ...natija 3, oraliq qiymat 2 hisobga olinmadi

P.S. this f1000 ga o’tgan argumentlar va kontekst asl f ga o’tkazilishi kerak.

Sinovlar bilan sandbox-ni oching.

function throttle(func, ms) {
  let isThrottled = false,
    savedArgs,
    savedThis;

  function wrapper() {
    if (isThrottled) {
      // (2)
      savedArgs = arguments;
      savedThis = this;
      return;
    }
    isThrottled = true;

    func.apply(this, arguments); // (1)

    setTimeout(function () {
      isThrottled = false; // (3)
      if (savedArgs) {
        wrapper.apply(savedThis, savedArgs);
        savedArgs = savedThis = null;
      }
    }, ms);
  }

  return wrapper;
}

throttle(func, ms) ga chaqiruv wrapper qaytaradi.

  1. Birinchi chaqiruv paytida, wrapper shunchaki func funktsiyasini bajaradi va sovutish holatini o’rnatadi (isThrottled = true).
  2. Ushbu holatda barcha savedArgs/savedThis da yodlangan chaqiruvlar. Iltimos, kontekst va argumentlar bir xil ahamiyatga ega va ularni yodlash kerak. Chaqiruvni takrorlash uchun ularga bir vaqtning o’zida kerak.
  3. …Keyin ms millisoniyalar o’tgandan so’ng, setTimeout triggerlar. Sovutish holati olib tashlandi (isThrottled = false). Agar biz chaqiruvni e’tiborsiz qoldirgan bo’lsak, u holda wrapper oxirgi yodlangan argumentlar va kontekst bilan bajariladi.

3-qadam func emas, balki wrapper ni ishga tushiradi, chunki biz nafaqat func ni bajarishimiz kerak, balki yana bir marta kutish holatiga kiramiz va uni tiklash uchun vaqt tugashini o’rnatamiz.

Yechimni sandbox-dagi sinovlar bilan oching.

O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <code> yorlig'ini ishlating, bir nechta satrlar uchun - ularni <pre> yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)