JavaScript функциялар менен иштөөнүн өтө ийкемдүү жолдорун сунуштайт: аларды башка функцияларга өткөрүп, объект катары колдонсо болот, эми биз алардын ортосунда чалууларды кантип багыттоо жана аларды кантип кооздоону карап чыгабыз .
Ачык кэштөө
slow(x)
Келгиле, бизде ресурсту көп талап кылган эсептөөлөрдү жүргүзгөн, бирок туруктуу натыйжаларды берген функция бар деп элестетип көрөлү . Башка сөз менен айтканда, бир эле нерсе үчүн, x
ал ар дайым ошол эле натыйжаны берет.
Функция тез-тез чакырылса, анда биз кайра эсептөөлөргө убакытты үнөмдөө үчүн ал кайтарып берген натыйжаларды кэштегибиз (эстөөнү) каалайбыз.
Кошумча функционалдуулук менен татаалдаштырбастан slow(x)
, биз аны орогуч функциясына - "wrapper" (англисчеден "wrap" - орогуч), кэш кошууга кошобуз. Бул ыкманын көптөгөн артыкчылыктары бар экенин кийинчерээк көрөбүз.
Бул жерде түшүндүрмөлөр менен код:
@A@function slow(x) {
// здесь могут быть ресурсоёмкие вычисления
alert(`Called with ${x}`);
return x;
}
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) { // если кеш содержит такой x,
return cache.get(x); // читаем из него результат
}
let result = func(x); // иначе, вызываем функцию
cache.set(x, result); // и кешируем (запоминаем) результат
return result;
};
}
slow = cachingDecorator(slow);
alert( slow(1) ); // slow(1) кешируем
alert( "Again: " + slow(1) ); // возвращаем из кеша
alert( slow(2) ); // slow(2) кешируем
alert( "Again: " + slow(2) ); // возвращаем из кеша@A@
Жогорудагы коддо cachingDecorator
бул жасалгалоочу , башка функцияны алып, анын жүрүм-турумун өзгөрткөн атайын функция.
Идея, биз cachingDecorator
каалаган функция менен чалсак болот, натыйжада кэштөөчү таңгыч пайда болот. Бул сонун, анткени бизде бул функцияны колдонгон көптөгөн функциялар болушу мүмкүн жана биз кылышыбыз керек болгон нерсе - колдонуу cachingDecorator
.
Кэш кодун негизги коддон бөлүү менен биз акыркысын да таза жана жөнөкөй сактайбыз.
Чалуунун натыйжасы cachingDecorator(func)
"ороочу", б.а. Кэштөө логикасында function(x)
чалууну "орот" :func(x)
Сырткы коддун көз карашынан алганда, оролгон функция slow
дагы эле ошол эле нерсени кылат. Ороочу жөн гана анын жүрүм-турумуна кэш аспектисин кошот.
cachingDecorator
Жыйынтыктап айтканда, коддун өзүн өзгөртүүнүн ордуна өзүнчө колдонуунун бир нече артыкчылыктары бар slow
:
- Функцияны
cachingDecorator
кайра колдонсо болот. Биз аны башка функцияга колдоно алабыз. - Кэштөө логикасы өзүнчө, ал өзүнүн татаалдыгын арттырбайт
slow
(эгерде бар болсо). - Зарыл болсо, биз бир нече жасалгалоочуларды бириктире алабыз (бул тууралуу кийинчерээк сүйлөшөбүз).
Контекстти өткөрүү үчүн "func.call" колдонуу.
Жогоруда айтылган кэш жасалгалоочу объект ыкмалары менен иштөө үчүн ылайыктуу эмес.
Мисалы, төмөнкү коддо, worker.slow()
ал декораторду колдонгондон кийин иштебей калат:
@A@// сделаем worker.slow кеширующим
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(x); // (**)
cache.set(x, result);
return result;
};
}
alert( worker.slow(1) ); // оригинальный метод работает
worker.slow = cachingDecorator(worker.slow); // теперь сделаем его кеширующим
alert( worker.slow(2) ); // Ой! Ошибка: не удаётся прочитать свойство 'someMethod' из 'undefined'@A@
Ката линияда пайда болот (*)
. Функция кирүүгө аракет кылат this.someMethod
жана ишке ашпай калат. Эмнеге көрдүңүз?
Себеби, сызыкта (**)
жасалгалоочу баштапкы функцияны деп атайт func(x)
, бул учурда ал алат this = undefined
.
Эгерде биз чуркай турган болсок, окшош жагдайды байкамакпыз:
let func = worker.slow;
func(2);
Ошол. жасалгалоочу чакырууну баштапкы методго өткөрөт, бирок контекстсиз. Демек, ката.
Келгиле, муну оңдойлу.
Func.call (контекст, …args) атайын орнотулган функция ыкмасы бар , ал функцияны ачык коюу менен чакырууга мүмкүндүк берет this
.
Синтаксис:
func.call(context, arg1, arg2, ...)
func
Ал биринчи аргументти контекст катары this
жана кийинки аргументтерди аргумент катары колдонуу менен функцияны иштетет .
Жөнөкөй сөз менен айтканда, бул эки чалуу дээрлик бирдей нерсени аткарат:
func(1, 2, 3);
func.call(obj, 1, 2, 3)
Экөө тең func
аргументтер менен 1
чакырышат 2
жана 3
. Бир гана айырмасы - бул func.call
да this
белгилейт obj
.
Мисалы, төмөндөгү коддо биз sayHi
ар кандай объекттердин контекстинде чакырабыз: sayHi.call(user)
runs sayHi
, passing this=user
, жана төмөнкү сап топтомдору this=admin
:
@A@function sayHi() {
alert(this.name);
}
let user = { name: "John" };
let admin = { name: "Admin" };
// используем 'call' для передачи различных объектов в качестве 'this'
sayHi.call( user ); // John
sayHi.call( admin ); // Admin
Бул жерде биз берилген контекст жана сөз айкашы менен call
чакыруу үчүн колдонобуз:say
function say(phrase) {
alert(this.name + ': ' + phrase);
}
let user = { name: "John" };
// 'user' становится 'this', и "Hello" становится первым аргументом
say.call( user, "Hello" ); // John: Hello
call
Биздин учурда, контекстти баштапкы функцияга өткөрүү үчүн орогучта колдоно алабыз :
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' передаётся правильно
cache.set(x, result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow); // теперь сделаем её кеширующей
alert( worker.slow(2) ); // работает
alert( worker.slow(2) ); // работает, не вызывая первоначальную функцию (кешируется)@A@
Азыр баары жайында.
Баары түшүнүктүү болушу үчүн, келгиле, анын кантип жугушун тереңирээк карап көрөлү this
:
- Декорациядан кийин
worker.slow
ал орогуч болуп калатfunction (x) { ... }
. - Ошентип, аткарылганда,
worker.slow(2)
орогуч2
аргумент катары кабыл алат жанаthis=worker
(анткени бул чекиттин алдындагы объект). - Ороочтун ичинде, эгерде натыйжа буга чейин кэштеле элек болсо,
func.call(this, x)
учурдагыthis
(=worker
) жана учурдагы аргументти (=2
) баштапкы функцияга өткөрөт.
"func.apply" менен бир нече аргументтерди өткөрүү
cachingDecorator
Эми аны ого бетер ар тараптуу кылалы . Буга чейин ал бир гана аргументтүү функциялар менен иштеген.
Бир нече аргументи бар ыкманы кантип кэштөө керек worker.slow
?
@A@let worker = {
slow(min, max) {
return min + max; // здесь может быть тяжёлая задача
}
};
// будет кешировать вызовы с одинаковыми аргументами
worker.slow = cachingDecorator(worker.slow);@A@
Бул жерде биз чече турган эки маселе бар.
Биринчиден, коллекциядагы аргументти min
жана ачкычты кантип колдонсо болот ? Мурда бир аргумент үчүн биз жөн гана натыйжаны сактап , аны кийинчерээк алуу үчүн чакыра алмакпыз. Бирок азыр биз аргументтердин айкалышынын жыйынтыгын эстешибиз керек . Курулган ачкыч катары бир гана маанини кабыл алат.max
cache
x
cache.set(x, result)
cache.get(x)
(min,max)
Map
Көптөгөн мүмкүн болгон чечимдер бар:
- Жыйноо үчүн жаңы (же үчүнчү тарапты колдонуңуз) орнотулганга караганда жалпы
Map
жана бир нече ачкычтарды колдогон маалымат структурасын ишке ашырыңыз. - Уюшкан коллекцияларды колдонуңуз:
cache.set(min)
willMap
, ал жупту сактайт(max, result)
. Андан кийинresult
телефон чалып алсак болотcache.get(min).get(max)
. - Эки баалуулукту бирге бириктириңиз. Биздин өзгөчө учурда, биз жөн гана сапты
"min,max"
ачкыч катары колдоно алабызMap
. Ийкемдүүлүк үчүн, биз хэшинг функциясын көптүн ичинен бир маанини кантип жасоону билген декораторго өткөрүүгө уруксат бере алабыз.
Көптөгөн практикалык колдонмолор үчүн үчүнчү вариант жетиштүү, ошондуктан биз аны карманабыз.
Биринчи эле эмес, оролгон функцияга бардык аргументтерди өткөрүү үчүн биз func.call(this, x)
менен алмаштыруу керек .func.call(this, ...arguments)
Бул жерде күчтүүрөөк cachingDecorator
:
@A@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) ); // работает
alert( "Again " + worker.slow(3, 5) ); // аналогично (из кеша)@A@
Азыр ал аргументтердин ар кандай саны менен иштейт.
Эки өзгөртүү бар:
- Бир ачкычты түзүү үчүн онлайн
(*)
чалуу . Бул жерде биз аргументтерди ачкычка айландырган жөнөкөй "биримдик" функциясын колдонуп жатабыз . Татаал учурлар башка хэшинг функцияларын талап кылышы мүмкүн.hash
arguments
(3, 5)
"3,5"
- Андан кийин, бир сапта биз аны контекстти да, орогуч тарабынан алынган бардык аргументтерди (алардын санына карабастан) баштапкы функцияга өткөрүү үчүн
(**)
колдонобуз .func.call(this, ...arguments)
Анын ордуна, func.call(this, ...arguments)
биз жаза алабыз func.apply(this, arguments)
.
Камтылган func.apply методунун синтаксиси :
func.apply(context, args)
Ал аргументтердин тизмеси катары псевдо-массивди func
коюу жана алуу менен аткарат .this=context
args
call
жана ортосундагы синтаксистин бир гана айырмасы, apply
ал псевдо-массивди алып call
жатканда аргументтердин тизмесин күтөт .apply
Бул эки чалуу дээрлик эквиваленттүү:
@A@func.call(context, ...args); // передаёт массив как список с оператором расширения
func.apply(context, args); // тот же эффект@A@
Бир гана кичинекей айырма бар:
- Жайылтуу оператору кайталанган объектти тизмеге
...
өткөрүүгө мүмкүндүк берет .args
call
- Ал псевдо-массивди
apply
гана кабыл алат .args
Ошентип, бул чакырыктар бири-бирин толуктап турат. Кайталануучу объекттер үчүн иштейт call
, жана биз псевдо-массив күткөн жерде - apply
.
Эгер бизде экөөнү тең аткарган объект болсо, мисалы, чыныгы массив, анда техникалык жактан биз эки ыкманы колдонсок болот, бирок, балким, apply
тезирээк болмок, анткени көпчүлүк JavaScript кыймылдаткычтары аны ички оптималдаштырышат.
Бардык аргументтерди башка функциянын контексти менен бирге өткөрүү "чакырууну багыттоо" деп аталат.
Мындай багыттоо эң жөнөкөй түрү:
@A@let wrapper = function() {
return func.apply(this, arguments);
};@A@
Тышкы коддон чакырганда wrapper
, ал баштапкы функцияны чакыруудан айырмаланбайт.
Карыз алуу ыкмасы
Эми хэш-функцияга дагы бир кичинекей жакшыртуу жасайлы:
@A@function hash(args) {
return args[0] + ',' + args[1];
}@A@
Учурда ал эки гана аргумент үчүн иштейт. Каалаган санды чаптап алса жакшы болмок args
.
Табигый чечим arr.join ыкмасын колдонуу болот :
@A@function hash(args) {
return args.join();
}@A@
...Тилекке каршы, бул иштебейт, анткени биз чалып жатабыз hash(arguments)
жана объект arguments
кайталануучу жана чыныгы массив эмес, псевдо-массив.
join
Ошентип , биз төмөндө көрүп тургандай, ага чакыруу ишке ашпай калат:
@A@function hash() {
alert( arguments.join() ); // Ошибка: arguments.join не является функцией
}
hash(1, 2);@A@
Бирок, массивди бириктирүүнү колдонуунун оңой жолу бар:
@A@function hash() {
alert( [].join.call(arguments) ); // 1,2
}
hash(1, 2);@A@
Бул ыкма карыз алуу деп аталат .
join
Биз кадимки массивден методду алабыз (карызга алабыз) [].join
. Жана биз [].join.call
аны контекстте аткаруу үчүн колдонобуз arguments
.
Эмне үчүн ал иштейт?
Себеби, орнотулган ыкманын ички алгоритми arr.join(glue)
абдан жөнөкөй. Иш жүзүндө "болгондой" спецификациядан алынган:
- Биринчи аргумент болсун
glue
же аргументтер жок болсо үтүр болот","
result
Бул бош сап болсун""
.- кошуу .
this[0]
_result
- кошуу
glue
жанаthis[1]
. - кошуу
glue
жанаthis[2]
. - ...
this.length
элементтер бири-бирине чапталганга чейин аткарыңыз. - Return
result
.
Ошентип, техникалык жактан ал алат this
жана бириктирет this[0]
, this[1]
... ж.б. бирге. Бул кандайдыр бир псевдо-массивди кабыл алуу үчүн атайын жазылган this
(кокусунан эмес, көптөгөн ыкмалар бул практиканы аткарат). Ошондуктан ал менен да иштейт this=arguments
.
Бардыгы
Декоратор - бул функциянын кыймыл-аракетин өзгөрткөн оролгон. Негизги ишти дагы эле функция аткарат.
Көбүнчө бир кичинекей нерседен башка функцияны же ыкманы кооздолгонго алмаштыруу коопсуз. Эгерде оригиналдуу функция ушуга окшогон func.calledCount
же ушул сыяктуу касиеттерди камсыз кылса, анда кооздолгон функция аларды камсыз кылбайт. Анткени ал орогуч. Андыктан аларды колдонууда этият болушуңуз керек. Кээ бир жасалгалоочулар өз касиеттерин камсыз кылат.
Декораторлорду функцияга кошула турган "кошумча функциялар" же "аспекттер" катары кароого болот. Биз бир же бир нече жасалгалоочуну кошо алабыз. Мунун баары баштапкы функциянын кодун өзгөртпөстөн!
Ишке ашыруу үчүн cachingDecorator
биз ыкмаларды изилдедик:
- func.call(контекст, arg1, arg2…) -
func
берилген контекст жана аргументтер менен чалуулар. - func.apply(контекст, args) - Аргументтердин тизмеси катары да, псевдо-массивди да
func
өткөргөн чалуулар.context
this
args
Негизинен чалууларды багыттоо төмөнкү менен жүзөгө ашырылат apply
:
let wrapper = function(original, arguments) {
return original.apply(this, arguments);
};
Биз ошондой эле карыз алуу ыкмасын карадык , анда биз башка объекттин контекстинде объект боюнча методду чакырабыз. Массив ыкмаларын алуу жана аларды колдонуу үчүн кеңири таралган arguments
. ...args
Же болбосо, чыныгы массив болгон rest объектин колдоно аласыз .
Иш жүзүндө, декораторлор ар кандай милдеттерди аткаруу үчүн колдонулат. Бул бөлүмдөгү маселелерди чечүү менен аларды канчалык жакшы өздөштүргөнүңүздү текшериңиз.
Tasks
spy(func)
Бардык функциялык чакырыктарды өзүнүн менчигинде сактаган таңгычты кайтарып бере турган жасалгалоочуну түзүңүз calls
.
Ар бир чалуу аргументтердин массивинде сакталышы керек.
Мисалы:
@A@function work(a, b) {
alert( a + b ); // произвольная функция или метод
}
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"
}@A@
PS: Бул жасалгалоочу кээде бирдикти сыноо үчүн пайдалуу. Анын кеңейтилген түрү - - Sinon.JSsinon.spy
китепканасында камтылган .
Ар бир чалууну миллисекундга delay(f, ms)
кечиктирген жасалгалоочу түзүңүз . Мисалы:f
ms
@A@function f(x) {
alert(x);
}
// создаём обёртки
let f1000 = delay(f, 1000);
let f1500 = delay(f, 1500);
f1000("test"); // показывает "test" после 1000 мс
f1500("test"); // показывает "test" после 1500 мс@A@
Башка сөз менен айтканда, ал "мес үчүн кечигүү " параметрин delay(f, ms)
кайтарат .f
ms
Жогорудагы коддо f
бул бир аргументи бар функция, бирок сиздин чечимиңиз бардык аргументтерден жана контексттен өтүшү керек this
.
Декоратордун натыйжасы debounce(f, ms)
чалуудан миллисекундда f
эң көп дегенде бир жолу өтүүчү орогуч болушу керек ms
. Башкача айтканда, биз чалганда debounce
, ал бардык башка чалуулар учурунда көңүл бурулбай турганын камсыздайт ms
.
Мисалы:
@A@let f = debounce(alert, 1000);
f(1); // выполняется немедленно
f(2); // проигнорирован
setTimeout( () => f(3), 100); // проигнорирован (прошло только 100 мс)
setTimeout( () => f(4), 1100); // выполняется
setTimeout( () => f(5), 1500); // проигнорирован (прошло только 400 мс от последнего вызова)@A@
Маалыматтарды алган/жаңыртуучу функциялар үчүн практикада пайдалуу debounce
жана кыска убакыттын ичинде кайра чалуу жаңы эч нерсе алып келбей турганын билебиз. Андыктан ага ресурстарды коротпогону жакшы.
Чалууну миллисекундда эң көп дегенде бир жолу throttle(f, ms)
өткөрүп, орогучту кайтарган жай жасалгалоочуну түзүңүз . «Тормоздук» мезгилге туш келген чакырыктарга көңүл бурулбайт.f
ms
Айырмачылыгы debounce
- эгерде көңүл бурулбаган чалуу "кечигүү" учурунда акыркы болуп калса, анда ал аягында аткарылат.
Келгиле, бул талапты жакшыраак түшүнүү жана анын кайдан келгенин билүү үчүн реалдуу тиркемени карап көрөлү.
Мисалы, биз чычкандын кыймылына көз салгыбыз келет.
Браузерде көрсөткүч жылган сайын иштей турган жана анын жайгашкан жерин ала турган функцияны жарыялай алабыз. Чычканды активдүү колдонуу учурунда бул функция абдан тез иштейт, ал секундасына 100 жолу (ар бир 10 мс) болушу мүмкүн.
Көчкөндө баракчадагы маалыматты жаңырткыбыз келет.
...Бирок жаңыртуу функциясы update()
өтө көп ресурстарды талап кылат, аны ар бир микро кыймыл менен аткаруу үчүн. Ооба, 1000 мс сайын бир жолудан көп жаңыртуу мааниси жок.
throttle(update, 1000)
Ошондуктан, биз чакырууну жасалгалоочуга оройбуз: аны көрсөткүчтүн ордуна оригиналдуу ордуна жылдырган сайын иштей турган функция катары колдонобуз update()
. Декоратор тез-тез чакырылат, бирок чалууну update()
эң көп дегенде 1000 мс бир жолу жөнөтөт.
Визуалдык түрдө ал төмөнкүдөй болот:
- Көрсөткүчтүн биринчи кыймылы үчүн кооздолгон версия дароо чакырууну
update
. Бул маанилүү, анткени колдонуучу дароо анын кыймылына биздин реакцияны көрөт. - Андан кийин, көрсөткүч кыймылды улантканда, 1000 мс эч нерсе болбойт. Декорацияланган версия чалууларга көңүл бурбайт.
- 1000 мсден кийин
update
акыркы координаттар менен дагы бир чакыруу пайда болот. - Анан, акырында, көрсөткүч бир жерде токтойт. Декорацияланган версия 1000 мс өткүчө күтүп, андан соң
update
акыркы координаттарды чакырат. Натыйжада көрсөткүчтүн акыркы координаттары да иштетилет.
Код мисалы:
@A@function f(a) {
console.log(a)
}
// f1000 передаёт вызовы f максимум раз в 1000 мс
let f1000 = throttle(f, 1000);
f1000(1); // показывает 1
f1000(2); // (ограничение, 1000 мс ещё нет)
f1000(3); // (ограничение, 1000 мс ещё нет)
// когда 1000 мс истекли ...
// ...выводим 3, промежуточное значение 2 было проигнорировано@A@
PS Аргументтер жана this
берилген контекст f1000
түпнускага өткөрүлүшү керек f
.